Merge branch 'master' of https://github.com/nataliemeurer/logging-log4j2 into nataliemeurer-master
diff --git a/.asf.yaml b/.asf.yaml
new file mode 100644
index 0000000..d976e25
--- /dev/null
+++ b/.asf.yaml
@@ -0,0 +1,17 @@
+notifications:
+ commits: commits@logging.apache.org
+ # issues and PRs send their own emails to devs
+ issues: notifications@logging.apache.org
+ pullrequests: notifications@logging.apache.org
+ jira_options: link label
+github:
+ description: "Apache Log4j 2 is an upgrade to Log4j that provides significant improvements over its predecessor, Log4j 1.x, and provides many of the improvements available in Logback while fixing some inherent problems in Logback's architecture."
+ homepage: https://logging.apache.org/log4j/2.x/
+ labels:
+ - apache
+ - api
+ - java
+ - library
+ - log4j
+ - log4j2
+ - logging
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..d0e9890
--- /dev/null
+++ b/.github/dependabot.yml
@@ -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.
+#
+version: 2
+updates:
+- package-ecosystem: maven
+ directory: "/"
+ schedule:
+ interval: daily
+ time: '04:00'
+ open-pull-requests-limit: 10
diff --git a/.github/workflows/maven-toolchains.xml b/.github/workflows/maven-toolchains.xml
new file mode 100644
index 0000000..066a50f
--- /dev/null
+++ b/.github/workflows/maven-toolchains.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF8"?>
+<!--
+ ~ Licensed to the Apache Software Foundation (ASF) under one or more
+ ~ contributor license agreements. See the NOTICE file distributed with
+ ~ this work for additional information regarding copyright ownership.
+ ~ The ASF licenses this file to You under the Apache license, Version 2.0
+ ~ (the "License"); you may not use this file except in compliance with
+ ~ the License. You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the license for the specific language governing permissions and
+ ~ limitations under the license.
+ -->
+<toolchains>
+ <toolchain>
+ <type>jdk</type>
+ <provides>
+ <version>1.8</version>
+ </provides>
+ <configuration>
+ <jdkHome>${env.JAVA_HOME_8_X64}</jdkHome>
+ </configuration>
+ </toolchain>
+ <toolchain>
+ <type>jdk</type>
+ <provides>
+ <version>11</version>
+ </provides>
+ <configuration>
+ <jdkHome>${env.JAVA_HOME_11_X64}</jdkHome>
+ </configuration>
+ </toolchain>
+</toolchains>
diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml
new file mode 100644
index 0000000..d6338a6
--- /dev/null
+++ b/.github/workflows/maven.yml
@@ -0,0 +1,55 @@
+name: Maven
+
+on: [push]
+
+jobs:
+ build:
+
+ runs-on: ${{ matrix.os }}
+
+ strategy:
+ matrix:
+ os: [ubuntu-latest, windows-latest]
+
+ steps:
+
+ - name: Checkout repository
+ uses: actions/checkout@v2
+
+ - name: Setup Maven caching
+ uses: actions/cache@v2
+ with:
+ path: ~/.m2/repository
+ key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
+ restore-keys: |
+ ${{ runner.os }}-maven-
+
+ - name: Setup JDK 11
+ uses: actions/setup-java@v1
+ with:
+ java-version: 11
+ java-package: jdk
+ architecture: x64
+
+ - name: Setup JDK 8
+ uses: actions/setup-java@v1
+ with:
+ java-version: 8
+ java-package: jdk
+ architecture: x64
+
+ - name: Inspect environment (Linux)
+ if: runner.os == 'Linux'
+ run: env | grep '^JAVA'
+
+ - name: Build with Maven (Linux)
+ if: runner.os == 'Linux'
+ run: ./mvnw -V -B -e -DtrimStackTrace=false verify --global-toolchains .github/workflows/maven-toolchains.xml
+
+ - name: Inspect environment (Windows)
+ if: runner.os == 'Windows'
+ run: set java
+
+ - name: Build with Maven (Windows)
+ if: runner.os == 'Windows'
+ run: ./mvnw -V -B -e -DtrimStackTrace=false verify --global-toolchains ".github\workflows\maven-toolchains.xml"
diff --git a/.gitignore b/.gitignore
index fce0e91..533fdc3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -27,3 +27,4 @@
velocity.log
felix-cache/
bin/
+.mvn/wrapper/MavenWrapperDownloader.java
diff --git a/.mvn/wrapper/maven-wrapper.jar b/.mvn/wrapper/maven-wrapper.jar
index c6feb8b..2cc7d4a 100644
--- a/.mvn/wrapper/maven-wrapper.jar
+++ b/.mvn/wrapper/maven-wrapper.jar
Binary files differ
diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties
index d183696..a8cc56e 100644
--- a/.mvn/wrapper/maven-wrapper.properties
+++ b/.mvn/wrapper/maven-wrapper.properties
@@ -13,4 +13,5 @@
# 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
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip
+wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar
diff --git a/.travis-toolchains.xml b/.travis-toolchains.xml
index 53ff360..e341594 100644
--- a/.travis-toolchains.xml
+++ b/.travis-toolchains.xml
@@ -25,18 +25,18 @@
<vendor>oracle</vendor>
</provides>
<configuration>
- <jdkHome>/usr/lib/jvm/java-8-oracle</jdkHome>
+ <jdkHome>/usr/lib/jvm/java-8-openjdk-amd64</jdkHome>
</configuration>
</toolchain>
<toolchain>
<type>jdk</type>
<provides>
- <id>java9</id>
- <version>9</version>
+ <id>java11</id>
+ <version>11</version>
<vendor>oracle</vendor>
</provides>
<configuration>
- <jdkHome>/usr/lib/jvm/java-9-oracle</jdkHome>
+ <jdkHome>/usr/local/lib/jvm/openjdk11</jdkHome>
</configuration>
</toolchain>
</toolchains>
\ No newline at end of file
diff --git a/.travis.yml b/.travis.yml
index 9fe270f..23cba96 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -11,20 +11,9 @@
# limitations under the License.
language: java
-# sudo enabled because sudo vms have more memory: https://docs.travis-ci.com/user/reference/overview/
-sudo: true
-# trusty required for oraclejdk9
-dist: trusty
-
+dist: xenial
jdk:
- - oraclejdk8
-
-addons:
- apt:
- packages:
- - oracle-java8-installer
- - oracle-java9-installer
- - maven
+ - openjdk8
env:
global:
@@ -36,11 +25,10 @@
before_cache:
- rm -rf $HOME/.m2/repository/org/apache/logging/log4j
-install:
- - mvn -t .travis-toolchains.xml install -DskipTests=true -Dmaven.javadoc.skip=true -B -V
+install: skip
script:
- - mvn -t .travis-toolchains.xml test -B -V
+ - mvn -t .travis-toolchains.xml install -B -V
after_success:
- mvn -t .travis-toolchains.xml -pl !log4j-bom jacoco:prepare-agent test jacoco:report coveralls:report -B -V
diff --git a/BUILDING.md b/BUILDING.md
index d973458..d77b14e 100644
--- a/BUILDING.md
+++ b/BUILDING.md
@@ -15,8 +15,8 @@
limitations under the License.
-->
# Building Log4j 2
-
-To build Log4j 2, you need a JDK implementation version 1.7 or greater, JDK
+
+To build Log4j 2, you need a JDK implementation version 1.8 or greater, JDK
version 9, and Apache Maven 3.x.
Log4j 2.x uses the Java 9 compiler in addition to
@@ -35,10 +35,6 @@
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
@@ -51,10 +47,6 @@
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:
diff --git a/Dockerfile b/Dockerfile
index 07b0b59..540d028 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -13,27 +13,17 @@
# 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
+FROM openjdk:8
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
+ && apt-get install -y openjdk-11-jdk-headless \
+ && ln -svT "/usr/lib/jvm/java-11-openjdk-$(dpkg --print-architecture)" /usr/local/openjdk-11
+
+VOLUME /src /root/.m2/repository
COPY . /src
RUN set -ex \
&& cd /src \
- && /opt/apache-maven-3.5.0/bin/mvn verify --global-toolchains toolchains-docker.xml
+ && ./mvnw --toolchains toolchains-docker.xml install
diff --git a/Jenkinsfile b/Jenkinsfile
deleted file mode 100644
index ac900ce..0000000
--- a/Jenkinsfile
+++ /dev/null
@@ -1,94 +0,0 @@
-#!groovy
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-pipeline {
- options {
- timeout time: 60, unit: 'MINUTES'
- }
- agent none
- stages {
- stage('Build') {
- failFast true
- parallel {
- stage('Ubuntu') {
- agent { label 'ubuntu&&!H20' }
- tools {
- // https://cwiki.apache.org/confluence/display/INFRA/JDK+Installation+Matrix
- jdk 'JDK 1.8 (latest)'
- // https://cwiki.apache.org/confluence/display/INFRA/Maven+Installation+Matrix
- maven 'Maven 3 (latest)'
- }
- steps {
- ansiColor('xterm') {
- sh 'mvn -t toolchains-jenkins-ubuntu.xml -Djenkins -V install'
- junit '*/target/*-reports/*.xml'
- stash includes: 'target/**', name: 'target'
- }
- }
- }
- stage('IBM JDK') {
- agent { label 'ubuntu&&!H20' }
- tools {
- jdk 'IBM 1.8 64-bit (on Ubuntu only)'
- maven 'Maven 3 (latest)'
- }
- steps {
- ansiColor('xterm') {
- sh 'mvn -t toolchains-jenkins-ibm.xml -Djenkins -V install'
- junit '*/target/*-reports/*.xml'
- }
- }
- }
- stage('Windows') {
- agent { label 'Windows' }
- tools {
- // https://cwiki.apache.org/confluence/display/INFRA/JDK+Installation+Matrix
- jdk 'JDK 1.8 (latest)'
- // https://cwiki.apache.org/confluence/display/INFRA/Maven+Installation+Matrix
- maven 'Maven 3 (latest)'
- }
- steps {
- bat 'if exist %userprofile%\\.embedmongo\\ rd /s /q %userprofile%\\.embedmongo'
- bat 'mvn -t toolchains-jenkins-win.xml -V -Dfile.encoding=UTF-8 install'
- junit '*/target/*-reports/*.xml'
- }
- }
- }
- }
- stage('Deploy') {
- when { branch 'master' }
- tools {
- // https://cwiki.apache.org/confluence/display/INFRA/JDK+Installation+Matrix
- jdk 'JDK 1.8 (latest)'
- // https://cwiki.apache.org/confluence/display/INFRA/Maven+Installation+Matrix
- maven 'Maven 3 (latest)'
- }
- steps {
- ansiColor('xterm') {
- unstash 'target'
- sh 'mvn -t toolchains-jenkins-ubuntu.xml -Djenkins -DskipTests -V deploy'
- }
- }
-// post {
-// failure {
-// emailext body: "See <${env.BUILD_URL}>", replyTo: 'dev@logging.apache.org', subject: "[Log4j] Jenkins build failure (#${env.BUILD_NUMBER})", to: 'notifications@logging.apache.org'
-// }
-// }
- }
- }
-}
diff --git a/NOTICE.txt b/NOTICE.txt
index bd95322..a241a12 100644
--- a/NOTICE.txt
+++ b/NOTICE.txt
@@ -1,5 +1,5 @@
Apache Log4j
-Copyright 1999-2017 Apache Software Foundation
+Copyright 1999-2019 Apache Software Foundation
This product includes software developed at
The Apache Software Foundation (http://www.apache.org/).
diff --git a/README.md b/README.md
index e5ade8a..79ae4f2 100644
--- a/README.md
+++ b/README.md
@@ -3,9 +3,10 @@
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)
+[![Jenkins build (3.x)](https://img.shields.io/jenkins/build?jobUrl=https%3A%2F%2Fci-builds.apache.org%2Fjob%2FLogging%2Fjob%2Flog4j%2Fjob%2Fmaster%2F&label=3.x&logo=cloudbees)](https://ci-builds.apache.org/job/Logging/job/log4j/job/master/)
+[![Jenkins build (2.x)](https://img.shields.io/jenkins/build?jobUrl=https%3A%2F%2Fci-builds.apache.org%2Fjob%2FLogging%2Fjob%2Flog4j%2Fjob%2Frelease-2.x%2F&label=2.x&logo=cloudbees)](https://ci-builds.apache.org/job/Logging/job/log4j/job/release-2.x/)
+[![GitHub build (3.x)](https://github.com/apache/logging-log4j2/workflows/Maven/badge.svg)](https://github.com/apache/logging-log4j2/actions?query=workflow%3AMaven+branch%3Amaster)
+[![Maven Central](https://img.shields.io/maven-central/v/org.apache.logging.log4j/log4j-api.svg?logo=java)](http://mvnrepository.com/artifact/org.apache.logging.log4j/log4j-api)
## Pull Requests on Github
@@ -13,7 +14,7 @@
By sending a pull request you grant the Apache Software Foundation sufficient rights to use and release the submitted
work under the Apache license. You grant the same rights (copyright license, patent license, etc.) to the
Apache Software Foundation as if you have signed a Contributor License Agreement. For contributions that are
-judged to be non-trivial, you will be asked to actually signing a Contributor License Agreement.
+judged to be non-trivial, you will be asked to actually sign a [Contributor License Agreement](https://www.apache.org/licenses/icla.pdf).
## Usage
diff --git a/log4j-1.2-api/pom.xml b/log4j-1.2-api/pom.xml
index c421276..e0cb127 100644
--- a/log4j-1.2-api/pom.xml
+++ b/log4j-1.2-api/pom.xml
@@ -24,7 +24,7 @@
<relativePath>../</relativePath>
</parent>
<artifactId>log4j-1.2-api</artifactId>
- <packaging>bundle</packaging>
+ <packaging>jar</packaging>
<name>Apache Log4j 1.x Compatibility API</name>
<description>The Apache Log4j 1.x Compatibility API</description>
<properties>
@@ -63,6 +63,7 @@
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
+ <optional>true</optional>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
@@ -159,6 +160,7 @@
project -->
<detectOfflineLinks>false</detectOfflineLinks>
<linksource>true</linksource>
+ <source>8</source>
</configuration>
<reportSets>
<reportSet>
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 a4c5231..1353dae 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
@@ -56,7 +56,7 @@
}
@Override
- public void addFilter(Filter newFilter) {
+ public void addFilter(final Filter newFilter) {
if(headFilter == null) {
headFilter = tailFilter = newFilter;
} else {
@@ -104,7 +104,7 @@
return threshold;
}
- public boolean isAsSevereAsThreshold(Priority priority) {
+ public boolean isAsSevereAsThreshold(final Priority priority) {
return ((threshold == null) || priority.isGreaterOrEqual(threshold));
}
@@ -113,7 +113,8 @@
* @param event The LoggingEvent.
*/
@Override
- public void doAppend(LoggingEvent event) {
+ public void doAppend(final LoggingEvent event) {
+ append(event);
}
/**
@@ -122,54 +123,54 @@
* @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/Category.java b/log4j-1.2-api/src/main/java/org/apache/log4j/Category.java
index 873aa0e..b0a0912 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
@@ -24,10 +24,14 @@
import java.util.concurrent.ConcurrentMap;
import org.apache.log4j.helpers.NullEnumeration;
+import org.apache.log4j.legacy.core.CategoryUtil;
+import org.apache.log4j.or.ObjectRenderer;
+import org.apache.log4j.or.RendererSupport;
import org.apache.log4j.spi.LoggerFactory;
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.MapMessage;
+import org.apache.logging.log4j.spi.ExtendedLogger;
+import org.apache.logging.log4j.spi.LoggerContext;
import org.apache.logging.log4j.message.LocalizedMessage;
import org.apache.logging.log4j.message.Message;
import org.apache.logging.log4j.message.ObjectMessage;
@@ -40,19 +44,34 @@
*/
public class Category {
- private static PrivateAdapter adapter = new PrivateAdapter();
+ private static final PrivateAdapter adapter = new PrivateAdapter();
private static final Map<LoggerContext, ConcurrentMap<String, Logger>> CONTEXT_MAP =
new WeakHashMap<>();
private static final String FQCN = Category.class.getName();
+ private static final boolean isCoreAvailable;
+
+ private final Map<Class<?>, ObjectRenderer> rendererMap;
+
+ static {
+ boolean available;
+
+ try {
+ available = Class.forName("org.apache.logging.log4j.core.Logger") != null;
+ } catch (Exception ex) {
+ available = false;
+ }
+ isCoreAvailable = available;
+ }
+
/**
* Resource bundle for localized messages.
*/
protected ResourceBundle bundle = null;
- private final org.apache.logging.log4j.core.Logger logger;
+ private final org.apache.logging.log4j.Logger logger;
/**
* Constructor used by Logger to specify a LoggerContext.
@@ -61,6 +80,7 @@
*/
protected Category(final LoggerContext context, final String name) {
this.logger = context.getLogger(name);
+ rendererMap = ((RendererSupport) LogManager.getLoggerRepository()).getRendererMap();
}
/**
@@ -71,8 +91,9 @@
this(PrivateManager.getContext(), name);
}
- private Category(final org.apache.logging.log4j.core.Logger logger) {
+ private Category(final org.apache.logging.log4j.Logger logger) {
this.logger = logger;
+ rendererMap = ((RendererSupport) LogManager.getLoggerRepository()).getRendererMap();
}
public static Category getInstance(final String name) {
@@ -117,16 +138,20 @@
return logger.getName();
}
- org.apache.logging.log4j.core.Logger getLogger() {
+ org.apache.logging.log4j.Logger getLogger() {
return logger;
}
public final Category getParent() {
- final org.apache.logging.log4j.core.Logger parent = logger.getParent();
- if (parent == null) {
+ if (!isCoreAvailable) {
return null;
}
- final ConcurrentMap<String, Logger> loggers = getLoggersMap(logger.getContext());
+ org.apache.logging.log4j.Logger parent = CategoryUtil.getParent(logger);
+ LoggerContext loggerContext = CategoryUtil.getLoggerContext(logger);
+ if (parent == null || loggerContext == null) {
+ return null;
+ }
+ final ConcurrentMap<String, Logger> loggers = getLoggersMap(loggerContext);
final Logger l = loggers.get(parent.getName());
return l == null ? new Category(parent) : l;
}
@@ -182,8 +207,6 @@
return Level.ERROR;
case FATAL:
return Level.FATAL;
- case OFF:
- return Level.OFF;
default:
// TODO Should this be an IllegalStateException?
return Level.OFF;
@@ -199,7 +222,7 @@
}
public void setLevel(final Level level) {
- logger.setLevel(org.apache.logging.log4j.Level.toLevel(level.levelStr));
+ setLevel(level.levelStr);
}
public final Level getPriority() {
@@ -207,7 +230,13 @@
}
public void setPriority(final Priority priority) {
- logger.setLevel(org.apache.logging.log4j.Level.toLevel(priority.levelStr));
+ setLevel(priority.levelStr);
+ }
+
+ private void setLevel(final String levelStr) {
+ if (isCoreAvailable) {
+ CategoryUtil.setLevel(logger, org.apache.logging.log4j.Level.toLevel(levelStr));
+ }
}
public void debug(final Object message) {
@@ -350,11 +379,19 @@
public static void shutdown() {
}
-
public void forcedLog(final String fqcn, final Priority level, final Object message, final Throwable t) {
final org.apache.logging.log4j.Level lvl = org.apache.logging.log4j.Level.toLevel(level.toString());
- final Message msg = message instanceof Message ? (Message) message : new ObjectMessage(message);
- logger.logMessage(fqcn, lvl, null, msg, t);
+ if (logger instanceof ExtendedLogger) {
+ @SuppressWarnings("unchecked")
+ Message msg = message instanceof Message ? (Message) message : message instanceof Map ?
+ new MapMessage((Map) message) : new ObjectMessage(message);
+ ((ExtendedLogger) logger).logMessage(fqcn, lvl, null, msg, t);
+ } else {
+ ObjectRenderer renderer = get(message.getClass());
+ final Message msg = message instanceof Message ? (Message) message : renderer != null ?
+ new RenderedMessage(renderer, message) : new ObjectMessage(message);
+ logger.log(lvl, msg, t);
+ }
}
public boolean exists(final String name) {
@@ -362,11 +399,13 @@
}
public boolean getAdditivity() {
- return logger.isAdditive();
+ return isCoreAvailable ? CategoryUtil.isAdditive(logger) : false;
}
public void setAdditivity(final boolean additivity) {
- logger.setAdditive(additivity);
+ if (isCoreAvailable) {
+ CategoryUtil.setAdditivity(logger, additivity);
+ }
}
public void setResourceBundle(final ResourceBundle bundle) {
@@ -378,19 +417,32 @@
return bundle;
}
String name = logger.getName();
- final ConcurrentMap<String, Logger> 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 (isCoreAvailable) {
+ LoggerContext ctx = CategoryUtil.getLoggerContext(logger);
+ if (ctx != null) {
+ final ConcurrentMap<String, Logger> loggers = 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;
}
+ private static String getSubName(final String name) {
+ if (Strings.isEmpty(name)) {
+ return null;
+ }
+ final int i = name.lastIndexOf('.');
+ return i > 0 ? name.substring(0, i) : Strings.EMPTY;
+ }
+
/**
If <code>assertion</code> parameter is {@code false}, then
logs <code>msg</code> as an {@link #error(Object) error} statement.
@@ -427,14 +479,16 @@
public void log(final Priority priority, final Object message, final Throwable t) {
if (isEnabledFor(priority)) {
- final Message msg = new ObjectMessage(message);
+ @SuppressWarnings("unchecked")
+ final Message msg = message instanceof Map ? new MapMessage((Map) message) : new ObjectMessage(message);
forcedLog(FQCN, priority, msg, t);
}
}
public void log(final Priority priority, final Object message) {
if (isEnabledFor(priority)) {
- final Message msg = new ObjectMessage(message);
+ @SuppressWarnings("unchecked")
+ final Message msg = message instanceof Map ? new MapMessage((Map) message) : new ObjectMessage(message);
forcedLog(FQCN, priority, msg, null);
}
}
@@ -448,8 +502,14 @@
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);
+ if (logger.isEnabled(level)) {
+ @SuppressWarnings("unchecked")
+ Message msg = message instanceof Map ? new MapMessage((Map) message) : new ObjectMessage(message);
+ if (logger instanceof ExtendedLogger) {
+ ((ExtendedLogger) logger).logMessage(fqcn, level, null, msg, throwable);
+ } else {
+ logger.log(level, msg, throwable);
+ }
}
}
@@ -457,7 +517,7 @@
@Override
protected Logger newLogger(final String name, final org.apache.logging.log4j.spi.LoggerContext context) {
- return new Logger((LoggerContext) context, name);
+ return new Logger(context, name);
}
@Override
@@ -473,7 +533,7 @@
private static final String FQCN = Category.class.getName();
public static LoggerContext getContext() {
- return (LoggerContext) getContext(FQCN, false);
+ return getContext(FQCN, false);
}
public static org.apache.logging.log4j.Logger getLogger(final String name) {
@@ -482,7 +542,38 @@
}
private boolean isEnabledFor(final org.apache.logging.log4j.Level level) {
- return logger.isEnabled(level, null, null);
+ return logger.isEnabled(level);
+ }
+
+ private ObjectRenderer get(Class clazz) {
+ ObjectRenderer renderer = null;
+ for(Class c = clazz; c != null; c = c.getSuperclass()) {
+ renderer = rendererMap.get(c);
+ if (renderer != null) {
+ return renderer;
+ }
+ renderer = searchInterfaces(c);
+ if (renderer != null) {
+ return renderer;
+ }
+ }
+ return null;
+ }
+
+ ObjectRenderer searchInterfaces(Class c) {
+ ObjectRenderer renderer = rendererMap.get(c);
+ if(renderer != null) {
+ return renderer;
+ } else {
+ Class[] ia = c.getInterfaces();
+ for (Class clazz : ia) {
+ renderer = searchInterfaces(clazz);
+ if (renderer != null) {
+ return renderer;
+ }
+ }
+ }
+ return null;
}
}
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 0000000..605fac7
--- /dev/null
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/ConsoleAppender.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;
+
+import org.apache.log4j.spi.LoggingEvent;
+
+/**
+ * Console-appender.
+ */
+public class ConsoleAppender extends AppenderSkeleton
+{
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void close()
+ {
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean requiresLayout()
+ {
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void append(final LoggingEvent theEvent)
+ {
+ }
+
+}
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 2ef4b1c..dbd2291 100644
--- a/log4j-1.2-api/src/main/java/org/apache/log4j/Layout.java
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/Layout.java
@@ -24,6 +24,8 @@
*/
public abstract class Layout {
+ public final static String LINE_SEP = Strings.LINE_SEPARATOR;
+
/** Note that the line.separator property can be looked up even by applets. */
public static final int LINE_SEP_LEN = Strings.LINE_SEPARATOR.length();
diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/LogManager.java b/log4j-1.2-api/src/main/java/org/apache/log4j/LogManager.java
index 751cb86..d595988 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
@@ -17,13 +17,18 @@
package org.apache.log4j;
import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
import org.apache.log4j.helpers.NullEnumeration;
+import org.apache.log4j.legacy.core.ContextUtil;
+import org.apache.log4j.or.ObjectRenderer;
+import org.apache.log4j.or.RendererSupport;
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.spi.LoggerContext;
import org.apache.logging.log4j.util.Strings;
/**
@@ -63,6 +68,20 @@
private static final LoggerRepository REPOSITORY = new Repository();
+ private static final boolean isLog4jCore;
+
+ static {
+ boolean core = false;
+ try {
+ if (Class.forName("org.apache.logging.log4j.core.LoggerContext") != null) {
+ core = true;
+ }
+ } catch (Exception ex) {
+ // Ignore the exception;
+ }
+ isLog4jCore = core;
+ }
+
private LogManager() {
}
@@ -96,8 +115,10 @@
}
static void reconfigure() {
- final LoggerContext ctx = PrivateManager.getContext();
- ctx.reconfigure();
+ if (isLog4jCore) {
+ final LoggerContext ctx = PrivateManager.getContext();
+ ContextUtil.reconfigure(ctx);
+ }
}
/**
@@ -129,7 +150,15 @@
/**
* The Repository.
*/
- private static class Repository implements LoggerRepository {
+ private static class Repository implements LoggerRepository, RendererSupport {
+
+ private final Map<Class<?>, ObjectRenderer> rendererMap = new HashMap<>();
+
+ @Override
+ public Map<Class<?>, ObjectRenderer> getRendererMap() {
+ return rendererMap;
+ }
+
@Override
public void addHierarchyEventListener(final HierarchyEventListener listener) {
@@ -212,7 +241,7 @@
private static final String FQCN = LogManager.class.getName();
public static LoggerContext getContext() {
- return (LoggerContext) getContext(FQCN, false);
+ return getContext(FQCN, false);
}
public static org.apache.logging.log4j.Logger getLogger(final String name) {
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 fb7277b..fe1f26a 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
@@ -18,7 +18,7 @@
import org.apache.log4j.spi.LoggerFactory;
-import org.apache.logging.log4j.core.LoggerContext;
+import org.apache.logging.log4j.spi.LoggerContext;
/**
*
@@ -56,7 +56,7 @@
private static final String FQCN = Logger.class.getName();
public static LoggerContext getContext() {
- return (LoggerContext) getContext(FQCN, false);
+ return getContext(FQCN, false);
}
public static org.apache.logging.log4j.Logger getLogger(final String name) {
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 ee7631a..ef99df9 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
@@ -29,7 +29,7 @@
public final class MDC {
- private static ThreadLocal<Map<String, Object>> localMap =
+ private static final ThreadLocal<Map<String, Object>> localMap =
new InheritableThreadLocal<Map<String, Object>>() {
@Override
protected Map<String, Object> initialValue() {
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 0000000..422a565
--- /dev/null
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/RenderedMessage.java
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.log4j;
+
+import org.apache.log4j.or.ObjectRenderer;
+import org.apache.logging.log4j.message.Message;
+
+/**
+ * Implements object rendering for Log4j 1.x compatibility.
+ */
+public class RenderedMessage implements Message {
+
+ private final ObjectRenderer renderer;
+ private final Object object;
+ private String rendered = null;
+
+ public RenderedMessage(ObjectRenderer renderer, Object object) {
+ this.renderer = renderer;
+ this.object = object;
+ }
+
+
+ @Override
+ public String getFormattedMessage() {
+ if (rendered == null) {
+ rendered = renderer.doRender(object);
+ }
+
+ return rendered;
+ }
+
+ @Override
+ public String getFormat() {
+ return getFormattedMessage();
+ }
+
+ @Override
+ public Object[] getParameters() {
+ return null;
+ }
+
+ @Override
+ public Throwable getThrowable() {
+ return null;
+ }
+}
diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/SimpleLayout.java b/log4j-1.2-api/src/main/java/org/apache/log4j/SimpleLayout.java
new file mode 100644
index 0000000..c77b9be
--- /dev/null
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/SimpleLayout.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;
+
+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/WriterAppender.java b/log4j-1.2-api/src/main/java/org/apache/log4j/WriterAppender.java
new file mode 100644
index 0000000..546ed44
--- /dev/null
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/WriterAppender.java
@@ -0,0 +1,391 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.LoggingEvent;
+import org.apache.logging.log4j.status.StatusLogger;
+
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+
+
+/**
+ * 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 <code>immediateFlush</code> is set to
+ * <code>false</code>, 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.
+ *
+ * <p>The <code>immediateFlush</code> variable is set to
+ * <code>true</code> by default.
+ */
+ protected boolean immediateFlush = true;
+
+ /**
+ * The encoding to use when writing. <p>The
+ * <code>encoding</code> variable is set to <code>null</code> 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 <code>os</code>
+ * as its {@link OutputStream}.
+ * @param layout The Layout.
+ * @param os The OutputStream.
+ */
+ public WriterAppender(Layout layout, OutputStream os) {
+ this(layout, new OutputStreamWriter(os));
+ }
+
+ /**
+ * Instantiate a WriterAppender and set the output destination to <code>writer</code>.
+ *
+ * <p>The <code>writer</code> must have been previously opened by
+ * the user.
+ *
+ * @param layout The Layout.
+ * @param writer The Writer.
+ */
+ public WriterAppender(Layout layout, Writer writer) {
+ this.layout = layout;
+ this.setWriter(writer);
+ }
+
+ /**
+ * Returns value of the <b>ImmediateFlush</b> option.
+ * @return the value of the immediate flush setting.
+ */
+ public boolean getImmediateFlush() {
+ return immediateFlush;
+ }
+
+ /**
+ * If the <b>ImmediateFlush</b> option is set to
+ * <code>true</code>, the appender will flush at the end of each
+ * write. This is the default behavior. If the option is set to
+ * <code>false</code>, then the underlying stream can defer writing
+ * to physical medium to a later time.
+ *
+ * <p>Avoiding the flush operation at the end of each append results in
+ * a performance gain of 10 to 20 percent. However, there is safety
+ * tradeoff involved in skipping flushing. Indeed, when flushing is
+ * skipped, then it is likely that the last few log events will not
+ * be recorded on disk when the application exits. This is a high
+ * price to pay even for a 20% performance gain.
+ *
+ * @param value the value to set the immediate flush setting to.
+ */
+ public void setImmediateFlush(boolean value) {
+ immediateFlush = value;
+ }
+
+ /**
+ * Does nothing.
+ */
+ public void activateOptions() {
+ }
+
+
+ /**
+ * This method is called by the {@link AppenderSkeleton#doAppend}
+ * method.
+ *
+ * <p>If the output stream exists and is writable then write a log
+ * statement to the output stream. Otherwise, write a single warning
+ * message to <code>System.err</code>.
+ *
+ * <p>The format of the output will depend on this appender's
+ * layout.
+ */
+ public void append(LoggingEvent event) {
+
+ // Reminder: the nesting of calls is:
+ //
+ // doAppend()
+ // - check threshold
+ // - filter
+ // - append();
+ // - checkEntryConditions();
+ // - subAppend();
+
+ if (!checkEntryConditions()) {
+ return;
+ }
+ subAppend(event);
+ }
+
+ /**
+ * This method determines if there is a sense in attempting to append.
+ *
+ * <p>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 <code>false</code> 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.
+ *
+ * <p>Closed appenders cannot be reused.
+ *
+ * @see #setWriter
+ * @since 0.8.4
+ */
+ 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
+ * <code>encoding</code> property. If the encoding value is
+ * specified incorrectly the writer will be opened using the default
+ * system encoding (an error message will be printed to the LOGGER.
+ * @param os The OutputStream.
+ * @return The OutputStreamWriter.
+ */
+ protected OutputStreamWriter createWriter(OutputStream os) {
+ OutputStreamWriter retval = null;
+
+ String enc = getEncoding();
+ if (enc != null) {
+ try {
+ retval = new OutputStreamWriter(os, enc);
+ } catch (IOException e) {
+ if (e instanceof InterruptedIOException) {
+ Thread.currentThread().interrupt();
+ }
+ LOGGER.warn("Error initializing output writer.");
+ LOGGER.warn("Unsupported encoding?");
+ }
+ }
+ if (retval == null) {
+ retval = new OutputStreamWriter(os);
+ }
+ return retval;
+ }
+
+ public String getEncoding() {
+ return encoding;
+ }
+
+ public void setEncoding(String value) {
+ encoding = value;
+ }
+
+
+ /**
+ * Set the {@link ErrorHandler} for this WriterAppender and also the
+ * underlying {@link QuietWriter} if any.
+ */
+ public synchronized void setErrorHandler(ErrorHandler eh) {
+ if (eh == null) {
+ LOGGER.warn("You have tried to set a null error-handler.");
+ } else {
+ this.errorHandler = eh;
+ if (this.qw != null) {
+ this.qw.setErrorHandler(eh);
+ }
+ }
+ }
+
+ /**
+ * <p>Sets the Writer where the log output will go. The
+ * specified Writer must be opened by the user and be
+ * writable.
+ *
+ * <p>The <code>java.io.Writer</code> will be closed when the
+ * appender instance is closed.
+ *
+ *
+ * <p><b>WARNING:</b> Logging to an unopened Writer will fail.
+ * <p>
+ *
+ * @param writer An already opened Writer.
+ */
+ public synchronized void setWriter(Writer writer) {
+ reset();
+ this.qw = new QuietWriter(writer, errorHandler);
+ //this.tp = new TracerPrintWriter(qw);
+ writeHeader();
+ }
+
+
+ /**
+ * Actual writing occurs here.
+ *
+ * <p>Most subclasses of <code>WriterAppender</code> will need to
+ * override this method.
+ * @param event The event to log.
+ *
+ * @since 0.9.0
+ */
+ protected void subAppend(LoggingEvent event) {
+ this.qw.write(this.layout.format(event));
+
+ if (layout.ignoresThrowable()) {
+ String[] s = event.getThrowableStrRep();
+ if (s != null) {
+ int len = s.length;
+ for (int i = 0; i < len; i++) {
+ this.qw.write(s[i]);
+ this.qw.write(Layout.LINE_SEP);
+ }
+ }
+ }
+
+ if (shouldFlush(event)) {
+ this.qw.flush();
+ }
+ }
+
+
+ /**
+ * The WriterAppender requires a layout. Hence, this method returns
+ * <code>true</code>.
+ */
+ public boolean requiresLayout() {
+ return true;
+ }
+
+ /**
+ * Clear internal references to the writer and other variables.
+ * <p>
+ * Subclasses can override this method for an alternate closing
+ * behavior.
+ */
+ protected void reset() {
+ closeWriter();
+ this.qw = null;
+ //this.tp = null;
+ }
+
+
+ /**
+ * Write a footer as produced by the embedded layout's {@link
+ * Layout#getFooter} method.
+ */
+ protected void writeFooter() {
+ if (layout != null) {
+ String f = layout.getFooter();
+ if (f != null && this.qw != null) {
+ this.qw.write(f);
+ this.qw.flush();
+ }
+ }
+ }
+
+ /**
+ * Write a header as produced by the embedded layout's {@link
+ * Layout#getHeader} method.
+ */
+ protected void writeHeader() {
+ if (layout != null) {
+ String h = layout.getHeader();
+ if (h != null && this.qw != null) {
+ this.qw.write(h);
+ }
+ }
+ }
+
+ /**
+ * Determines whether the writer should be flushed after
+ * this event is written.
+ * @param event The event to log.
+ * @return true if the writer should be flushed.
+ *
+ * @since 1.2.16
+ */
+ protected boolean shouldFlush(final LoggingEvent event) {
+ return immediateFlush;
+ }
+}
diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/bridge/AppenderAdapter.java b/log4j-1.2-api/src/main/java/org/apache/log4j/bridge/AppenderAdapter.java
new file mode 100644
index 0000000..1ae0369
--- /dev/null
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/bridge/AppenderAdapter.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.bridge;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+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.core.filter.CompositeFilter;
+
+/**
+ * Binds a Log4j 1.x Appender to Log4j 2.
+ */
+public class AppenderAdapter {
+
+ private final Appender appender;
+ private final Adapter adapter;
+
+ /**
+ * Constructor.
+ * @param appender The Appender to wrap.
+ */
+ public AppenderAdapter(Appender appender) {
+ this.appender = appender;
+ org.apache.logging.log4j.core.Filter appenderFilter = null;
+ if (appender.getFilter() != null) {
+ if (appender.getFilter().getNext() != null) {
+ org.apache.log4j.spi.Filter filter = appender.getFilter();
+ List<org.apache.logging.log4j.core.Filter> filters = new ArrayList<>();
+ while (filter != null) {
+ filters.add(new FilterAdapter(filter));
+ filter = filter.getNext();
+ }
+ appenderFilter = CompositeFilter.createFilters(filters.toArray(new Filter[0]));
+ } else {
+ appenderFilter = new FilterAdapter(appender.getFilter());
+ }
+ }
+ this.adapter = new Adapter(appender.getName(), appenderFilter, null, true, null);
+ }
+
+ public Adapter getAdapter() {
+ return adapter;
+ }
+
+ public class Adapter extends AbstractAppender {
+
+ protected Adapter(final String name, final Filter filter, final Layout<? extends Serializable> layout,
+ final boolean ignoreExceptions, final Property[] properties) {
+ super(name, filter, layout, ignoreExceptions, properties);
+ }
+
+ @Override
+ public void append(LogEvent event) {
+ appender.doAppend(new LogEventAdapter(event));
+ }
+
+ @Override
+ public void stop() {
+ appender.close();
+ }
+
+ public Appender getAppender() {
+ return appender;
+ }
+ }
+}
diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/bridge/AppenderWrapper.java b/log4j-1.2-api/src/main/java/org/apache/log4j/bridge/AppenderWrapper.java
new file mode 100644
index 0000000..eb39fe7
--- /dev/null
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/bridge/AppenderWrapper.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.bridge;
+
+import org.apache.log4j.Appender;
+import org.apache.log4j.Layout;
+import org.apache.log4j.helpers.AppenderAttachableImpl;
+import org.apache.log4j.spi.AppenderAttachable;
+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;
+
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+
+/**
+ * Holds 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 extends AppenderAttachableImpl implements Appender {
+
+ private static final Logger LOGGER = StatusLogger.getLogger();
+ private final org.apache.logging.log4j.core.Appender appender;
+
+ public AppenderWrapper(org.apache.logging.log4j.core.Appender appender) {
+ this.appender = appender;
+ }
+
+ public org.apache.logging.log4j.core.Appender getAppender() {
+ return appender;
+ }
+
+ @Override
+ public void addFilter(Filter newFilter) {
+ if (appender instanceof AbstractFilterable) {
+ if (newFilter instanceof FilterWrapper) {
+ ((AbstractFilterable) appender).addFilter(((FilterWrapper) newFilter).getFilter());
+ } else {
+ ((AbstractFilterable) appender).addFilter(new FilterAdapter(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() {
+
+ }
+
+ @Override
+ public void close() {
+ // Not supported with Log4j 2.
+ }
+
+ @Override
+ public void doAppend(LoggingEvent event) {
+ if (event instanceof LogEventAdapter) {
+ appender.append(((LogEventAdapter) event).getEvent());
+ }
+ }
+
+ @Override
+ public String getName() {
+ return appender.getName();
+ }
+
+ @Override
+ public void setErrorHandler(ErrorHandler errorHandler) {
+ appender.setHandler(new ErrorHandlerAdapter(errorHandler));
+ }
+
+ @Override
+ public ErrorHandler getErrorHandler() {
+ return ((ErrorHandlerAdapter)appender.getHandler()).getHandler();
+ }
+
+ @Override
+ public void setLayout(Layout layout) {
+ // Log4j 2 doesn't support this.
+ }
+
+ @Override
+ public Layout getLayout() {
+ return new LayoutWrapper(appender.getLayout());
+ }
+
+ @Override
+ public void setName(String name) {
+ // Log4j 2 doesn't support this.
+ }
+
+ @Override
+ public boolean requiresLayout() {
+ return false;
+ }
+}
diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/bridge/ErrorHandlerAdapter.java b/log4j-1.2-api/src/main/java/org/apache/log4j/bridge/ErrorHandlerAdapter.java
new file mode 100644
index 0000000..1f166a2
--- /dev/null
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/bridge/ErrorHandlerAdapter.java
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.log4j.bridge;
+
+import org.apache.log4j.spi.ErrorHandler;
+import org.apache.logging.log4j.core.LogEvent;
+
+/**
+ * Makes a Log4j 1 ErrorHandler usable by a Log4j 2 Appender.
+ */
+public class ErrorHandlerAdapter implements org.apache.logging.log4j.core.ErrorHandler {
+
+ private final ErrorHandler errorHandler;
+
+ public ErrorHandlerAdapter(ErrorHandler errorHandler) {
+ this.errorHandler = errorHandler;
+ }
+
+ public ErrorHandler getHandler() {
+ return errorHandler;
+ }
+
+ @Override
+ public void error(String msg) {
+ errorHandler.error(msg);
+ }
+
+ @Override
+ public void error(String msg, Throwable t) {
+ if (t instanceof Exception) {
+ errorHandler.error(msg, (Exception) t, 0);
+ } else {
+ errorHandler.error(msg);
+ }
+ }
+
+ @Override
+ public void error(String msg, LogEvent event, Throwable t) {
+ if (t == null || t instanceof Exception) {
+ errorHandler.error(msg, (Exception) t, 0, new LogEventAdapter(event));
+ } else {
+ errorHandler.error(msg);
+ }
+ }
+}
diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/bridge/FilterAdapter.java b/log4j-1.2-api/src/main/java/org/apache/log4j/bridge/FilterAdapter.java
new file mode 100644
index 0000000..2dff272
--- /dev/null
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/bridge/FilterAdapter.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.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;
+
+/**
+ * Binds a Log4j 1.x Filter with Log4j 2.
+ */
+public class FilterAdapter extends AbstractFilter {
+
+ private final Filter filter;
+
+ public FilterAdapter(Filter filter) {
+ this.filter = filter;
+ }
+
+ @Override
+ public void start() {
+ filter.activateOptions();
+ }
+
+ @Override
+ public Result filter(LogEvent event) {
+ LoggingEvent loggingEvent = new LogEventAdapter(event);
+ Filter next = filter;
+ while (next != null) {
+ switch (filter.decide(loggingEvent)) {
+ case Filter.ACCEPT:
+ return Result.ACCEPT;
+ case Filter.DENY:
+ return Result.DENY;
+ default:
+ }
+ next = filter.getNext();
+ }
+ return Result.NEUTRAL;
+ }
+}
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 0000000..b2855cd
--- /dev/null
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/bridge/FilterWrapper.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.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;
+
+ public FilterWrapper(org.apache.logging.log4j.core.Filter filter) {
+ this.filter = filter;
+ }
+
+ public org.apache.logging.log4j.core.Filter getFilter() {
+ return filter;
+ }
+
+ /**
+ * This method is never called.
+ * @param event The LoggingEvent to decide upon.
+ * @return 0
+ */
+ @Override
+ public int decide(LoggingEvent event) {
+ return 0;
+ }
+}
diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/bridge/LayoutAdapter.java b/log4j-1.2-api/src/main/java/org/apache/log4j/bridge/LayoutAdapter.java
new file mode 100644
index 0000000..8711f2d
--- /dev/null
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/bridge/LayoutAdapter.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.bridge;
+
+import org.apache.log4j.Layout;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.layout.ByteBufferDestination;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Class Description goes here.
+ */
+public class LayoutAdapter implements org.apache.logging.log4j.core.Layout<String> {
+ private final Layout layout;
+
+ public LayoutAdapter(Layout layout) {
+ this.layout = layout;
+ }
+
+
+ @Override
+ public byte[] getFooter() {
+ return layout.getFooter() == null ? null : layout.getFooter().getBytes();
+ }
+
+ @Override
+ public byte[] getHeader() {
+ return layout.getHeader() == null ? null : layout.getHeader().getBytes();
+ }
+
+ @Override
+ public byte[] toByteArray(LogEvent event) {
+ String result = layout.format(new LogEventAdapter(event));
+ return result == null ? null : result.getBytes();
+ }
+
+ @Override
+ public String toSerializable(LogEvent event) {
+ return layout.format(new LogEventAdapter(event));
+ }
+
+ @Override
+ public String getContentType() {
+ return layout.getContentType();
+ }
+
+ @Override
+ public Map<String, String> getContentFormat() {
+ return new HashMap<>();
+ }
+
+ @Override
+ public void encode(LogEvent event, ByteBufferDestination destination) {
+ final byte[] data = toByteArray(event);
+ destination.writeBytes(data, 0, data.length);
+ }
+}
diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/bridge/LayoutWrapper.java b/log4j-1.2-api/src/main/java/org/apache/log4j/bridge/LayoutWrapper.java
new file mode 100644
index 0000000..fc1e72f
--- /dev/null
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/bridge/LayoutWrapper.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.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;
+
+ public LayoutWrapper(org.apache.logging.log4j.core.Layout<?> layout) {
+ this.layout = layout;
+ }
+
+ @Override
+ public String format(LoggingEvent event) {
+ return layout.toSerializable(((LogEventAdapter)event).getEvent()).toString();
+ }
+
+ @Override
+ public boolean ignoresThrowable() {
+ return false;
+ }
+
+ public org.apache.logging.log4j.core.Layout<?> getLayout() {
+ return this.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 0000000..894045e
--- /dev/null
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/bridge/LogEventAdapter.java
@@ -0,0 +1,227 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.log4j.bridge;
+
+import org.apache.log4j.Category;
+import org.apache.log4j.Level;
+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.spi.StandardLevel;
+import org.apache.logging.log4j.status.StatusLogger;
+
+import java.lang.reflect.Method;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Converts a Log4j 2 LogEvent into the components needed by a Log4j 1.x LoggingEvent.
+ * This class requires Log4j 2.
+ */
+public class LogEventAdapter extends LoggingEvent {
+
+ private static final long JVM_START_TIME = initStartTime();
+
+ private final LogEvent event;
+
+ public LogEventAdapter(LogEvent event) {
+ this.event = event;
+ }
+
+ /**
+ * Returns the time when the application started, in milliseconds
+ * elapsed since 01.01.1970.
+ * @return the time when the JVM started.
+ */
+ public static long getStartTime() {
+ return JVM_START_TIME;
+ }
+
+ /**
+ * Returns the result of {@code ManagementFactory.getRuntimeMXBean().getStartTime()},
+ * or the current system time if JMX is not available.
+ */
+ private static long initStartTime() {
+ // We'd like to call ManagementFactory.getRuntimeMXBean().getStartTime(),
+ // but Google App Engine throws a java.lang.NoClassDefFoundError
+ // "java.lang.management.ManagementFactory is a restricted class".
+ // The reflection is necessary because without it, Google App Engine
+ // will refuse to initialize this class.
+ try {
+ final Class<?> factoryClass = Loader.loadSystemClass("java.lang.management.ManagementFactory");
+ final Method getRuntimeMXBean = factoryClass.getMethod("getRuntimeMXBean");
+ final Object runtimeMXBean = getRuntimeMXBean.invoke(null);
+
+ final Class<?> runtimeMXBeanClass = Loader.loadSystemClass("java.lang.management.RuntimeMXBean");
+ final Method getStartTime = runtimeMXBeanClass.getMethod("getStartTime");
+ return (Long) getStartTime.invoke(runtimeMXBean);
+ } catch (final Throwable t) {
+ StatusLogger.getLogger().error("Unable to call ManagementFactory.getRuntimeMXBean().getStartTime(), "
+ + "using system time for OnStartupTriggeringPolicy", t);
+ // We have little option but to declare "now" as the beginning of time.
+ return System.currentTimeMillis();
+ }
+ }
+
+ public LogEvent getEvent() {
+ return this.event;
+ }
+
+ /**
+ * Set the location information for this logging event. The collected
+ * information is cached for future use.
+ */
+ @Override
+ public LocationInfo getLocationInformation() {
+ return new LocationInfo(event.getSource());
+ }
+
+ /**
+ * Return the level of this event. Use this form instead of directly
+ * accessing the <code>level</code> field.
+ */
+ @Override
+ public Level getLevel() {
+ switch (StandardLevel.getStandardLevel(event.getLevel().intLevel())) {
+ case TRACE:
+ return Level.TRACE;
+ case DEBUG:
+ return Level.DEBUG;
+ case INFO:
+ return Level.INFO;
+ case WARN:
+ return Level.WARN;
+ case FATAL:
+ return Level.FATAL;
+ case OFF:
+ return Level.OFF;
+ case ALL:
+ return Level.ALL;
+ default:
+ return Level.ERROR;
+ }
+ }
+
+ /**
+ * Return the name of the logger. Use this form instead of directly
+ * accessing the <code>categoryName</code> field.
+ */
+ @Override
+ public String getLoggerName() {
+ return event.getLoggerName();
+ }
+
+ /**
+ * 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 the context corresponding to the <code>key</code> parameter.
+ */
+ @Override
+ public Object getMDC(String key) {
+ if (event.getContextData() != null) {
+ return event.getContextData().getValue(key);
+ } else {
+ 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 <code>null</code> if there is no such information.
+ *
+ * <p>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(new String[0]);
+ }
+ 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 0000000..4ab9db9
--- /dev/null
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/bridge/LogEventWrapper.java
@@ -0,0 +1,218 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.log4j.bridge;
+
+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;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * Exposes a Log4j 1 logging event as a Log4j 2 LogEvent.
+ */
+public class LogEventWrapper implements LogEvent {
+
+ private final LoggingEvent event;
+ private final ContextDataMap contextData;
+ private final MutableThreadContextStack contextStack;
+ private Thread thread;
+
+ public LogEventWrapper(LoggingEvent event) {
+ this.event = event;
+ this.contextData = new ContextDataMap(event.getProperties());
+ this.contextStack = new MutableThreadContextStack(NDC.cloneStack());
+ this.thread = Objects.equals(event.getThreadName(), Thread.currentThread().getName())
+ ? Thread.currentThread() : null;
+ }
+
+ @Override
+ public LogEvent toImmutable() {
+ return this;
+ }
+
+ @Override
+ public ReadOnlyStringMap getContextData() {
+ return contextData;
+ }
+
+ @Override
+ public ThreadContext.ContextStack getContextStack() {
+ return contextStack;
+ }
+
+ @Override
+ public String getLoggerFqcn() {
+ return null;
+ }
+
+ @Override
+ public Level getLevel() {
+ return OptionConverter.convertLevel(event.getLevel());
+ }
+
+ @Override
+ public String getLoggerName() {
+ return event.getLoggerName();
+ }
+
+ @Override
+ public Marker getMarker() {
+ return null;
+ }
+
+ @Override
+ public Message getMessage() {
+ return new SimpleMessage(event.getRenderedMessage());
+ }
+
+ @Override
+ public long getTimeMillis() {
+ return event.getTimeStamp();
+ }
+
+ @Override
+ public Instant getInstant() {
+ MutableInstant mutable = new MutableInstant();
+ mutable.initFromEpochMilli(event.getTimeStamp(), 0);
+ return mutable;
+ }
+
+ @Override
+ public StackTraceElement getSource() {
+ LocationInfo info = event.getLocationInformation();
+ return new StackTraceElement(info.getClassName(), info.getMethodName(), info.getFileName(),
+ Integer.parseInt(info.getLineNumber()));
+ }
+
+ @Override
+ public String getThreadName() {
+ return event.getThreadName();
+ }
+
+ @Override
+ public long getThreadId() {
+ Thread thread = getThread();
+ return thread != null ? thread.getId() : 0;
+ }
+
+ @Override
+ public int getThreadPriority() {
+ Thread thread = getThread();
+ return thread != null ? thread.getPriority() : 0;
+ }
+
+ private Thread getThread() {
+ if (thread == null && event.getThreadName() != null) {
+ for (Thread thread : Thread.getAllStackTraces().keySet()) {
+ if (thread.getName().equals(event.getThreadName())) {
+ this.thread = thread;
+ return thread;
+ }
+ }
+ }
+ return thread;
+ }
+
+ @Override
+ public Throwable getThrown() {
+ ThrowableInformation throwableInformation = event.getThrowableInformation();
+ return throwableInformation == null ? null : throwableInformation.getThrowable();
+ }
+
+ @Override
+ public ThrowableProxy getThrownProxy() {
+ return null;
+ }
+
+ @Override
+ public boolean isEndOfBatch() {
+ return false;
+ }
+
+ @Override
+ public boolean isIncludeLocation() {
+ return false;
+ }
+
+ @Override
+ public void setEndOfBatch(boolean endOfBatch) {
+
+ }
+
+ @Override
+ public void setIncludeLocation(boolean locationRequired) {
+
+ }
+
+ @Override
+ public long getNanoTime() {
+ return 0;
+ }
+
+
+ private static class ContextDataMap extends HashMap<String, String> implements ReadOnlyStringMap {
+
+ ContextDataMap(Map<String, String> map) {
+ if (map != null) {
+ super.putAll(map);
+ }
+ }
+
+ @Override
+ public Map<String, String> toMap() {
+ return this;
+ }
+
+ @Override
+ public boolean containsKey(String key) {
+ return super.containsKey(key);
+ }
+
+ @Override
+ public <V> void forEach(BiConsumer<String, ? super V> action) {
+ super.forEach((k,v) -> action.accept(k, (V) v));
+ }
+
+ @Override
+ public <V, S> void forEach(TriConsumer<String, ? super V, S> action, S state) {
+ super.forEach((k,v) -> action.accept(k, (V) v, state));
+ }
+
+ @Override
+ public <V> V getValue(String key) {
+ return (V) super.get(key);
+ }
+ }
+}
diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/bridge/RewritePolicyAdapter.java b/log4j-1.2-api/src/main/java/org/apache/log4j/bridge/RewritePolicyAdapter.java
new file mode 100644
index 0000000..cd03754
--- /dev/null
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/bridge/RewritePolicyAdapter.java
@@ -0,0 +1,49 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.log4j.bridge;
+
+import org.apache.log4j.rewrite.RewritePolicy;
+import org.apache.log4j.spi.LoggingEvent;
+import org.apache.logging.log4j.core.LogEvent;
+
+
+/**
+ * Binds a Log4j 1.x RewritePolicy to Log4j 2.
+ */
+public class RewritePolicyAdapter implements org.apache.logging.log4j.core.appender.rewrite.RewritePolicy {
+
+ private final RewritePolicy policy;
+
+ /**
+ * Constructor.
+ * @param policy The Rewrite policy.
+ */
+ public RewritePolicyAdapter(RewritePolicy policy) {
+ this.policy = policy;
+ }
+
+ @Override
+ public LogEvent rewrite(LogEvent source) {
+ LoggingEvent event = policy.rewrite(new LogEventAdapter(source));
+ return event instanceof LogEventAdapter ? ((LogEventAdapter) event).getEvent() : new LogEventWrapper(event);
+ }
+
+ public RewritePolicy getPolicy() {
+ return this.policy;
+ }
+
+}
diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/bridge/RewritePolicyWrapper.java b/log4j-1.2-api/src/main/java/org/apache/log4j/bridge/RewritePolicyWrapper.java
new file mode 100644
index 0000000..e0994e1
--- /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.spi.LoggingEvent;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.log4j.rewrite.RewritePolicy;
+
+/**
+ * Binds a Log4j 2 RewritePolicy to Log4j 1.
+ */
+public class RewritePolicyWrapper implements RewritePolicy {
+
+ private final org.apache.logging.log4j.core.appender.rewrite.RewritePolicy policy;
+
+ public RewritePolicyWrapper(org.apache.logging.log4j.core.appender.rewrite.RewritePolicy policy) {
+ this.policy = policy;
+ }
+
+ @Override
+ public LoggingEvent rewrite(LoggingEvent source) {
+ LogEvent event = source instanceof LogEventAdapter ? ((LogEventAdapter) source).getEvent() :
+ new LogEventWrapper(source);
+ return new LogEventAdapter(policy.rewrite(event));
+ }
+
+ public org.apache.logging.log4j.core.appender.rewrite.RewritePolicy getPolicy() {
+ return policy;
+ }
+}
diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/builders/AbstractBuilder.java b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/AbstractBuilder.java
new file mode 100644
index 0000000..b86102c
--- /dev/null
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/AbstractBuilder.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.builders;
+
+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.CompositeFilter;
+import org.apache.logging.log4j.core.filter.ThresholdFilter;
+import org.apache.logging.log4j.status.StatusLogger;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Properties;
+
+/**
+ * Base class for Log4j 1 component builders.
+ */
+public abstract class AbstractBuilder {
+
+ 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 MAX_SIZE_PARAM = "MaxFileSize";
+ protected static final String MAX_BACKUP_INDEX = "MaxBackupIndex";
+ protected static final String RELATIVE = "RELATIVE";
+
+ private final String prefix;
+ private final Properties props;
+
+ public AbstractBuilder() {
+ this.prefix = null;
+ this.props = new Properties();
+ }
+
+ public AbstractBuilder(String prefix, Properties props) {
+ this.prefix = prefix + ".";
+ this.props = props;
+ }
+
+ public String getProperty(String key) {
+ return props.getProperty(prefix + key);
+ }
+
+ public String getProperty(String key, String defaultValue) {
+ return props.getProperty(prefix + key, defaultValue);
+ }
+
+ public boolean getBooleanProperty(String key) {
+ return Boolean.parseBoolean(props.getProperty(prefix + key, Boolean.FALSE.toString()));
+ }
+
+ public int getIntegerProperty(String key, int defaultValue) {
+ String value = props.getProperty(key);
+ try {
+ if (value != null) {
+ return Integer.parseInt(value);
+ }
+ } catch (Exception ex) {
+ LOGGER.warn("Error converting value {} of {} to an integer: {}", value, key, ex.getMessage());
+ }
+ return defaultValue;
+ }
+
+ public Properties getProperties() {
+ return props;
+ }
+
+
+ protected org.apache.logging.log4j.core.Filter buildFilters(String level, Filter filter) {
+ if (level != null && filter != null) {
+ List<org.apache.logging.log4j.core.Filter> filterList = new ArrayList<>();
+ 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);
+ filterList.add(thresholdFilter);
+ Filter f = filter;
+ while (f != null) {
+ if (filter instanceof FilterWrapper) {
+ filterList.add(((FilterWrapper) f).getFilter());
+ } else {
+ filterList.add(new FilterAdapter(f));
+ }
+ f = f.next;
+ }
+ return CompositeFilter.createFilters(filterList.toArray(new org.apache.logging.log4j.core.Filter[0]));
+ } else if (level != null) {
+ return ThresholdFilter.createFilter(OptionConverter.convertLevel(level, Level.TRACE),
+ org.apache.logging.log4j.core.Filter.Result.NEUTRAL,
+ org.apache.logging.log4j.core.Filter.Result.DENY);
+ } else if (filter != null) {
+ if (filter instanceof FilterWrapper) {
+ return ((FilterWrapper) filter).getFilter();
+ } else {
+ return new FilterAdapter(filter);
+ }
+ }
+ return null;
+ }
+}
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 0000000..16e46d3
--- /dev/null
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/BooleanHolder.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.log4j.builders;
+
+/**
+ * Holds Boolean values created inside of a Lambda expression.
+ */
+public class BooleanHolder extends Holder<Boolean> {
+ public BooleanHolder() {
+ super(Boolean.FALSE);
+ }
+
+ @Override
+ public void set(Boolean value) {
+ if (value != null) {
+ super.set(value);
+ }
+ }
+}
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 0000000..ec3a5fb
--- /dev/null
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/BuilderManager.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.log4j.builders;
+
+import org.apache.log4j.Appender;
+import org.apache.log4j.Layout;
+import org.apache.log4j.builders.appender.AppenderBuilder;
+import org.apache.log4j.builders.filter.FilterBuilder;
+import org.apache.log4j.builders.layout.LayoutBuilder;
+import org.apache.log4j.builders.rewrite.RewritePolicyBuilder;
+import org.apache.log4j.config.PropertiesConfiguration;
+import org.apache.log4j.rewrite.RewritePolicy;
+import org.apache.log4j.spi.Filter;
+import org.apache.log4j.xml.XmlConfiguration;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.plugins.util.PluginManager;
+import org.apache.logging.log4j.plugins.util.PluginType;
+import org.apache.logging.log4j.status.StatusLogger;
+import org.apache.logging.log4j.util.LoaderUtil;
+import org.w3c.dom.Element;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.util.Map;
+import java.util.Properties;
+
+/**
+ *
+ */
+public class BuilderManager {
+
+ public static final String CATEGORY = "Log4j Builder";
+ private static final Logger LOGGER = StatusLogger.getLogger();
+ private final Map<String, PluginType<?>> plugins;
+ private static final Class<?>[] constructorParams = new Class[] { String.class, Properties.class};
+
+ public BuilderManager() {
+ final PluginManager manager = new PluginManager(CATEGORY);
+ manager.collectPlugins();
+ plugins = manager.getPlugins();
+ }
+
+ public Appender parseAppender(String className, Element appenderElement, XmlConfiguration config) {
+ PluginType<?> plugin = plugins.get(className.toLowerCase());
+ if (plugin != null) {
+ try {
+ @SuppressWarnings("unchecked")
+ AppenderBuilder builder = (AppenderBuilder) LoaderUtil.newInstanceOf(plugin.getPluginClass());
+ return builder.parseAppender(appenderElement, config);
+ } catch (InstantiationException | IllegalAccessException | InvocationTargetException ex) {
+ LOGGER.warn("Unable to load plugin: {} due to: {}", plugin.getKey(), ex.getMessage());
+ }
+ }
+ return null;
+ }
+
+ public Appender parseAppender(String name, String className, String prefix, String layoutPrefix,
+ String filterPrefix, Properties props, PropertiesConfiguration config) {
+ PluginType<?> plugin = plugins.get(className.toLowerCase());
+ if (plugin != null) {
+ AppenderBuilder builder = createBuilder(plugin, prefix, props);
+ if (builder != null) {
+ return builder.parseAppender(name, prefix, layoutPrefix, filterPrefix, props, config);
+ }
+ }
+ return null;
+ }
+
+ public Filter parseFilter(String className, Element filterElement, XmlConfiguration config) {
+ PluginType<?> plugin = plugins.get(className.toLowerCase());
+ if (plugin != null) {
+ try {
+ @SuppressWarnings("unchecked")
+ FilterBuilder builder = (FilterBuilder) LoaderUtil.newInstanceOf(plugin.getPluginClass());
+ return builder.parseFilter(filterElement, config);
+ } catch (InstantiationException | IllegalAccessException | InvocationTargetException ex) {
+ LOGGER.warn("Unable to load plugin: {} due to: {}", plugin.getKey(), ex.getMessage());
+ }
+ }
+ return null;
+ }
+
+ public Filter parseFilter(String className, String filterPrefix, Properties props, PropertiesConfiguration config) {
+ PluginType<?> plugin = plugins.get(className.toLowerCase());
+ if (plugin != null) {
+ FilterBuilder builder = createBuilder(plugin, filterPrefix, props);
+ if (builder != null) {
+ return builder.parseFilter(config);
+ }
+ }
+ return null;
+ }
+
+ public Layout parseLayout(String className, Element layoutElement, XmlConfiguration config) {
+ PluginType<?> plugin = plugins.get(className.toLowerCase());
+ if (plugin != null) {
+ try {
+ @SuppressWarnings("unchecked")
+ LayoutBuilder builder = (LayoutBuilder) LoaderUtil.newInstanceOf(plugin.getPluginClass());
+ return builder.parseLayout(layoutElement, config);
+ } catch (InstantiationException | IllegalAccessException | InvocationTargetException ex) {
+ LOGGER.warn("Unable to load plugin: {} due to: {}", plugin.getKey(), ex.getMessage());
+ }
+ }
+ return null;
+ }
+ public Layout parseLayout(String className, String layoutPrefix, Properties props, PropertiesConfiguration config) {
+ PluginType<?> plugin = plugins.get(className.toLowerCase());
+ if (plugin != null) {
+ LayoutBuilder builder = createBuilder(plugin, layoutPrefix, props);
+ if (builder != null) {
+ return builder.parseLayout(config);
+ }
+ }
+ return null;
+ }
+
+ public RewritePolicy parseRewritePolicy(String className, Element rewriteElement, XmlConfiguration config) {
+ PluginType<?> plugin = plugins.get(className.toLowerCase());
+ if (plugin != null) {
+ try {
+ @SuppressWarnings("unchecked")
+ RewritePolicyBuilder builder = (RewritePolicyBuilder) LoaderUtil.newInstanceOf(plugin.getPluginClass());
+ return builder.parseRewritePolicy(rewriteElement, config);
+ } catch (InstantiationException | IllegalAccessException | InvocationTargetException ex) {
+ LOGGER.warn("Unable to load plugin: {} due to: {}", plugin.getKey(), ex.getMessage());
+ }
+ }
+ return null;
+ }
+ public RewritePolicy parseRewritePolicy(String className, String policyPrefix, Properties props, PropertiesConfiguration config) {
+ PluginType<?> plugin = plugins.get(className.toLowerCase());
+ if (plugin != null) {
+ RewritePolicyBuilder builder = createBuilder(plugin, policyPrefix, props);
+ if (builder != null) {
+ return builder.parseRewritePolicy(config);
+ }
+ }
+ return null;
+ }
+
+ private <T extends AbstractBuilder> T createBuilder(PluginType<?> plugin, String prefix, Properties props) {
+ try {
+ Class<?> clazz = plugin.getPluginClass();
+ if (AbstractBuilder.class.isAssignableFrom(clazz)) {
+ @SuppressWarnings("unchecked")
+ Constructor<T> constructor =
+ (Constructor<T>) clazz.getConstructor(constructorParams);
+ return constructor.newInstance(prefix, props);
+ } else {
+ @SuppressWarnings("unchecked")
+ T builder = (T) LoaderUtil.newInstanceOf(clazz);
+ return builder;
+ }
+ } catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException ex) {
+ LOGGER.warn("Unable to load plugin: {} due to: {}", plugin.getKey(), ex.getMessage());
+ return 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 0000000..b9ce2bf
--- /dev/null
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/Holder.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;
+
+/**
+ * Provides a place to hold values generated inside of a Lambda expression.
+ */
+public class Holder<T> {
+ private T value;
+
+ public Holder() {
+ }
+
+ public Holder(T defaultValue) {
+ this.value = defaultValue;
+ }
+
+ public void set(T value) {
+ this.value = value;
+ }
+
+ public T get() {
+ return value;
+ }
+}
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 0000000..bb7ff93
--- /dev/null
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/appender/AppenderBuilder.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.builders.appender;
+
+import org.apache.log4j.Appender;
+import org.apache.log4j.config.PropertiesConfiguration;
+import org.apache.log4j.xml.XmlConfiguration;
+import org.w3c.dom.Element;
+
+import java.util.Properties;
+
+/**
+ * Define an Appender Builder.
+ */
+public interface AppenderBuilder {
+
+ 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 0000000..e60d50b
--- /dev/null
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/appender/AsyncAppenderBuilder.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 org.apache.log4j.Appender;
+import org.apache.log4j.bridge.AppenderWrapper;
+import org.apache.log4j.builders.AbstractBuilder;
+import org.apache.log4j.builders.BooleanHolder;
+import org.apache.log4j.builders.Holder;
+import org.apache.log4j.config.Log4j1Configuration;
+import org.apache.log4j.config.PropertiesConfiguration;
+import org.apache.log4j.helpers.OptionConverter;
+import org.apache.log4j.xml.XmlConfiguration;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.core.appender.AsyncAppender;
+import org.apache.logging.log4j.core.config.AppenderRef;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.status.StatusLogger;
+import org.w3c.dom.Element;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Properties;
+
+import static org.apache.log4j.builders.BuilderManager.CATEGORY;
+import static org.apache.log4j.xml.XmlConfiguration.NAME_ATTR;
+import static org.apache.log4j.xml.XmlConfiguration.PARAM_TAG;
+import static org.apache.log4j.xml.XmlConfiguration.VALUE_ATTR;
+import static org.apache.log4j.xml.XmlConfiguration.forEachElement;
+import static org.apache.log4j.config.Log4j1Configuration.APPENDER_REF_TAG;
+import static org.apache.log4j.config.Log4j1Configuration.THRESHOLD_PARAM;
+
+
+/**
+ * Build an Asynch 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(String prefix, Properties props) {
+ super(prefix, props);
+ }
+
+ @Override
+ public Appender parseAppender(final Element appenderElement, final XmlConfiguration config) {
+ String name = appenderElement.getAttribute(NAME_ATTR);
+ Holder<List<String>> appenderRefs = new Holder<>(new ArrayList<>());
+ Holder<Boolean> blocking = new BooleanHolder();
+ Holder<Boolean> includeLocation = new BooleanHolder();
+ Holder<String> level = new Holder<>("trace");
+ Holder<Integer> bufferSize = new Holder<>(1024);
+ forEachElement(appenderElement.getChildNodes(), (currentElement) -> {
+ switch (currentElement.getTagName()) {
+ case APPENDER_REF_TAG:
+ Appender appender = config.findAppenderByReference(currentElement);
+ if (appender != null) {
+ appenderRefs.get().add(appender.getName());
+ }
+ break;
+ case PARAM_TAG: {
+ switch (currentElement.getAttribute(NAME_ATTR)) {
+ case BUFFER_SIZE_PARAM: {
+ String value = currentElement.getAttribute(VALUE_ATTR);
+ if (value == null) {
+ LOGGER.warn("No value supplied for BufferSize parameter. Defaulting to 1024.");
+ } else {
+ bufferSize.set(Integer.parseInt(value));
+ }
+ break;
+ }
+ case BLOCKING_PARAM: {
+ String value = currentElement.getAttribute(VALUE_ATTR);
+ if (value == null) {
+ LOGGER.warn("No value supplied for Blocking parameter. Defaulting to false.");
+ } else {
+ blocking.set(Boolean.parseBoolean(value));
+ }
+ break;
+ }
+ case INCLUDE_LOCATION_PARAM: {
+ String value = currentElement.getAttribute(VALUE_ATTR);
+ if (value == null) {
+ LOGGER.warn("No value supplied for IncludeLocation parameter. Defaulting to false.");
+ } else {
+ includeLocation.set(Boolean.parseBoolean(value));
+ }
+ break;
+ }
+ case THRESHOLD_PARAM: {
+ String value = currentElement.getAttribute(VALUE_ATTR);
+ if (value == null) {
+ LOGGER.warn("No value supplied for Threshold parameter, ignoring.");
+ } else {
+ level.set(value);
+ }
+ break;
+ }
+ }
+ break;
+ }
+ }
+ });
+ return createAppender(name, level.get(), appenderRefs.get().toArray(new String[0]), blocking.get(),
+ bufferSize.get(), includeLocation.get(), config);
+ }
+
+ @Override
+ public Appender parseAppender(final String name, final String appenderPrefix, final String layoutPrefix,
+ final String filterPrefix, final Properties props, final PropertiesConfiguration configuration) {
+ String appenderRef = getProperty(APPENDER_REF_TAG);
+ boolean blocking = getBooleanProperty(BLOCKING_PARAM);
+ boolean includeLocation = getBooleanProperty(INCLUDE_LOCATION_PARAM);
+ String level = getProperty(THRESHOLD_PARAM);
+ int bufferSize = getIntegerProperty(BUFFER_SIZE_PARAM, 1024);
+ if (appenderRef == null) {
+ LOGGER.warn("No appender references configured for AsyncAppender {}", name);
+ return null;
+ }
+ Appender appender = configuration.parseAppender(props, appenderRef);
+ if (appender == null) {
+ LOGGER.warn("Cannot locate Appender {}", appenderRef);
+ return null;
+ }
+ return createAppender(name, level, new String[] {appenderRef}, blocking, bufferSize, includeLocation,
+ configuration);
+ }
+
+ private <T extends Log4j1Configuration> Appender createAppender(String name, String level,
+ String[] appenderRefs, boolean blocking, int bufferSize, boolean includeLocation,
+ T configuration) {
+ org.apache.logging.log4j.Level logLevel = OptionConverter.convertLevel(level,
+ org.apache.logging.log4j.Level.TRACE);
+ AppenderRef[] refs = new AppenderRef[appenderRefs.length];
+ int index = 0;
+ for (String appenderRef : appenderRefs) {
+ refs[index++] = AppenderRef.createAppenderRef(appenderRef, logLevel, null);
+ }
+ return new AppenderWrapper(AsyncAppender.newBuilder()
+ .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 0000000..119e6fb
--- /dev/null
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/appender/ConsoleAppenderBuilder.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 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.bridge.LayoutWrapper;
+import org.apache.log4j.builders.AbstractBuilder;
+import org.apache.log4j.builders.Holder;
+import org.apache.log4j.config.Log4j1Configuration;
+import org.apache.log4j.config.PropertiesConfiguration;
+import org.apache.log4j.spi.Filter;
+import org.apache.log4j.xml.XmlConfiguration;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.core.appender.ConsoleAppender;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.status.StatusLogger;
+import org.w3c.dom.Element;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Properties;
+
+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.NAME_ATTR;
+import static org.apache.log4j.xml.XmlConfiguration.PARAM_TAG;
+import static org.apache.log4j.xml.XmlConfiguration.VALUE_ATTR;
+import static org.apache.log4j.xml.XmlConfiguration.forEachElement;
+
+/**
+ * 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 = "target";
+
+ private static final Logger LOGGER = StatusLogger.getLogger();
+
+ public ConsoleAppenderBuilder() {
+ }
+
+ public ConsoleAppenderBuilder(String prefix, Properties props) {
+ super(prefix, props);
+ }
+
+ @Override
+ public Appender parseAppender(final Element appenderElement, final XmlConfiguration config) {
+ String name = appenderElement.getAttribute(NAME_ATTR);
+ Holder<String> target = new Holder<>(SYSTEM_OUT);
+ Holder<Layout> layout = new Holder<>();
+ Holder<List<Filter>> filters = new Holder<>(new ArrayList<>());
+ Holder<String> level = new Holder<>();
+ forEachElement(appenderElement.getChildNodes(), (currentElement) -> {
+ switch (currentElement.getTagName()) {
+ case LAYOUT_TAG:
+ layout.set(config.parseLayout(currentElement));
+ break;
+ case FILTER_TAG:
+ filters.get().add(config.parseFilters(currentElement));
+ break;
+ case PARAM_TAG: {
+ switch (currentElement.getAttribute(NAME_ATTR)) {
+ case TARGET: {
+ String value = currentElement.getAttribute(VALUE_ATTR);
+ 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 System.out",
+ value);
+ }
+ }
+ break;
+ }
+ case THRESHOLD_PARAM: {
+ String value = currentElement.getAttribute(VALUE_ATTR);
+ if (value == null) {
+ LOGGER.warn("No value supplied for Threshold parameter, ignoring.");
+ } else {
+ level.set(value);
+ }
+ break;
+ }
+ }
+ break;
+ }
+ }
+ });
+ Filter head = null;
+ Filter current = null;
+ for (Filter f : filters.get()) {
+ if (head == null) {
+ head = f;
+ current = f;
+ } else {
+ current.next = f;
+ current = f;
+ }
+ }
+ return createAppender(name, layout.get(), head, level.get(), target.get(), config);
+ }
+
+ @Override
+ public Appender parseAppender(final String name, final String appenderPrefix, final String layoutPrefix,
+ final String filterPrefix, final Properties props, final PropertiesConfiguration configuration) {
+ Layout layout = configuration.parseLayout(layoutPrefix, name, props);
+ Filter filter = configuration.parseAppenderFilters(props, filterPrefix, name);
+ String level = getProperty(THRESHOLD_PARAM);
+ String target = getProperty(TARGET);
+ return createAppender(name, layout, filter, level, target, configuration);
+ }
+
+ private <T extends Log4j1Configuration> Appender createAppender(String name, Layout layout, Filter filter,
+ String level, String target, T configuration) {
+ org.apache.logging.log4j.core.Layout<?> consoleLayout = null;
+
+ if (layout instanceof LayoutWrapper) {
+ consoleLayout = ((LayoutWrapper) layout).getLayout();
+ } else if (layout != null) {
+ consoleLayout = new LayoutAdapter(layout);
+ }
+ org.apache.logging.log4j.core.Filter consoleFilter = buildFilters(level, filter);
+ ConsoleAppender.Target consoleTarget = SYSTEM_ERR.equals(target)
+ ? ConsoleAppender.Target.SYSTEM_ERR : ConsoleAppender.Target.SYSTEM_OUT;
+ return new AppenderWrapper(ConsoleAppender.newBuilder()
+ .setName(name)
+ .setTarget(consoleTarget)
+ .setLayout(consoleLayout)
+ .setFilter(consoleFilter)
+ .setConfiguration(configuration)
+ .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 0000000..89c2dba
--- /dev/null
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/appender/DailyRollingFileAppenderBuilder.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.log4j.builders.appender;
+
+import org.apache.log4j.Appender;
+import org.apache.log4j.Layout;
+import org.apache.log4j.bridge.AppenderWrapper;
+import org.apache.log4j.bridge.LayoutAdapter;
+import org.apache.log4j.bridge.LayoutWrapper;
+import org.apache.log4j.builders.AbstractBuilder;
+import org.apache.log4j.builders.BooleanHolder;
+import org.apache.log4j.builders.Holder;
+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.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.plugins.Plugin;
+import org.apache.logging.log4j.status.StatusLogger;
+import org.w3c.dom.Element;
+
+import java.util.Properties;
+
+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.NAME_ATTR;
+import static org.apache.log4j.xml.XmlConfiguration.PARAM_TAG;
+import static org.apache.log4j.xml.XmlConfiguration.VALUE_ATTR;
+import static org.apache.log4j.xml.XmlConfiguration.forEachElement;
+
+
+/**
+ * Build a Daily Rolling File Appender
+ */
+@Plugin(name = "org.apache.log4j.DailyRollingFileAppender", category = CATEGORY)
+public class DailyRollingFileAppenderBuilder extends AbstractBuilder implements AppenderBuilder {
+
+ private static final Logger LOGGER = StatusLogger.getLogger();
+
+ public DailyRollingFileAppenderBuilder() {
+ }
+
+ public DailyRollingFileAppenderBuilder(String prefix, Properties props) {
+ super(prefix, props);
+ }
+
+
+ @Override
+ public Appender parseAppender(final Element appenderElement, final XmlConfiguration config) {
+ String name = appenderElement.getAttribute(NAME_ATTR);
+ Holder<Layout> layout = new Holder<>();
+ Holder<Filter> filter = new Holder<>();
+ Holder<String> fileName = new Holder<>();
+ Holder<String> level = new Holder<>();
+ Holder<Boolean> immediateFlush = new BooleanHolder();
+ Holder<Boolean> append = new BooleanHolder();
+ Holder<Boolean> bufferedIo = new BooleanHolder();
+ Holder<Integer> bufferSize = new Holder<>(8192);
+ forEachElement(appenderElement.getChildNodes(), (currentElement) -> {
+ switch (currentElement.getTagName()) {
+ case LAYOUT_TAG:
+ layout.set(config.parseLayout(currentElement));
+ break;
+ case FILTER_TAG:
+ filter.set(config.parseFilters(currentElement));
+ break;
+ case PARAM_TAG: {
+ switch (currentElement.getAttribute(NAME_ATTR)) {
+ case FILE_PARAM:
+ fileName.set(currentElement.getAttribute(VALUE_ATTR));
+ break;
+ case APPEND_PARAM: {
+ String bool = currentElement.getAttribute(VALUE_ATTR);
+ if (bool != null) {
+ append.set(Boolean.parseBoolean(bool));
+ } else {
+ LOGGER.warn("No value provided for append parameter");
+ }
+ break;
+ }
+ case BUFFERED_IO_PARAM: {
+ String bool = currentElement.getAttribute(VALUE_ATTR);
+ if (bool != null) {
+ bufferedIo.set(Boolean.parseBoolean(bool));
+ } else {
+ LOGGER.warn("No value provided for bufferedIo parameter");
+ }
+ break;
+ }
+ case BUFFER_SIZE_PARAM: {
+ String size = currentElement.getAttribute(VALUE_ATTR);
+ if (size != null) {
+ bufferSize.set(Integer.parseInt(size));
+ } else {
+ LOGGER.warn("No value provide for bufferSize parameter");
+ }
+ break;
+ }
+ case THRESHOLD_PARAM: {
+ String value = currentElement.getAttribute(VALUE_ATTR);
+ if (value == null) {
+ LOGGER.warn("No value supplied for Threshold parameter, ignoring.");
+ } else {
+ level.set(value);
+ }
+ break;
+ }
+ }
+ break;
+ }
+ }
+ });
+ return createAppender(name, layout.get(), filter.get(), fileName.get(), append.get(), immediateFlush.get(),
+ level.get(), bufferedIo.get(), bufferSize.get(), config);
+ }
+
+ @Override
+ public Appender parseAppender(final String name, final String appenderPrefix, final String layoutPrefix,
+ final String filterPrefix, final Properties props, final PropertiesConfiguration configuration) {
+ Layout layout = configuration.parseLayout(layoutPrefix, name, props);
+ Filter filter = configuration.parseAppenderFilters(props, filterPrefix, name);
+ String fileName = getProperty(FILE_PARAM);
+ String level = getProperty(THRESHOLD_PARAM);
+ boolean append = getBooleanProperty(APPEND_PARAM);
+ boolean immediateFlush = false;
+ boolean bufferedIo = getBooleanProperty(BUFFERED_IO_PARAM);
+ int bufferSize = Integer.parseInt(getProperty(BUFFER_SIZE_PARAM, "8192"));
+ return createAppender(name, layout, filter, fileName, append, immediateFlush,
+ level, bufferedIo, bufferSize, configuration);
+ }
+
+ private <T extends Log4j1Configuration> 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 T configuration) {
+
+ org.apache.logging.log4j.core.Layout<?> fileLayout = null;
+ if (bufferedIo) {
+ immediateFlush = true;
+ }
+ if (layout instanceof LayoutWrapper) {
+ fileLayout = ((LayoutWrapper) layout).getLayout();
+ } else if (layout != null) {
+ fileLayout = new LayoutAdapter(layout);
+ }
+ org.apache.logging.log4j.core.Filter fileFilter = buildFilters(level, filter);
+ if (fileName == null) {
+ LOGGER.warn("Unable to create File Appender, no file name provided");
+ return null;
+ }
+ String filePattern = fileName +"%d{yyy-MM-dd}";
+ TriggeringPolicy policy = TimeBasedTriggeringPolicy.newBuilder().setModulate(true).build();
+ RolloverStrategy strategy = DefaultRolloverStrategy.newBuilder()
+ .setConfig(configuration)
+ .setMax(Integer.toString(Integer.MAX_VALUE))
+ .build();
+ return new AppenderWrapper(RollingFileAppender.newBuilder()
+ .setName(name)
+ .setConfiguration(configuration)
+ .setLayout(fileLayout)
+ .setFilter(fileFilter)
+ .setFileName(fileName)
+ .setBufferSize(bufferSize)
+ .setImmediateFlush(immediateFlush)
+ .setFilePattern(filePattern)
+ .setPolicy(policy)
+ .setStrategy(strategy)
+ .build());
+ }
+}
diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/builders/appender/FileAppenderBuilder.java b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/appender/FileAppenderBuilder.java
new file mode 100644
index 0000000..8b7f467
--- /dev/null
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/appender/FileAppenderBuilder.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.log4j.builders.appender;
+
+import org.apache.log4j.Appender;
+import org.apache.log4j.Layout;
+import org.apache.log4j.bridge.AppenderWrapper;
+import org.apache.log4j.bridge.LayoutAdapter;
+import org.apache.log4j.bridge.LayoutWrapper;
+import org.apache.log4j.builders.AbstractBuilder;
+import org.apache.log4j.builders.BooleanHolder;
+import org.apache.log4j.builders.Holder;
+import org.apache.log4j.config.Log4j1Configuration;
+import org.apache.log4j.config.PropertiesConfiguration;
+import org.apache.log4j.spi.Filter;
+import org.apache.log4j.xml.XmlConfiguration;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.core.appender.FileAppender;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.status.StatusLogger;
+import org.w3c.dom.Element;
+
+import java.util.Properties;
+
+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 static org.apache.log4j.xml.XmlConfiguration.NAME_ATTR;
+import static org.apache.log4j.xml.XmlConfiguration.VALUE_ATTR;
+
+/**
+ * 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(String prefix, Properties props) {
+ super(prefix, props);
+ }
+
+ @Override
+ public Appender parseAppender(Element appenderElement, XmlConfiguration config) {
+ String name = appenderElement.getAttribute(NAME_ATTR);
+ Holder<Layout> layout = new Holder<>();
+ Holder<Filter> filter = new Holder<>();
+ Holder<String> fileName = new Holder<>();
+ Holder<String> level = new Holder<>();
+ Holder<Boolean> immediateFlush = new BooleanHolder();
+ Holder<Boolean> append = new BooleanHolder();
+ Holder<Boolean> bufferedIo = new BooleanHolder();
+ Holder<Integer> bufferSize = new Holder<>(8192);
+ forEachElement(appenderElement.getChildNodes(), (currentElement) -> {
+ switch (currentElement.getTagName()) {
+ case LAYOUT_TAG:
+ layout.set(config.parseLayout(currentElement));
+ break;
+ case FILTER_TAG:
+ filter.set(config.parseFilters(currentElement));
+ break;
+ case PARAM_TAG: {
+ switch (currentElement.getAttribute(NAME_ATTR)) {
+ case FILE_PARAM:
+ fileName.set(currentElement.getAttribute(VALUE_ATTR));
+ break;
+ case APPEND_PARAM: {
+ String bool = currentElement.getAttribute(VALUE_ATTR);
+ if (bool != null) {
+ append.set(Boolean.parseBoolean(bool));
+ } else {
+ LOGGER.warn("No value provided for append parameter");
+ }
+ break;
+ }
+ case BUFFERED_IO_PARAM: {
+ String bool = currentElement.getAttribute(VALUE_ATTR);
+ if (bool != null) {
+ bufferedIo.set(Boolean.parseBoolean(bool));
+ } else {
+ LOGGER.warn("No value provided for bufferedIo parameter");
+ }
+ break;
+ }
+ case BUFFER_SIZE_PARAM: {
+ String size = currentElement.getAttribute(VALUE_ATTR);
+ if (size != null) {
+ bufferSize.set(Integer.parseInt(size));
+ } else {
+ LOGGER.warn("No value provide for bufferSize parameter");
+ }
+ break;
+ }
+ case THRESHOLD_PARAM: {
+ String value = currentElement.getAttribute(VALUE_ATTR);
+ if (value == null) {
+ LOGGER.warn("No value supplied for Threshold parameter, ignoring.");
+ } else {
+ level.set(value);
+ }
+ 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) {
+ Layout layout = configuration.parseLayout(layoutPrefix, name, props);
+ Filter filter = configuration.parseAppenderFilters(props, filterPrefix, name);
+ String level = getProperty(THRESHOLD_PARAM);
+ String fileName = getProperty(FILE_PARAM);
+ boolean append = getBooleanProperty(APPEND_PARAM);
+ boolean immediateFlush = false;
+ boolean bufferedIo = getBooleanProperty(BUFFERED_IO_PARAM);
+ 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, String level, boolean immediateFlush, final boolean append,
+ final boolean bufferedIo, final int bufferSize) {
+ org.apache.logging.log4j.core.Layout<?> fileLayout = null;
+ if (bufferedIo) {
+ immediateFlush = true;
+ }
+ if (layout instanceof LayoutWrapper) {
+ fileLayout = ((LayoutWrapper) layout).getLayout();
+ } else if (layout != null) {
+ fileLayout = new LayoutAdapter(layout);
+ }
+ org.apache.logging.log4j.core.Filter fileFilter = buildFilters(level, filter);
+ if (fileName == null) {
+ LOGGER.warn("Unable to create File Appender, no file name provided");
+ return null;
+ }
+ return new AppenderWrapper(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 0000000..07f2fd5
--- /dev/null
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/appender/NullAppenderBuilder.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.appender;
+
+import org.apache.log4j.Appender;
+import org.apache.log4j.bridge.AppenderWrapper;
+import org.apache.log4j.config.PropertiesConfiguration;
+import org.apache.log4j.xml.XmlConfiguration;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.core.appender.NullAppender;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.status.StatusLogger;
+import org.w3c.dom.Element;
+
+import java.util.Properties;
+
+import static org.apache.log4j.builders.BuilderManager.CATEGORY;
+
+/**
+ * Build a Null Appender
+ */
+@Plugin(name = "org.apache.log4j.varia.NullAppender", category = CATEGORY)
+public class NullAppenderBuilder implements AppenderBuilder {
+
+ private static final Logger LOGGER = StatusLogger.getLogger();
+
+ @Override
+ public Appender parseAppender(Element appenderElement, XmlConfiguration config) {
+ String name = appenderElement.getAttribute("name");
+ return new AppenderWrapper(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 new AppenderWrapper(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 0000000..37ac70f
--- /dev/null
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/appender/RewriteAppenderBuilder.java
@@ -0,0 +1,152 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.log4j.builders.appender;
+
+import org.apache.log4j.Appender;
+import org.apache.log4j.bridge.AppenderWrapper;
+import org.apache.log4j.bridge.RewritePolicyAdapter;
+import org.apache.log4j.bridge.RewritePolicyWrapper;
+import org.apache.log4j.builders.AbstractBuilder;
+import org.apache.log4j.builders.Holder;
+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.Logger;
+import org.apache.logging.log4j.core.appender.rewrite.RewriteAppender;
+import org.apache.logging.log4j.core.config.AppenderRef;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.status.StatusLogger;
+import org.w3c.dom.Element;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Properties;
+
+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.NAME_ATTR;
+import static org.apache.log4j.xml.XmlConfiguration.PARAM_TAG;
+import static org.apache.log4j.xml.XmlConfiguration.VALUE_ATTR;
+import static org.apache.log4j.xml.XmlConfiguration.forEachElement;
+
+
+/**
+ * Build an Asynch 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(String prefix, Properties props) {
+ super(prefix, props);
+ }
+
+ @Override
+ public Appender parseAppender(final Element appenderElement, final XmlConfiguration config) {
+ String name = appenderElement.getAttribute(NAME_ATTR);
+ Holder<List<String>> appenderRefs = new Holder<>(new ArrayList<>());
+ Holder<RewritePolicy> rewritePolicyHolder = new Holder<>();
+ Holder<String> level = new Holder<>();
+ Holder<Filter> filter = new Holder<>();
+ forEachElement(appenderElement.getChildNodes(), (currentElement) -> {
+ switch (currentElement.getTagName()) {
+ case APPENDER_REF_TAG:
+ Appender appender = config.findAppenderByReference(currentElement);
+ if (appender != null) {
+ appenderRefs.get().add(appender.getName());
+ }
+ break;
+ case REWRITE_POLICY_TAG: {
+ RewritePolicy policy = config.parseRewritePolicy(currentElement);
+ if (policy != null) {
+ rewritePolicyHolder.set(policy);
+ }
+ break;
+ }
+ case FILTER_TAG: {
+ filter.set(config.parseFilters(currentElement));
+ break;
+ }
+ case PARAM_TAG: {
+ if (currentElement.getAttribute(NAME_ATTR).equalsIgnoreCase(THRESHOLD_PARAM)) {
+ String value = currentElement.getAttribute(VALUE_ATTR);
+ if (value == null) {
+ LOGGER.warn("No value supplied for Threshold parameter, ignoring.");
+ } else {
+ level.set(value);
+ }
+ }
+ break;
+ }
+ }
+ });
+ return createAppender(name, level.get(), appenderRefs.get().toArray(new String[0]), 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) {
+ String appenderRef = getProperty(APPENDER_REF_TAG);
+ Filter filter = configuration.parseAppenderFilters(props, filterPrefix, name);
+ String policyPrefix = appenderPrefix + ".rewritePolicy";
+ String className = getProperty(policyPrefix);
+ RewritePolicy policy = configuration.getBuilderManager().parseRewritePolicy(className, policyPrefix,
+ props, configuration);
+ String level = getProperty(THRESHOLD_PARAM);
+ if (appenderRef == null) {
+ LOGGER.warn("No appender references configured for AsyncAppender {}", name);
+ return null;
+ }
+ Appender appender = configuration.parseAppender(props, appenderRef);
+ if (appender == null) {
+ LOGGER.warn("Cannot locate Appender {}", appenderRef);
+ return null;
+ }
+ return createAppender(name, level, new String[] {appenderRef}, policy, filter, configuration);
+ }
+
+ private <T extends Log4j1Configuration> Appender createAppender(String name, String level,
+ String[] appenderRefs, RewritePolicy policy, Filter filter, T configuration) {
+ org.apache.logging.log4j.Level logLevel = OptionConverter.convertLevel(level,
+ org.apache.logging.log4j.Level.TRACE);
+ AppenderRef[] refs = new AppenderRef[appenderRefs.length];
+ int index = 0;
+ for (String appenderRef : appenderRefs) {
+ refs[index++] = AppenderRef.createAppenderRef(appenderRef, logLevel, null);
+ }
+ 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 new AppenderWrapper(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 0000000..bac9425
--- /dev/null
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/appender/RollingFileAppenderBuilder.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.log4j.builders.appender;
+
+import org.apache.log4j.Appender;
+import org.apache.log4j.Layout;
+import org.apache.log4j.bridge.AppenderWrapper;
+import org.apache.log4j.bridge.LayoutAdapter;
+import org.apache.log4j.bridge.LayoutWrapper;
+import org.apache.log4j.builders.AbstractBuilder;
+import org.apache.log4j.builders.BooleanHolder;
+import org.apache.log4j.builders.Holder;
+import org.apache.log4j.config.Log4j1Configuration;
+import org.apache.log4j.config.PropertiesConfiguration;
+import org.apache.log4j.spi.Filter;
+import org.apache.log4j.xml.XmlConfiguration;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.core.appender.RollingFileAppender;
+import org.apache.logging.log4j.core.appender.rolling.CompositeTriggeringPolicy;
+import org.apache.logging.log4j.core.appender.rolling.DefaultRolloverStrategy;
+import org.apache.logging.log4j.core.appender.rolling.RolloverStrategy;
+import org.apache.logging.log4j.core.appender.rolling.SizeBasedTriggeringPolicy;
+import org.apache.logging.log4j.core.appender.rolling.TimeBasedTriggeringPolicy;
+import org.apache.logging.log4j.core.appender.rolling.TriggeringPolicy;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.status.StatusLogger;
+import org.w3c.dom.Element;
+
+import java.util.Properties;
+
+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.NAME_ATTR;
+import static org.apache.log4j.xml.XmlConfiguration.PARAM_TAG;
+import static org.apache.log4j.xml.XmlConfiguration.VALUE_ATTR;
+import static org.apache.log4j.xml.XmlConfiguration.forEachElement;
+
+
+/**
+ * Build a File Appender
+ */
+@Plugin(name = "org.apache.log4j.RollingFileAppender", category = CATEGORY)
+public class RollingFileAppenderBuilder extends AbstractBuilder implements AppenderBuilder {
+
+ private static final Logger LOGGER = StatusLogger.getLogger();
+
+ public RollingFileAppenderBuilder() {
+ }
+
+ public RollingFileAppenderBuilder(String prefix, Properties props) {
+ super(prefix, props);
+ }
+
+ @Override
+ public Appender parseAppender(Element appenderElement, XmlConfiguration config) {
+ String name = appenderElement.getAttribute(NAME_ATTR);
+ Holder<Layout> layout = new Holder<>();
+ Holder<Filter> filter = new Holder<>();
+ Holder<String> fileName = new Holder<>();
+ Holder<Boolean> immediateFlush = new BooleanHolder();
+ Holder<Boolean> append = new BooleanHolder();
+ Holder<Boolean> bufferedIo = new BooleanHolder();
+ Holder<Integer> bufferSize = new Holder<>(8192);
+ Holder<String> maxSize = new Holder<>();
+ Holder<String> maxBackups = new Holder<>();
+ Holder<String> level = new Holder<>();
+ forEachElement(appenderElement.getChildNodes(), (currentElement) -> {
+ switch (currentElement.getTagName()) {
+ case LAYOUT_TAG:
+ layout.set(config.parseLayout(currentElement));
+ break;
+ case FILTER_TAG:
+ filter.set(config.parseFilters(currentElement));
+ break;
+ case PARAM_TAG: {
+ switch (currentElement.getAttribute(NAME_ATTR)) {
+ case FILE_PARAM:
+ fileName.set(currentElement.getAttribute(VALUE_ATTR));
+ break;
+ case APPEND_PARAM: {
+ String bool = currentElement.getAttribute(VALUE_ATTR);
+ if (bool != null) {
+ append.set(Boolean.parseBoolean(bool));
+ } else {
+ LOGGER.warn("No value provided for append parameter");
+ }
+ break;
+ }
+ case BUFFERED_IO_PARAM: {
+ String bool = currentElement.getAttribute(VALUE_ATTR);
+ if (bool != null) {
+ bufferedIo.set(Boolean.parseBoolean(bool));
+ } else {
+ LOGGER.warn("No value provided for bufferedIo parameter");
+ }
+ break;
+ }
+ case BUFFER_SIZE_PARAM: {
+ String size = currentElement.getAttribute(VALUE_ATTR);
+ if (size != null) {
+ bufferSize.set(Integer.parseInt(size));
+ } else {
+ LOGGER.warn("No value provide for bufferSize parameter");
+ }
+ break;
+ }
+ case MAX_BACKUP_INDEX: {
+ String size = currentElement.getAttribute(VALUE_ATTR);
+ if (size != null) {
+ maxBackups.set(size);
+ } else {
+ LOGGER.warn("No value provide for maxBackupIndex parameter");
+ }
+ break;
+ }
+ case MAX_SIZE_PARAM: {
+ String size = currentElement.getAttribute(VALUE_ATTR);
+ if (size != null) {
+ maxSize.set(size);
+ } else {
+ LOGGER.warn("No value provide for bufferSize parameter");
+ }
+ break;
+ }
+ case THRESHOLD_PARAM: {
+ String value = currentElement.getAttribute(VALUE_ATTR);
+ if (value == null) {
+ LOGGER.warn("No value supplied for Threshold parameter, ignoring.");
+ } else {
+ level.set(value);
+ }
+ break;
+ }
+ }
+ break;
+ }
+ }
+ });
+ return createAppender(name, config, layout.get(), filter.get(), bufferedIo.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) {
+ Layout layout = configuration.parseLayout(layoutPrefix, name, props);
+ Filter filter = configuration.parseAppenderFilters(props, filterPrefix, name);
+ String fileName = getProperty(FILE_PARAM);
+ String level = getProperty(THRESHOLD_PARAM);
+ boolean immediateFlush = false;
+ boolean bufferedIo = getBooleanProperty(BUFFERED_IO_PARAM);
+ String maxSize = getProperty(MAX_SIZE_PARAM);
+ String maxBackups = getProperty(MAX_BACKUP_INDEX);
+ return createAppender(name, configuration, layout, filter, bufferedIo, immediateFlush, fileName, level, maxSize,
+ maxBackups);
+ }
+
+ private Appender createAppender(final String name, final Log4j1Configuration config, final Layout layout,
+ final Filter filter, final boolean bufferedIo, boolean immediateFlush, final String fileName,
+ final String level, final String maxSize, final String maxBackups) {
+ org.apache.logging.log4j.core.Layout<?> fileLayout = null;
+ if (bufferedIo) {
+ immediateFlush = true;
+ }
+ if (layout instanceof LayoutWrapper) {
+ fileLayout = ((LayoutWrapper) layout).getLayout();
+ } else if (layout != null) {
+ fileLayout = new LayoutAdapter(layout);
+ }
+ org.apache.logging.log4j.core.Filter fileFilter = buildFilters(level, filter);
+ if (fileName == null) {
+ LOGGER.warn("Unable to create File Appender, no file name provided");
+ return null;
+ }
+ String filePattern = fileName +"%d{yyy-MM-dd}";
+ TriggeringPolicy timePolicy = TimeBasedTriggeringPolicy.newBuilder().setModulate(true).build();
+ SizeBasedTriggeringPolicy sizePolicy = SizeBasedTriggeringPolicy.createPolicy(maxSize);
+ CompositeTriggeringPolicy policy = CompositeTriggeringPolicy.createPolicy(sizePolicy, timePolicy);
+ RolloverStrategy strategy = DefaultRolloverStrategy.newBuilder()
+ .setConfig(config)
+ .setMax(maxBackups)
+ .build();
+ return new AppenderWrapper(RollingFileAppender.newBuilder()
+ .setName(name)
+ .setConfiguration(config)
+ .setLayout(fileLayout)
+ .setFilter(fileFilter)
+ .setBufferedIo(bufferedIo)
+ .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/SyslogAppenderBuilder.java b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/appender/SyslogAppenderBuilder.java
new file mode 100644
index 0000000..72f9e2b
--- /dev/null
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/appender/SyslogAppenderBuilder.java
@@ -0,0 +1,173 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.log4j.builders.appender;
+
+import org.apache.log4j.Appender;
+import org.apache.log4j.Layout;
+import org.apache.log4j.bridge.AppenderWrapper;
+import org.apache.log4j.bridge.LayoutAdapter;
+import org.apache.log4j.bridge.LayoutWrapper;
+import org.apache.log4j.builders.AbstractBuilder;
+import org.apache.log4j.builders.Holder;
+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.SyslogAppender;
+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.plugins.Plugin;
+import org.apache.logging.log4j.status.StatusLogger;
+import org.w3c.dom.Element;
+
+import java.util.Properties;
+
+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.NAME_ATTR;
+import static org.apache.log4j.xml.XmlConfiguration.PARAM_TAG;
+import static org.apache.log4j.xml.XmlConfiguration.VALUE_ATTR;
+import static org.apache.log4j.xml.XmlConfiguration.forEachElement;
+
+/**
+ * Build a File Appender
+ */
+@Plugin(name = "org.apache.log4j.net.SyslogAppender", category = CATEGORY)
+public class SyslogAppenderBuilder extends AbstractBuilder implements AppenderBuilder {
+
+ private static final Logger LOGGER = StatusLogger.getLogger();
+ private static final String FACILITY_PARAM = "Facility";
+ private static final String SYSLOG_HOST_PARAM = "SyslogHost";
+ private static final int SYSLOG_PORT = 512;
+
+ public SyslogAppenderBuilder() {
+ }
+
+ public SyslogAppenderBuilder(String prefix, Properties props) {
+ super(prefix, props);
+ }
+
+ @Override
+ public Appender parseAppender(Element appenderElement, XmlConfiguration config) {
+ String name = appenderElement.getAttribute(NAME_ATTR);
+ Holder<Layout> layout = new Holder<>();
+ Holder<Filter> filter = new Holder<>();
+ Holder<String> facility = new Holder<>();
+ Holder<String> level = new Holder<>();
+ Holder<String> host = new Holder<>();
+ forEachElement(appenderElement.getChildNodes(), (currentElement) -> {
+ switch (currentElement.getTagName()) {
+ case LAYOUT_TAG:
+ layout.set(config.parseLayout(currentElement));
+ break;
+ case FILTER_TAG:
+ filter.set(config.parseFilters(currentElement));
+ break;
+ case PARAM_TAG: {
+ switch (currentElement.getAttribute(NAME_ATTR)) {
+ case SYSLOG_HOST_PARAM: {
+ host.set(currentElement.getAttribute(VALUE_ATTR));
+ break;
+ }
+ case FACILITY_PARAM:
+ facility.set(currentElement.getAttribute(VALUE_ATTR));
+ break;
+ case THRESHOLD_PARAM: {
+ String value = currentElement.getAttribute(VALUE_ATTR);
+ if (value == null) {
+ LOGGER.warn("No value supplied for Threshold parameter, ignoring.");
+ } else {
+ level.set(value);
+ }
+ break;
+ }
+ }
+ break;
+ }
+ }
+ });
+
+ return createAppender(name, config, layout.get(), facility.get(), filter.get(), host.get(), level.get());
+ }
+
+
+ @Override
+ public Appender parseAppender(final String name, final String appenderPrefix, final String layoutPrefix,
+ final String filterPrefix, final Properties props, final PropertiesConfiguration configuration) {
+ Filter filter = configuration.parseAppenderFilters(props, filterPrefix, name);
+ Layout layout = configuration.parseLayout(layoutPrefix, name, props);
+ String level = getProperty(THRESHOLD_PARAM);
+ String facility = getProperty(FACILITY_PARAM, "LOCAL0");
+ String syslogHost = getProperty(SYSLOG_HOST_PARAM, "localhost:514");
+
+ return createAppender(name, configuration, layout, facility, filter, syslogHost, level);
+ }
+
+ private Appender createAppender(final String name, final Log4j1Configuration configuration, Layout layout,
+ String facility, final Filter filter, final String syslogHost, final String level) {
+ Holder<String> host = new Holder<>();
+ Holder<Integer> port = new Holder<>();
+ resolveSyslogHost(syslogHost, host, port);
+ org.apache.logging.log4j.core.Layout appenderLayout;
+ if (layout instanceof LayoutWrapper) {
+ appenderLayout = ((LayoutWrapper) layout).getLayout();
+ } else if (layout != null) {
+ appenderLayout = new LayoutAdapter(layout);
+ } else {
+ appenderLayout = SyslogLayout.newBuilder()
+ .setFacility(Facility.toFacility(facility))
+ .setConfiguration(configuration)
+ .build();
+ }
+
+ org.apache.logging.log4j.core.Filter fileFilter = buildFilters(level, filter);
+ return new AppenderWrapper(SyslogAppender.newBuilder()
+ .setName(name)
+ .setConfiguration(configuration)
+ .setLayout(appenderLayout)
+ .setFilter(fileFilter)
+ .setPort(port.get())
+ .setProtocol(Protocol.TCP)
+ .setHost(host.get())
+ .build());
+ }
+
+ private void resolveSyslogHost(String syslogHost, Holder<String> host, Holder<Integer> port) {
+ int urlPort = -1;
+
+ //
+ // If not an unbracketed IPv6 address then
+ // parse as a URL
+ //
+ String[] parts = syslogHost.split(":");
+ if (parts.length == 1) {
+ host.set(parts[0]);
+ port.set(SYSLOG_PORT);
+ } else if (parts.length == 2) {
+ host.set(parts[0]);
+ port.set(Integer.parseInt(parts[1]));
+ } else {
+ LOGGER.warn("Invalid syslogHost setting: {}. Using default", syslogHost);
+ host.set("localhost");
+ port.set(SYSLOG_PORT);
+ }
+ }
+}
diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/builders/filter/DenyAllFilterBuilder.java b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/filter/DenyAllFilterBuilder.java
new file mode 100644
index 0000000..f15d6e5
--- /dev/null
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/filter/DenyAllFilterBuilder.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.log4j.builders.filter;
+
+import org.apache.log4j.bridge.FilterWrapper;
+import org.apache.log4j.config.PropertiesConfiguration;
+import org.apache.log4j.spi.Filter;
+import org.apache.log4j.xml.XmlConfiguration;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.core.filter.DenyAllFilter;
+import org.apache.logging.log4j.status.StatusLogger;
+import org.w3c.dom.Element;
+
+import static org.apache.log4j.builders.BuilderManager.CATEGORY;
+
+/**
+ * Build a Pattern Layout
+ */
+@Plugin(name = "org.apache.log4j.varia.DenyAllFilter", category = CATEGORY)
+public class DenyAllFilterBuilder implements FilterBuilder {
+
+ private static final Logger LOGGER = StatusLogger.getLogger();
+
+ @Override
+ public Filter parseFilter(Element filterElement, XmlConfiguration config) {
+ return new FilterWrapper(DenyAllFilter.newBuilder().build());
+ }
+
+ @Override
+ public Filter parseFilter(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 0000000..f26e7cb
--- /dev/null
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/filter/FilterBuilder.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.log4j.builders.filter;
+
+import org.apache.log4j.config.PropertiesConfiguration;
+import org.apache.log4j.spi.Filter;
+import org.apache.log4j.xml.XmlConfiguration;
+import org.w3c.dom.Element;
+
+/**
+ * Define a Filter Builder.
+ */
+public interface FilterBuilder {
+
+ Filter parseFilter(Element element, XmlConfiguration config);
+
+ Filter parseFilter(PropertiesConfiguration config);
+
+}
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 0000000..5b1e435
--- /dev/null
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/filter/LevelMatchFilterBuilder.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.builders.filter;
+
+import org.apache.log4j.bridge.FilterWrapper;
+import org.apache.log4j.builders.AbstractBuilder;
+import org.apache.log4j.builders.BooleanHolder;
+import org.apache.log4j.builders.Holder;
+import org.apache.log4j.config.PropertiesConfiguration;
+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.plugins.Plugin;
+import org.apache.logging.log4j.core.filter.LevelMatchFilter;
+import org.apache.logging.log4j.status.StatusLogger;
+import org.w3c.dom.Element;
+
+import java.util.Properties;
+
+import static org.apache.log4j.builders.BuilderManager.CATEGORY;
+import static org.apache.log4j.xml.XmlConfiguration.*;
+import static org.apache.log4j.xml.XmlConfiguration.VALUE_ATTR;
+
+/**
+ * Build a Level match failter.
+ */
+@Plugin(name = "org.apache.log4j.varia.LevelMatchFilter", category = CATEGORY)
+public class LevelMatchFilterBuilder extends AbstractBuilder implements FilterBuilder {
+
+ private static final Logger LOGGER = StatusLogger.getLogger();
+ private static final String LEVEL = "LevelToMatch";
+ private static final String ACCEPT_ON_MATCH = "AcceptOnMatch";
+
+ public LevelMatchFilterBuilder() {
+ }
+
+ public LevelMatchFilterBuilder(String prefix, Properties props) {
+ super(prefix, props);
+ }
+
+ @Override
+ public Filter parseFilter(Element filterElement, XmlConfiguration config) {
+ final Holder<String> level = new Holder<>();
+ final Holder<Boolean> acceptOnMatch = new BooleanHolder();
+ forEachElement(filterElement.getElementsByTagName("param"), (currentElement) -> {
+ if (currentElement.getTagName().equals("param")) {
+ switch (currentElement.getAttribute(NAME_ATTR)) {
+ case LEVEL:
+ level.set(currentElement.getAttribute(VALUE_ATTR));
+ break;
+ case ACCEPT_ON_MATCH:
+ acceptOnMatch.set(Boolean.parseBoolean(currentElement.getAttribute(VALUE_ATTR)));
+ break;
+ }
+ }
+ });
+ return createFilter(level.get(), acceptOnMatch.get());
+ }
+
+ @Override
+ public Filter parseFilter(PropertiesConfiguration config) {
+ String level = getProperty(LEVEL);
+ boolean acceptOnMatch = getBooleanProperty(ACCEPT_ON_MATCH);
+ return createFilter(level, acceptOnMatch);
+ }
+
+ private Filter createFilter(String level, boolean acceptOnMatch) {
+ Level lvl = Level.ERROR;
+ if (level != null) {
+ lvl = Level.toLevel(level, Level.ERROR);
+ }
+ 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 new FilterWrapper(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 0000000..ca2c70f
--- /dev/null
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/filter/LevelRangeFilterBuilder.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.log4j.builders.filter;
+
+import org.apache.log4j.bridge.FilterWrapper;
+import org.apache.log4j.builders.AbstractBuilder;
+import org.apache.log4j.builders.BooleanHolder;
+import org.apache.log4j.builders.Holder;
+import org.apache.log4j.config.PropertiesConfiguration;
+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.plugins.Plugin;
+import org.apache.logging.log4j.core.filter.LevelRangeFilter;
+import org.apache.logging.log4j.status.StatusLogger;
+import org.w3c.dom.Element;
+
+
+import java.util.Properties;
+
+import static org.apache.log4j.builders.BuilderManager.CATEGORY;
+import static org.apache.log4j.xml.XmlConfiguration.*;
+
+/**
+ * Build a Level match failter.
+ */
+@Plugin(name = "org.apache.log4j.varia.LevelRangeFilter", category = CATEGORY)
+public class LevelRangeFilterBuilder extends AbstractBuilder implements FilterBuilder {
+
+ private static final Logger LOGGER = StatusLogger.getLogger();
+ private static final String LEVEL_MAX = "LevelMax";
+ private static final String LEVEL_MIN = "LevelMin";
+ private static final String ACCEPT_ON_MATCH = "AcceptOnMatch";
+
+ public LevelRangeFilterBuilder() {
+ }
+
+ public LevelRangeFilterBuilder(String prefix, Properties props) {
+ super(prefix, props);
+ }
+
+ @Override
+ public Filter parseFilter(Element filterElement, XmlConfiguration config) {
+ final Holder<String> levelMax = new Holder<>();
+ final Holder<String> levelMin = new Holder<>();
+ final Holder<Boolean> acceptOnMatch = new BooleanHolder();
+ forEachElement(filterElement.getElementsByTagName("param"), (currentElement) -> {
+ if (currentElement.getTagName().equals("param")) {
+ switch (currentElement.getAttribute(NAME_ATTR)) {
+ case LEVEL_MAX:
+ levelMax.set(currentElement.getAttribute(VALUE_ATTR));
+ break;
+ case LEVEL_MIN:
+ levelMax.set(currentElement.getAttribute(VALUE_ATTR));
+ break;
+ case ACCEPT_ON_MATCH:
+ acceptOnMatch.set(Boolean.parseBoolean(currentElement.getAttribute(VALUE_ATTR)));
+ break;
+ }
+ }
+ });
+ return createFilter(levelMax.get(), levelMin.get(), acceptOnMatch.get());
+ }
+
+ @Override
+ public Filter parseFilter(PropertiesConfiguration config) {
+ String levelMax = getProperty(LEVEL_MAX);
+ String levelMin = getProperty(LEVEL_MIN);
+ boolean acceptOnMatch = getBooleanProperty(ACCEPT_ON_MATCH);
+ return createFilter(levelMax, levelMin, acceptOnMatch);
+ }
+
+ private Filter createFilter(String levelMax, String levelMin, boolean acceptOnMatch) {
+ Level max = Level.FATAL;
+ Level min = Level.TRACE;
+ if (levelMax != null) {
+ max = Level.toLevel(levelMax, Level.FATAL);
+ }
+ if (levelMin != null) {
+ min = Level.toLevel(levelMin, Level.DEBUG);
+ }
+ org.apache.logging.log4j.core.Filter.Result onMatch = acceptOnMatch
+ ? org.apache.logging.log4j.core.Filter.Result.ACCEPT
+ : org.apache.logging.log4j.core.Filter.Result.NEUTRAL;
+
+ return new FilterWrapper(LevelRangeFilter.createFilter(min, max, onMatch,
+ org.apache.logging.log4j.core.Filter.Result.DENY));
+ }
+}
diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/builders/filter/StringMatchFilterBuilder.java b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/filter/StringMatchFilterBuilder.java
new file mode 100644
index 0000000..f81dbdc
--- /dev/null
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/filter/StringMatchFilterBuilder.java
@@ -0,0 +1,88 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.log4j.builders.filter;
+
+import org.apache.log4j.bridge.FilterWrapper;
+import org.apache.log4j.builders.AbstractBuilder;
+import org.apache.log4j.builders.BooleanHolder;
+import org.apache.log4j.builders.Holder;
+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.plugins.Plugin;
+import org.apache.logging.log4j.core.filter.StringMatchFilter;
+import org.apache.logging.log4j.status.StatusLogger;
+import org.w3c.dom.Element;
+
+import java.util.Properties;
+
+import static org.apache.log4j.builders.BuilderManager.CATEGORY;
+import static org.apache.log4j.xml.XmlConfiguration.*;
+
+/**
+ * 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";
+
+ @Override
+ public Filter parseFilter(Element filterElement, XmlConfiguration config) {
+ final Holder<Boolean> acceptOnMatch = new BooleanHolder();
+ final Holder<String> text = new Holder<>();
+ forEachElement(filterElement.getElementsByTagName("param"), (currentElement) -> {
+ if (currentElement.getTagName().equals("param")) {
+ switch (currentElement.getAttribute(NAME_ATTR)) {
+ case STRING_TO_MATCH:
+ text.set(currentElement.getAttribute(VALUE_ATTR));
+ break;
+ case ACCEPT_ON_MATCH:
+ acceptOnMatch.set(Boolean.parseBoolean(currentElement.getAttribute(VALUE_ATTR)));
+ break;
+
+ }
+ }
+ });
+ return createFilter(text.get(), acceptOnMatch.get());
+ }
+
+ @Override
+ public Filter parseFilter(PropertiesConfiguration config) {
+ String text = getProperty(STRING_TO_MATCH);
+ boolean acceptOnMatch = getBooleanProperty(ACCEPT_ON_MATCH);
+ return createFilter(text, acceptOnMatch);
+ }
+
+ private Filter createFilter(String text, boolean acceptOnMatch) {
+ if (text == null) {
+ LOGGER.warn("No text provided for StringMatchFilter");
+ return null;
+ }
+ org.apache.logging.log4j.core.Filter.Result onMatch = acceptOnMatch
+ ? org.apache.logging.log4j.core.Filter.Result.ACCEPT
+ : org.apache.logging.log4j.core.Filter.Result.DENY;
+ return new FilterWrapper(StringMatchFilter.newBuilder()
+ .setMatchString(text)
+ .setOnMatch(onMatch)
+ .setOnMismatch(org.apache.logging.log4j.core.Filter.Result.NEUTRAL)
+ .build());
+ }
+}
diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/builders/layout/HtmlLayoutBuilder.java b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/layout/HtmlLayoutBuilder.java
new file mode 100644
index 0000000..423350e
--- /dev/null
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/layout/HtmlLayoutBuilder.java
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.log4j.builders.layout;
+
+import org.apache.log4j.Layout;
+import org.apache.log4j.bridge.LayoutWrapper;
+import org.apache.log4j.builders.AbstractBuilder;
+import org.apache.log4j.builders.BooleanHolder;
+import org.apache.log4j.builders.Holder;
+import org.apache.log4j.config.PropertiesConfiguration;
+import org.apache.log4j.xml.XmlConfiguration;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.core.layout.HtmlLayout;
+import org.apache.logging.log4j.status.StatusLogger;
+import org.w3c.dom.Element;
+
+import java.util.Properties;
+
+import static org.apache.log4j.builders.BuilderManager.CATEGORY;
+import static org.apache.log4j.xml.XmlConfiguration.*;
+
+/**
+ * Build a Pattern Layout
+ */
+@Plugin(name = "org.apache.log4j.HTMLLayout", category = CATEGORY)
+public class HtmlLayoutBuilder extends AbstractBuilder implements LayoutBuilder {
+
+ private static final Logger LOGGER = StatusLogger.getLogger();
+
+ private static final String TITLE = "Title";
+ private static final String LOCATION_INFO = "LocationInfo";
+
+ public HtmlLayoutBuilder() {
+ }
+
+ public HtmlLayoutBuilder(String prefix, Properties props) {
+ super(prefix, props);
+ }
+
+
+ @Override
+ public Layout parseLayout(Element layoutElement, XmlConfiguration config) {
+ final Holder<String> title = new Holder<>();
+ final Holder<Boolean> locationInfo = new BooleanHolder();
+ forEachElement(layoutElement.getElementsByTagName("param"), (currentElement) -> {
+ if (currentElement.getTagName().equals(PARAM_TAG)) {
+ if (TITLE.equalsIgnoreCase(currentElement.getAttribute("name"))) {
+ title.set(currentElement.getAttribute("value"));
+ } else if (LOCATION_INFO.equalsIgnoreCase(currentElement.getAttribute("name"))) {
+ locationInfo.set(Boolean.parseBoolean(currentElement.getAttribute("value")));
+ }
+ }
+ });
+ return createLayout(title.get(), locationInfo.get());
+ }
+
+ @Override
+ public Layout parseLayout(PropertiesConfiguration config) {
+ String title = getProperty(TITLE);
+ boolean locationInfo = getBooleanProperty(LOCATION_INFO);
+ return createLayout(title, locationInfo);
+ }
+
+ private Layout createLayout(String title, boolean locationInfo) {
+ return new LayoutWrapper(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 0000000..4f28284
--- /dev/null
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/layout/LayoutBuilder.java
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.log4j.builders.layout;
+
+import org.apache.log4j.Layout;
+import org.apache.log4j.config.PropertiesConfiguration;
+import org.apache.log4j.xml.XmlConfiguration;
+import org.w3c.dom.Element;
+
+/**
+ * Define a Layout Builder.
+ */
+public interface LayoutBuilder {
+
+ Layout parseLayout(Element element, XmlConfiguration config);
+
+ Layout parseLayout(PropertiesConfiguration config);
+}
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 0000000..1849768
--- /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 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.PluginAliases;
+import org.apache.logging.log4j.core.layout.PatternLayout;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.status.StatusLogger;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+
+import java.util.Properties;
+
+import static org.apache.log4j.builders.BuilderManager.CATEGORY;
+import static org.apache.log4j.xml.XmlConfiguration.PARAM_TAG;
+
+/**
+ * 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(String prefix, Properties props) {
+ super(prefix, props);
+ }
+
+ @Override
+ public Layout parseLayout(final Element layoutElement, final XmlConfiguration config) {
+ NodeList params = layoutElement.getElementsByTagName("param");
+ final int length = params.getLength();
+ String pattern = null;
+ for (int index = 0; index < length; ++ index) {
+ Node currentNode = params.item(index);
+ if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
+ Element currentElement = (Element) currentNode;
+ if (currentElement.getTagName().equals(PARAM_TAG)) {
+ if (PATTERN.equalsIgnoreCase(currentElement.getAttribute("name"))) {
+ pattern = currentElement.getAttribute("value");
+ break;
+ }
+ }
+ }
+ }
+ return createLayout(pattern, config);
+ }
+
+ @Override
+ public Layout parseLayout(final PropertiesConfiguration config) {
+ String pattern = getProperty(PATTERN);
+ return createLayout(pattern, config);
+ }
+
+ private 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 new LayoutWrapper(PatternLayout.newBuilder()
+ .setPattern(pattern
+ // 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"))
+ .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 0000000..d35beba
--- /dev/null
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/layout/SimpleLayoutBuilder.java
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.log4j.builders.layout;
+
+import org.apache.log4j.Layout;
+import org.apache.log4j.bridge.LayoutWrapper;
+import org.apache.log4j.config.PropertiesConfiguration;
+import org.apache.log4j.xml.XmlConfiguration;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.core.layout.PatternLayout;
+import org.apache.logging.log4j.status.StatusLogger;
+import org.w3c.dom.Element;
+
+import java.util.Properties;
+
+import static org.apache.log4j.builders.BuilderManager.CATEGORY;
+
+/**
+ * Build a Pattern Layout
+ */
+@Plugin(name = "org.apache.log4j.SimpleLayout", category = CATEGORY)
+public class SimpleLayoutBuilder implements LayoutBuilder {
+
+ private static final Logger LOGGER = StatusLogger.getLogger();
+
+ @Override
+ public Layout parseLayout(Element layoutElement, XmlConfiguration config) {
+ return new LayoutWrapper(PatternLayout.newBuilder()
+ .setPattern("%level - %m%n")
+ .setConfiguration(config)
+ .build());
+ }
+
+ @Override
+ public Layout parseLayout(PropertiesConfiguration config) {
+ return new LayoutWrapper(PatternLayout.newBuilder()
+ .setPattern("%level - %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 0000000..2641ea2
--- /dev/null
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/layout/TTCCLayoutBuilder.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.log4j.builders.layout;
+
+import org.apache.log4j.Layout;
+import org.apache.log4j.bridge.LayoutWrapper;
+import org.apache.log4j.builders.AbstractBuilder;
+import org.apache.log4j.builders.BooleanHolder;
+import org.apache.log4j.builders.Holder;
+import org.apache.log4j.config.Log4j1Configuration;
+import org.apache.log4j.config.PropertiesConfiguration;
+import org.apache.log4j.xml.XmlConfiguration;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.core.layout.PatternLayout;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.status.StatusLogger;
+import org.w3c.dom.Element;
+
+import java.util.Properties;
+
+import static org.apache.log4j.builders.BuilderManager.CATEGORY;
+import static org.apache.log4j.xml.XmlConfiguration.*;
+
+/**
+ * Build a Pattern Layout
+ */
+@Plugin(name = "org.apache.log4j.TTCCLayout", category = CATEGORY)
+public class TTCCLayoutBuilder extends AbstractBuilder implements LayoutBuilder {
+
+ private static final Logger LOGGER = StatusLogger.getLogger();
+
+ private static final String THREAD_PRINTING_PARAM = "ThreadPrinting";
+ private static final String CATEGORY_PREFIXING_PARAM = "CategoryPrefixing";
+ private static final String CONTEXT_PRINTING_PARAM = "ContextPrinting";
+ private static final String DATE_FORMAT_PARAM = "DateFormat";
+ private static final String TIMEZONE_FORMAT = "TimeZone";
+
+ public TTCCLayoutBuilder() {
+ }
+
+ public TTCCLayoutBuilder(String prefix, Properties props) {
+ super(prefix, props);
+ }
+
+ @Override
+ public Layout parseLayout(Element layoutElement, XmlConfiguration config) {
+ final Holder<Boolean> threadPrinting = new BooleanHolder();
+ final Holder<Boolean> categoryPrefixing = new BooleanHolder();
+ final Holder<Boolean> contextPrinting = new BooleanHolder();
+ final Holder<String> dateFormat = new Holder<>();
+ final Holder<String> timezone = new Holder<>();
+ forEachElement(layoutElement.getElementsByTagName("param"), (currentElement) -> {
+ if (currentElement.getTagName().equals(PARAM_TAG)) {
+ switch (currentElement.getAttribute(NAME_ATTR)) {
+ case THREAD_PRINTING_PARAM:
+ threadPrinting.set(Boolean.parseBoolean(currentElement.getAttribute(VALUE_ATTR)));
+ break;
+ case CATEGORY_PREFIXING_PARAM:
+ categoryPrefixing.set(Boolean.parseBoolean(currentElement.getAttribute(VALUE_ATTR)));
+ break;
+ case CONTEXT_PRINTING_PARAM:
+ contextPrinting.set(Boolean.parseBoolean(currentElement.getAttribute(VALUE_ATTR)));
+ break;
+ case DATE_FORMAT_PARAM:
+ dateFormat.set(currentElement.getAttribute(VALUE_ATTR));
+ break;
+ case TIMEZONE_FORMAT:
+ timezone.set(currentElement.getAttribute(VALUE_ATTR));
+ break;
+ }
+ }
+ });
+ return createLayout(threadPrinting.get(), categoryPrefixing.get(), contextPrinting.get(),
+ dateFormat.get(), timezone.get(), config);
+ }
+
+ @Override
+ public Layout parseLayout(PropertiesConfiguration config) {
+ boolean threadPrinting = getBooleanProperty(THREAD_PRINTING_PARAM);
+ boolean categoryPrefixing = getBooleanProperty(CATEGORY_PREFIXING_PARAM);
+ boolean contextPrinting = getBooleanProperty(CONTEXT_PRINTING_PARAM);
+ String dateFormat = getProperty(DATE_FORMAT_PARAM);
+ String timezone = getProperty(TIMEZONE_FORMAT);
+
+ return createLayout(threadPrinting, categoryPrefixing, contextPrinting,
+ dateFormat, timezone, config);
+ }
+
+ private Layout createLayout(boolean threadPrinting, boolean categoryPrefixing, boolean contextPrinting,
+ String dateFormat, String timezone, Log4j1Configuration config) {
+ StringBuilder sb = new StringBuilder();
+ if (dateFormat != null) {
+ if (RELATIVE.equalsIgnoreCase(dateFormat)) {
+ sb.append("%r ");
+ } else {
+ 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 new LayoutWrapper(PatternLayout.newBuilder()
+ .setPattern(sb.toString())
+ .setConfiguration(config)
+ .build());
+ }
+}
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 0000000..5ca196a
--- /dev/null
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/builders/rewrite/RewritePolicyBuilder.java
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.log4j.builders.rewrite;
+
+import org.apache.log4j.config.PropertiesConfiguration;
+import org.apache.log4j.rewrite.RewritePolicy;
+import org.apache.log4j.xml.XmlConfiguration;
+import org.w3c.dom.Element;
+
+/**
+ * Define a RewritePolicy Builder.
+ */
+public interface RewritePolicyBuilder {
+
+ RewritePolicy parseRewritePolicy(Element element, XmlConfiguration config);
+
+ RewritePolicy parseRewritePolicy(PropertiesConfiguration config);
+}
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 0000000..9236006
--- /dev/null
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/config/Log4j1Configuration.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.config;
+
+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";
+
+ protected final BuilderManager manager;
+
+ public Log4j1Configuration(final LoggerContext loggerContext, final ConfigurationSource source,
+ int monitorIntervalSeconds) {
+ super(loggerContext, source);
+ manager = new BuilderManager();
+ initializeWatchers(this, source, monitorIntervalSeconds);
+ }
+
+ public BuilderManager getBuilderManager() {
+ return manager;
+ }
+
+ /**
+ * Initialize the configuration.
+ */
+ @Override
+ public void initialize() {
+ getStrSubstitutor().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/PropertiesConfiguration.java b/log4j-1.2-api/src/main/java/org/apache/log4j/config/PropertiesConfiguration.java
new file mode 100644
index 0000000..2ceffaf
--- /dev/null
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/config/PropertiesConfiguration.java
@@ -0,0 +1,592 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.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.AppenderWrapper;
+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.core.LoggerContext;
+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.AbstractFilterable;
+import org.apache.logging.log4j.util.LoaderUtil;
+
+import java.io.InputStream;
+import java.lang.reflect.InvocationTargetException;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.SortedMap;
+import java.util.StringTokenizer;
+import java.util.TreeMap;
+
+/**
+ * Construct 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";
+ public static final long DEFAULT_DELAY = 60000;
+ public static final String DEBUG_KEY="log4j.debug";
+
+ private static final String INTERNAL_ROOT_NAME = "root";
+
+ private final Map<String, Appender> registry;
+
+ /**
+ * Constructor.
+ * @param loggerContext The LoggerContext.
+ * @param source The ConfigurationSource.
+ * @param monitorIntervalSeconds The monitoring interval in seconds.
+ */
+ public PropertiesConfiguration(final LoggerContext loggerContext, final ConfigurationSource source,
+ int monitorIntervalSeconds) {
+ super(loggerContext, source, monitorIntervalSeconds);
+ registry = new HashMap<>();
+ }
+
+ public void doConfigure() {
+ InputStream is = getConfigurationSource().getInputStream();
+ Properties props = new Properties();
+ try {
+ props.load(is);
+ } catch (Exception e) {
+ LOGGER.error("Could not read configuration file [{}].", getConfigurationSource().toString(), e);
+ return;
+ }
+ // If we reach here, then the config file is alright.
+ doConfigure(props);
+ }
+
+ /**
+ * Read configuration from a file. <b>The existing configuration is
+ * not cleared nor reset.</b> If you require a different behavior,
+ * then call {@link LogManager#resetConfiguration
+ * resetConfiguration} method before calling
+ * <code>doConfigure</code>.
+ *
+ * <p>The configuration file consists of statements in the format
+ * <code>key=value</code>. The syntax of different configuration
+ * elements are discussed below.
+ *
+ * <p>The level value can consist of the string values OFF, FATAL,
+ * ERROR, WARN, INFO, DEBUG, ALL or a <em>custom level</em> 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 <code>ALL</code>.
+ * </p>
+ *
+ *
+ * <h3>Appender configuration</h3>
+ *
+ * <p>Appender configuration syntax is:
+ * <pre>
+ * # For appender named <i>appenderName</i>, 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
+ * </pre>
+ * <p>
+ * For each named appender you can configure its {@link Layout}. The
+ * syntax for configuring an appender's layout is:
+ * <pre>
+ * log4j.appender.appenderName.layout=fully.qualified.name.of.layout.class
+ * log4j.appender.appenderName.layout.option1=value1
+ * ....
+ * log4j.appender.appenderName.layout.optionN=valueN
+ * </pre>
+ * <p>
+ * The syntax for adding {@link Filter}s to an appender is:
+ * <pre>
+ * 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
+ * </pre>
+ * 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.
+ * <p>
+ * The syntax for adding an {@link ErrorHandler} to an appender is:
+ * <pre>
+ * 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
+ * </pre>
+ *
+ * <h3>Configuring loggers</h3>
+ *
+ * <p>The syntax for configuring the root logger is:
+ * <pre>
+ * log4j.rootLogger=[level], appenderName, appenderName, ...
+ * </pre>
+ *
+ * <p>This syntax means that an optional <em>level</em> can be
+ * supplied followed by appender names separated by commas.
+ *
+ * <p>The level value can consist of the string values OFF, FATAL,
+ * ERROR, WARN, INFO, DEBUG, ALL or a <em>custom level</em> value. A
+ * custom level value can be specified in the form
+ * <code>level#classname</code>.
+ *
+ * <p>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.
+ *
+ * <p>The root logger can be assigned multiple appenders.
+ *
+ * <p>Each <i>appenderName</i> (separated by commas) will be added to
+ * the root logger. The named appender is defined using the
+ * appender syntax defined above.
+ *
+ * <p>For non-root categories the syntax is almost the same:
+ * <pre>
+ * log4j.logger.logger_name=[level|INHERITED|NULL], appenderName, appenderName, ...
+ * </pre>
+ *
+ * <p>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.
+ *
+ * <p>If no level value is supplied, then the level of the
+ * named logger remains untouched.
+ *
+ * <p>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.
+ *
+ * <p>Similar to the root logger syntax, each <i>appenderName</i>
+ * (separated by commas) will be attached to the named logger.
+ *
+ * <p>See the <a href="../../../../manual.html#additivity">appender
+ * additivity rule</a> in the user manual for the meaning of the
+ * <code>additivity</code> 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
+ * # <b>%r %-5p %c{2} %M.%L %x - %m\n</b>. 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
+ * </pre>
+ *
+ * <p>Refer to the <b>setOption</b> method in each Appender and
+ * Layout for class specific options.
+ *
+ * <p>Use the <code>#</code> or <code>!</code> characters at the
+ * beginning of a line for comments.
+ *
+ * <p>
+ */
+ private void doConfigure(Properties properties) {
+ String status = "error";
+ String value = properties.getProperty(DEBUG_KEY);
+ if (value == null) {
+ value = properties.getProperty("log4j.configDebug");
+ if (value != null) {
+ LOGGER.warn("[log4j.configDebug] is deprecated. Use [log4j.debug] instead.");
+ }
+ }
+
+ if (value != null) {
+ status = OptionConverter.toBoolean(value, false) ? "debug" : "error";
+ }
+
+ final StatusConfiguration statusConfig = new StatusConfiguration().setStatus(status);
+ statusConfig.initialize();
+
+ configureRoot(properties);
+ parseLoggers(properties);
+
+ LOGGER.debug("Finished configuring.");
+ }
+
+ // --------------------------------------------------------------------------
+ // Internal stuff
+ // --------------------------------------------------------------------------
+
+ private void configureRoot(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 {
+ LoggerConfig root = getRootLogger();
+ parseLogger(props, root, effectiveFrefix, INTERNAL_ROOT_NAME, value);
+ }
+ }
+
+ /**
+ * Parse non-root elements, such non-root categories and renderers.
+ */
+ private void parseLoggers(Properties props) {
+ Enumeration enumeration = props.propertyNames();
+ while (enumeration.hasMoreElements()) {
+ 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());
+ }
+ String value = OptionConverter.findAndSubst(key, props);
+ LoggerConfig loggerConfig = getLogger(loggerName);
+ if (loggerConfig == null) {
+ 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);
+ }
+ }
+ }
+
+ /**
+ * Parse the additivity option for a non-root category.
+ */
+ private boolean getAdditivityForLogger(Properties props, String loggerName) {
+ boolean additivity = true;
+ String key = ADDITIVITY_PREFIX + loggerName;
+ String value = OptionConverter.findAndSubst(key, props);
+ LOGGER.debug("Handling {}=[{}]", key, value);
+ // touch additivity only if necessary
+ if ((value != null) && (!value.equals(""))) {
+ additivity = OptionConverter.toBoolean(value, true);
+ }
+ return additivity;
+ }
+
+ /**
+ * This method must work for the root category as well.
+ */
+ private void parseLogger(Properties props, LoggerConfig logger, String optionKey, String loggerName, String value) {
+
+ LOGGER.debug("Parsing for [{}] with value=[{}].", loggerName, value);
+ // We must skip over ',' but not white space
+ StringTokenizer st = new StringTokenizer(value, ",");
+
+ // If value is not in the form ", appender.." or "", then we should set the level of the logger.
+
+ if (!(value.startsWith(",") || value.equals(""))) {
+
+ // just to be on the safe side...
+ if (!st.hasMoreTokens()) {
+ return;
+ }
+
+ String levelStr = st.nextToken();
+ LOGGER.debug("Level token is [{}].", levelStr);
+
+ org.apache.logging.log4j.Level level = levelStr == null ? org.apache.logging.log4j.Level.ERROR :
+ OptionConverter.convertLevel(levelStr, org.apache.logging.log4j.Level.DEBUG);
+ logger.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,
+ logger.getName());
+ logger.addAppender(getAppender(appenderName), null, null);
+ } else {
+ LOGGER.debug("Appender named [{}] not found.", appenderName);
+ }
+ }
+ }
+
+ public Appender parseAppender(Properties props, 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.";
+ String className = OptionConverter.findAndSubst(prefix, props);
+ 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);
+ if (appender instanceof AppenderWrapper) {
+ addAppender(((AppenderWrapper) appender).getAppender());
+ } else {
+ addAppender(new AppenderAdapter(appender).getAdapter());
+ }
+ }
+ return appender;
+ }
+
+ private Appender buildAppender(final String appenderName, final String className, final String prefix,
+ final String layoutPrefix, final String filterPrefix, final Properties props) {
+ Appender appender = newInstanceOf(className, "Appender");
+ if (appender == null) {
+ return null;
+ }
+ appender.setName(appenderName);
+ appender.setLayout(parseLayout(layoutPrefix, appenderName, props));
+ final String errorHandlerPrefix = prefix + ".errorhandler";
+ String errorHandlerClass = OptionConverter.findAndSubst(errorHandlerPrefix, props);
+ if (errorHandlerClass != null) {
+ ErrorHandler eh = parseErrorHandler(props, errorHandlerPrefix, errorHandlerClass, appender);
+ if (eh != null) {
+ appender.setErrorHandler(eh);
+ }
+ }
+ parseAppenderFilters(props, filterPrefix, appenderName);
+ String[] keys = new String[] {
+ layoutPrefix,
+ };
+ addProperties(appender, keys, props, prefix);
+ if (appender instanceof AppenderWrapper) {
+ addAppender(((AppenderWrapper) appender).getAppender());
+ } else {
+ addAppender(new AppenderAdapter(appender).getAdapter());
+ }
+ registry.put(appenderName, appender);
+ return appender;
+ }
+
+ public Layout parseLayout(String layoutPrefix, String appenderName, Properties props) {
+ String layoutClass = OptionConverter.findAndSubst(layoutPrefix, props);
+ if (layoutClass == null) {
+ return null;
+ }
+ Layout layout = manager.parseLayout(layoutClass, layoutPrefix, props, this);
+ if (layout == null) {
+ layout = buildLayout(layoutPrefix, layoutClass, appenderName, props);
+ }
+ return layout;
+ }
+
+ private Layout buildLayout(String layoutPrefix, String className, String appenderName, Properties props) {
+ 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) {
+ ErrorHandler eh = newInstanceOf(errorHandlerClass, "ErrorHandler");
+ final String[] keys = new String[] {
+ errorHandlerPrefix + "." + ROOT_REF,
+ errorHandlerPrefix + "." + LOGGER_REF,
+ errorHandlerPrefix + "." + APPENDER_REF_TAG
+ };
+ 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 (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(Properties props, String filterPrefix, 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
+ int fIdx = filterPrefix.length();
+ SortedMap<String, List<NameValue>> filters = new TreeMap<>();
+ Enumeration e = props.keys();
+ String name = "";
+ while (e.hasMoreElements()) {
+ String key = (String) e.nextElement();
+ if (key.startsWith(filterPrefix)) {
+ int dotIdx = key.indexOf('.', fIdx);
+ String filterKey = key;
+ if (dotIdx != -1) {
+ filterKey = key.substring(0, dotIdx);
+ name = key.substring(dotIdx + 1);
+ }
+ List<NameValue> filterOpts = filters.computeIfAbsent(filterKey, k -> new ArrayList<>());
+ if (dotIdx != -1) {
+ String value = OptionConverter.findAndSubst(key, props);
+ filterOpts.add(new NameValue(name, value));
+ }
+ }
+ }
+
+ Filter head = null;
+ Filter next = null;
+ for (Map.Entry<String, List<NameValue>> entry : filters.entrySet()) {
+ String clazz = props.getProperty(entry.getKey());
+ Filter filter = null;
+ if (clazz != null) {
+ filter = manager.parseFilter(clazz, filterPrefix, props, this);
+ if (filter == null) {
+ LOGGER.debug("Filter key: [{}] class: [{}] props: {}", entry.getKey(), clazz, entry.getValue());
+ filter = buildFilter(clazz, appenderName, entry.getValue());
+ }
+ }
+ if (filter != null) {
+ if (head != null) {
+ head = filter;
+ next = filter;
+ } else {
+ next.setNext(filter);
+ next = filter;
+ }
+ }
+ }
+ return head;
+ }
+
+ private Filter buildFilter(String className, String appenderName, List<NameValue> props) {
+ Filter filter = newInstanceOf(className, "Filter");
+ if (filter != null) {
+ PropertySetter propSetter = new PropertySetter(filter);
+ for (NameValue property : props) {
+ propSetter.setProperty(property.key, property.value);
+ }
+ propSetter.activate();
+ }
+ return filter;
+ }
+
+
+ private static <T> T newInstanceOf(String className, String type) {
+ try {
+ return LoaderUtil.newInstanceOf(className);
+ } catch (ClassNotFoundException | IllegalAccessException | NoSuchMethodException |
+ InstantiationException | InvocationTargetException ex) {
+ LOGGER.error("Unable to create {} {} due to {}:{}", type, className,
+ ex.getClass().getSimpleName(), ex.getMessage());
+ return null;
+ }
+ }
+
+ private static class NameValue {
+ String key, value;
+
+ NameValue(String key, String value) {
+ this.key = key;
+ this.value = value;
+ }
+
+ 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 0000000..465eaa1
--- /dev/null
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/config/PropertiesConfigurationFactory.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.config;
+
+import org.apache.logging.log4j.core.LoggerContext;
+import org.apache.logging.log4j.core.config.Configuration;
+import org.apache.logging.log4j.core.config.ConfigurationFactory;
+import org.apache.logging.log4j.core.config.ConfigurationSource;
+import org.apache.logging.log4j.core.config.Order;
+import org.apache.logging.log4j.plugins.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 {
+
+
+ /**
+ * 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[] {".properties"};
+ }
+
+ @Override
+ public Configuration getConfiguration(LoggerContext loggerContext, ConfigurationSource source) {
+ int interval = PropertiesUtil.getProperties().getIntegerProperty(Log4j1Configuration.MONITOR_INTERVAL, 0);
+ return new PropertiesConfiguration(loggerContext, source, interval);
+ }
+
+ @Override
+ protected String getTestPrefix() {
+ return TEST_PREFIX;
+ }
+
+ @Override
+ protected String getDefaultPrefix() {
+ return DEFAULT_PREFIX;
+ }
+
+ @Override
+ protected String getVersion() {
+ return LOG4J1_VERSION;
+ }
+}
diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/config/PropertySetter.java b/log4j-1.2-api/src/main/java/org/apache/log4j/config/PropertySetter.java
index 342024a..b328ec3 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,158 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
+ * contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache license, Version 2.0
+ * The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
+ * the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the license for the specific language governing permissions and
- * limitations under the license.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
*/
+
+// Contributors: Georg Lundesgaard
+
package org.apache.log4j.config;
+import org.apache.log4j.Appender;
+import org.apache.log4j.Level;
+import org.apache.log4j.Priority;
+import org.apache.log4j.spi.ErrorHandler;
+import org.apache.log4j.spi.OptionHandler;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.core.util.OptionConverter;
+import org.apache.logging.log4j.status.StatusLogger;
+
+import java.beans.BeanInfo;
+import java.beans.IntrospectionException;
+import java.beans.Introspector;
import java.beans.PropertyDescriptor;
+import java.io.InterruptedIOException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
import java.util.Properties;
/**
+ * General purpose Object property setter. Clients repeatedly invokes
+ * {@link #setProperty setProperty(name,value)} in order to invoke setters
+ * on the Object specified in the constructor. This class relies on the
+ * JavaBeans {@link Introspector} to analyze the given Object Class using
+ * reflection.
*
- * @since 1.1
+ * <p>Usage:
+ * <pre>
+ * PropertySetter ps = new PropertySetter(anObject);
+ * ps.set("name", "Joe");
+ * ps.set("age", "32");
+ * ps.set("isMale", "true");
+ * </pre>
+ * 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 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.
+ * in prepartion for invoking {@link #setProperty} one or more times.
*
- * @param obj the object for which to set properties
+ * @param obj the object for which to set properties
*/
- public PropertySetter(final Object obj) {
+ public PropertySetter(Object obj) {
+ this.obj = obj;
}
+ /**
+ * Set the properties of an object passed as a parameter in one
+ * go. The <code>properties</code> are parsed relative to a
+ * <code>prefix</code>.
+ *
+ * @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(Object obj, Properties properties, String prefix) {
+ new PropertySetter(obj).setProperties(properties, prefix);
+ }
/**
- * Set the properties for the object that match the <code>prefix</code> passed as parameter.
- *
- * @param properties The properties
- * @param prefix The prefix
+ * Uses JavaBeans {@link Introspector} to computer setters of object to be
+ * configured.
*/
- public void setProperties(final Properties properties, final String prefix) {
+ protected void introspect() {
+ try {
+ BeanInfo bi = Introspector.getBeanInfo(obj.getClass());
+ props = bi.getPropertyDescriptors();
+ } catch (IntrospectionException ex) {
+ LOGGER.error("Failed to introspect {}: {}", obj, ex.getMessage());
+ props = new PropertyDescriptor[0];
+ }
+ }
+
+ /**
+ * Set the properites for the object that match the
+ * <code>prefix</code> passed as parameter.
+ * @param properties The properties.
+ * @param prefix The prefix of the properties to use.
+ */
+ public void setProperties(Properties properties, String prefix) {
+ int len = prefix.length();
+
+ for (String key : properties.stringPropertyNames()) {
+
+ // handle only properties that start with the desired prefix.
+ if (key.startsWith(prefix)) {
+
+
+ // ignore key if it contains dots after the prefix
+ if (key.indexOf('.', len + 1) > 0) {
+ continue;
+ }
+
+ String value = OptionConverter.findAndSubst(key, properties);
+ key = key.substring(len);
+ if (("layout".equals(key) || "errorhandler".equals(key)) && obj instanceof Appender) {
+ continue;
+ }
+ //
+ // if the property type is an OptionHandler
+ // (for example, triggeringPolicy of org.apache.log4j.rolling.RollingFileAppender)
+ PropertyDescriptor prop = getPropertyDescriptor(Introspector.decapitalize(key));
+ if (prop != null
+ && OptionHandler.class.isAssignableFrom(prop.getPropertyType())
+ && prop.getWriteMethod() != null) {
+ OptionHandler opt = (OptionHandler)
+ OptionConverter.instantiateByKey(properties, prefix + key,
+ prop.getPropertyType(),
+ null);
+ PropertySetter setter = new PropertySetter(opt);
+ setter.setProperties(properties, prefix + key + ".");
+ try {
+ prop.getWriteMethod().invoke(this.obj, opt);
+ } catch (InvocationTargetException ex) {
+ if (ex.getTargetException() instanceof InterruptedException
+ || ex.getTargetException() instanceof InterruptedIOException) {
+ Thread.currentThread().interrupt();
+ }
+ LOGGER.warn("Failed to set property [{}] to value \"{}\".", key, value, ex);
+ } catch (IllegalAccessException | RuntimeException ex) {
+ LOGGER.warn("Failed to set property [{}] to value \"{}\".", key, value, ex);
+ }
+ continue;
+ }
+
+ setProperty(key, value);
+ }
+ }
+ activate();
}
/**
@@ -56,33 +167,127 @@
* to an int using new Integer(value). If the setter expects a boolean,
* the conversion is by new Boolean(value).
*
- * @param name name of the property
- * @param value String value of the property
+ * @param name name of the property
+ * @param value String value of the property
*/
- public void setProperty(final String name, final String value) {
+ public void setProperty(String name, String value) {
+ if (value == null) {
+ return;
+ }
+
+ name = Introspector.decapitalize(name);
+ PropertyDescriptor prop = getPropertyDescriptor(name);
+
+ //LOGGER.debug("---------Key: "+name+", type="+prop.getPropertyType());
+
+ if (prop == null) {
+ LOGGER.warn("No such property [" + name + "] in " +
+ obj.getClass().getName() + ".");
+ } else {
+ try {
+ setProperty(prop, name, value);
+ } catch (PropertySetterException ex) {
+ LOGGER.warn("Failed to set property [{}] to value \"{}\".", name, value, ex.rootCause);
+ }
+ }
}
/**
* Set the named property given a {@link PropertyDescriptor}.
*
- * @param prop A PropertyDescriptor describing the characteristics of the property to set.
- * @param name The named of the property to set.
+ * @param prop A PropertyDescriptor describing the characteristics
+ * of the property to set.
+ * @param name The named of the property to set.
* @param value The value of the property.
- * @throws PropertySetterException (Never actually throws this exception. Kept for historical purposes.)
+ * @throws PropertySetterException if no setter is available.
*/
- public void setProperty(final PropertyDescriptor prop, final String name, final String value)
- throws PropertySetterException {
+ public void setProperty(PropertyDescriptor prop, String name, String value)
+ throws PropertySetterException {
+ Method setter = prop.getWriteMethod();
+ if (setter == null) {
+ throw new PropertySetterException("No setter for property [" + name + "].");
+ }
+ Class<?>[] paramTypes = setter.getParameterTypes();
+ if (paramTypes.length != 1) {
+ throw new PropertySetterException("#params for setter != 1");
+ }
+
+ Object arg;
+ try {
+ arg = convertArg(value, paramTypes[0]);
+ } catch (Throwable t) {
+ throw new PropertySetterException("Conversion to type [" + paramTypes[0] +
+ "] failed. Reason: " + t);
+ }
+ if (arg == null) {
+ throw new PropertySetterException(
+ "Conversion to type [" + paramTypes[0] + "] failed.");
+ }
+ LOGGER.debug("Setting property [" + name + "] to [" + arg + "].");
+ try {
+ setter.invoke(obj, arg);
+ } catch (InvocationTargetException ex) {
+ if (ex.getTargetException() instanceof InterruptedException
+ || ex.getTargetException() instanceof InterruptedIOException) {
+ Thread.currentThread().interrupt();
+ }
+ throw new PropertySetterException(ex);
+ } catch (IllegalAccessException | RuntimeException ex) {
+ throw new PropertySetterException(ex);
+ }
}
+
/**
- * Set the properties of an object passed as a parameter in one
- * go. The <code>properties</code> are parsed relative to a
- * <code>prefix</code>.
- *
- * @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 <code>val</code> a String parameter to an object of a
+ * given type.
+ * @param val The value to convert.
+ * @param type The type of the value to convert to.
+ * @return The result of the conversion.
*/
- public static void setProperties(final Object obj, final Properties properties, final String prefix) {
+ protected Object convertArg(String val, Class<?> type) {
+ if (val == null) {
+ return null;
+ }
+
+ String v = val.trim();
+ if (String.class.isAssignableFrom(type)) {
+ return val;
+ } else if (Integer.TYPE.isAssignableFrom(type)) {
+ return Integer.parseInt(v);
+ } else if (Long.TYPE.isAssignableFrom(type)) {
+ return Long.parseLong(v);
+ } else if (Boolean.TYPE.isAssignableFrom(type)) {
+ if ("true".equalsIgnoreCase(v)) {
+ return Boolean.TRUE;
+ } else if ("false".equalsIgnoreCase(v)) {
+ return Boolean.FALSE;
+ }
+ } else if (Priority.class.isAssignableFrom(type)) {
+ return org.apache.log4j.helpers.OptionConverter.toLevel(v, Level.DEBUG);
+ } 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/helpers/AppenderAttachableImpl.java b/log4j-1.2-api/src/main/java/org/apache/log4j/helpers/AppenderAttachableImpl.java
new file mode 100644
index 0000000..4d6aecd
--- /dev/null
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/helpers/AppenderAttachableImpl.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.log4j.helpers;
+
+import org.apache.log4j.Appender;
+import org.apache.log4j.spi.AppenderAttachable;
+import org.apache.log4j.spi.LoggingEvent;
+
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+/**
+ * Allows Classes to attach Appenders.
+ */
+public class AppenderAttachableImpl implements AppenderAttachable {
+
+ private final ConcurrentMap<String, Appender> appenders = new ConcurrentHashMap<>();
+
+ @Override
+ public void addAppender(Appender newAppender) {
+ if (newAppender != null) {
+ appenders.put(newAppender.getName(), newAppender);
+ }
+ }
+
+ @Override
+ public Enumeration getAllAppenders() {
+ return Collections.enumeration(appenders.values());
+ }
+
+ @Override
+ public Appender getAppender(String name) {
+ return appenders.get(name);
+ }
+
+ @Override
+ public boolean isAttached(Appender appender) {
+ return appenders.containsValue(appender);
+ }
+
+ @Override
+ public void removeAllAppenders() {
+ appenders.clear();
+ }
+
+ @Override
+ public void removeAppender(Appender appender) {
+ appenders.remove(appender.getName(), appender);
+ }
+
+ @Override
+ public void removeAppender(String name) {
+ appenders.remove(name);
+ }
+
+ /**
+ * Call the <code>doAppend</code> method on all attached appenders.
+ * @param event The event to log.
+ * @return The number of appenders.
+ */
+ public int appendLoopOnAppenders(LoggingEvent event) {
+ for (Appender appender : appenders.values()) {
+ appender.doAppend(event);
+ }
+ return appenders.size();
+ }
+
+ public void close() {
+ for (Appender appender : appenders.values()) {
+ appender.close();
+ }
+ }
+}
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 0000000..b74a341
--- /dev/null
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/helpers/OptionConverter.java
@@ -0,0 +1,426 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.helpers;
+
+import org.apache.log4j.Level;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.spi.StandardLevel;
+import org.apache.logging.log4j.util.LoaderUtil;
+
+import java.io.InterruptedIOException;
+import java.lang.reflect.InvocationTargetException;
+import java.util.Properties;
+
+/**
+ * A convenience class to convert property values to specific types.
+ */
+public class OptionConverter {
+
+ 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 = LogManager.getLogger(OptionConverter.class);
+ 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('\\', '\\')
+ };
+
+ /**
+ * OptionConverter is a static class.
+ */
+ private OptionConverter() {
+ }
+
+ public static String[] concatanateArrays(String[] l, String[] r) {
+ int len = l.length + r.length;
+ String[] a = new String[len];
+
+ System.arraycopy(l, 0, a, 0, l.length);
+ System.arraycopy(r, 0, a, l.length, r.length);
+
+ return a;
+ }
+
+ public static String convertSpecialChars(String s) {
+ char c;
+ int len = s.length();
+ StringBuilder sbuf = new StringBuilder(len);
+
+ int i = 0;
+ while (i < len) {
+ c = s.charAt(i++);
+ if (c == '\\') {
+ c = s.charAt(i++);
+ for (CharMap entry : charMap) {
+ if (entry.key == c) {
+ c = entry.replacement;
+ }
+ }
+ }
+ sbuf.append(c);
+ }
+ return sbuf.toString();
+ }
+
+
+ /**
+ * Very similar to <code>System.getProperty</code> 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(String key, String def) {
+ try {
+ return System.getProperty(key, def);
+ } catch (Throwable e) { // MS-Java throws com.ms.security.SecurityExceptionEx
+ LOGGER.debug("Was not allowed to read system property \"{}\".", key);
+ return def;
+ }
+ }
+
+ /**
+ * If <code>value</code> is "true", then <code>true</code> is
+ * returned. If <code>value</code> is "false", then
+ * <code>true</code> is returned. Otherwise, <code>default</code> is
+ * returned.
+ *
+ * <p>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(String value, boolean dEfault) {
+ if (value == null) {
+ return dEfault;
+ }
+ String trimmedVal = value.trim();
+ if ("true".equalsIgnoreCase(trimmedVal)) {
+ return true;
+ }
+ if ("false".equalsIgnoreCase(trimmedVal)) {
+ return false;
+ }
+ return dEfault;
+ }
+
+ /**
+ * Converts a standard or custom priority level to a Level
+ * object. <p> If <code>value</code> 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.
+ *
+ * <p>As a special case, if the <code>value</code> parameter is
+ * equal to the string "NULL", then the value <code>null</code> will
+ * be returned.
+ *
+ * <p> If any error occurs while converting the value to a level,
+ * the <code>defaultValue</code> parameter, which may be
+ * <code>null</code>, is returned.
+ *
+ * <p> Case of <code>value</code> is insignificant for the level 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, Level defaultValue) {
+ if (value == null) {
+ return defaultValue;
+ }
+
+ value = value.trim();
+
+ int hashIndex = value.indexOf('#');
+ if (hashIndex == -1) {
+ if ("NULL".equalsIgnoreCase(value)) {
+ return null;
+ } else {
+ // no class name specified : use standard Level class
+ return Level.toLevel(value, defaultValue);
+ }
+ }
+
+ Level result = defaultValue;
+
+ String clazz = value.substring(hashIndex + 1);
+ String levelName = value.substring(0, hashIndex);
+
+ // This is degenerate case but you never know.
+ if ("NULL".equalsIgnoreCase(levelName)) {
+ return null;
+ }
+
+ LOGGER.debug("toLevel" + ":class=[" + clazz + "]"
+ + ":pri=[" + levelName + "]");
+
+ try {
+ Class customLevel = LoaderUtil.loadClass(clazz);
+
+ // get a ref to the specified class' static method
+ // toLevel(String, org.apache.log4j.Level)
+ Class[] paramTypes = new Class[]{String.class,
+ org.apache.log4j.Level.class
+ };
+ java.lang.reflect.Method toLevelMethod =
+ customLevel.getMethod("toLevel", paramTypes);
+
+ // now call the toLevel method, passing level string + default
+ Object[] params = new Object[]{levelName, defaultValue};
+ Object o = toLevelMethod.invoke(null, params);
+
+ result = (Level) o;
+ } catch (ClassNotFoundException e) {
+ LOGGER.warn("custom level class [" + clazz + "] not found.");
+ } catch (NoSuchMethodException e) {
+ LOGGER.warn("custom level class [" + clazz + "]"
+ + " does not have a class function toLevel(String, Level)", e);
+ } catch (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 (ClassCastException e) {
+ LOGGER.warn("class [" + clazz
+ + "] is not a subclass of org.apache.log4j.Level", e);
+ } catch (IllegalAccessException e) {
+ LOGGER.warn("class [" + clazz +
+ "] cannot be instantiated due to access restrictions", e);
+ } catch (RuntimeException e) {
+ LOGGER.warn("class [" + clazz + "], level [" + levelName +
+ "] conversion failed.", e);
+ }
+ return result;
+ }
+
+ /**
+ * Instantiate an object given a class name. Check that the
+ * <code>className</code> is a subclass of
+ * <code>superClass</code>. If that test fails or the object could
+ * not be instantiated, then <code>defaultValue</code> 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(String className, Class<?> superClass,
+ Object defaultValue) {
+ if (className != null) {
+ try {
+ 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 (ClassNotFoundException | NoSuchMethodException | IllegalAccessException
+ | InstantiationException | InvocationTargetException e) {
+ LOGGER.error("Could not instantiate class [" + className + "].", e);
+ }
+ }
+ return defaultValue;
+ }
+
+
+ /**
+ * Perform variable substitution in string <code>val</code> from the
+ * values of keys found in the system propeties.
+ *
+ * <p>The variable substitution delimeters are <b>${</b> and <b>}</b>.
+ *
+ * <p>For example, if the System properties contains "key=value", then
+ * the call
+ * <pre>
+ * String s = OptionConverter.substituteVars("Value of key is ${key}.");
+ * </pre>
+ * <p>
+ * will set the variable <code>s</code> to "Value of key is value.".
+ *
+ * <p>If no value could be found for the specified key, then the
+ * <code>props</code> parameter is searched, if the value could not
+ * be found there, then substitution defaults to the empty string.
+ *
+ * <p>For example, if system propeties contains no value for the key
+ * "inexistentKey", then the call
+ *
+ * <pre>
+ * String s = OptionConverter.subsVars("Value of inexistentKey is [${inexistentKey}]");
+ * </pre>
+ * will set <code>s</code> to "Value of inexistentKey is []"
+ *
+ * <p>An {@link IllegalArgumentException} is thrown if
+ * <code>val</code> contains a start delimeter "${" which is not
+ * balanced by a stop delimeter "}". </p>
+ *
+ * <p><b>Author</b> Avy Sharell</p>
+ *
+ * @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 <code>val</code> is malformed.
+ */
+ public static String substVars(String val, Properties props) throws IllegalArgumentException {
+
+ StringBuilder sbuf = new StringBuilder();
+
+ int i = 0;
+ int j, 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;
+ } else { // add the tail string which contails no variables and return the result.
+ sbuf.append(val.substring(i, val.length()));
+ return sbuf.toString();
+ }
+ } else {
+ sbuf.append(val.substring(i, j));
+ k = val.indexOf(DELIM_STOP, j);
+ if (k == -1) {
+ throw new IllegalArgumentException('"' + val +
+ "\" has no closing brace. Opening brace at position " + j
+ + '.');
+ } else {
+ j += DELIM_START_LEN;
+ String key = val.substring(j, k);
+ // first try in System properties
+ String replacement = getSystemProperty(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}
+ String recursiveReplacement = substVars(replacement, props);
+ sbuf.append(recursiveReplacement);
+ }
+ i = k + DELIM_STOP_LEN;
+ }
+ }
+ }
+ }
+
+ public static org.apache.logging.log4j.Level convertLevel(String level,
+ org.apache.logging.log4j.Level defaultLevel) {
+ Level l = toLevel(level, null);
+ return l != null ? convertLevel(l) : defaultLevel;
+ }
+
+ public static org.apache.logging.log4j.Level convertLevel(Level level) {
+ if (level == null) {
+ return org.apache.logging.log4j.Level.ERROR;
+ }
+ if (level.isGreaterOrEqual(Level.FATAL)) {
+ return org.apache.logging.log4j.Level.FATAL;
+ } else if (level.isGreaterOrEqual(Level.ERROR)) {
+ return org.apache.logging.log4j.Level.ERROR;
+ } else if (level.isGreaterOrEqual(Level.WARN)) {
+ return org.apache.logging.log4j.Level.WARN;
+ } else if (level.isGreaterOrEqual(Level.INFO)) {
+ return org.apache.logging.log4j.Level.INFO;
+ } else if (level.isGreaterOrEqual(Level.DEBUG)) {
+ return org.apache.logging.log4j.Level.DEBUG;
+ } else if (level.isGreaterOrEqual(Level.TRACE)) {
+ return org.apache.logging.log4j.Level.TRACE;
+ }
+ return org.apache.logging.log4j.Level.ALL;
+ }
+
+ public static Level convertLevel(org.apache.logging.log4j.Level level) {
+ if (level == null) {
+ return Level.ERROR;
+ }
+ switch (level.getStandardLevel()) {
+ case FATAL:
+ return Level.FATAL;
+ case WARN:
+ return Level.WARN;
+ case INFO:
+ return Level.INFO;
+ case DEBUG:
+ return Level.DEBUG;
+ case TRACE:
+ return Level.TRACE;
+ case ALL:
+ return Level.ALL;
+ case OFF:
+ return Level.OFF;
+ default:
+ return Level.ERROR;
+ }
+ }
+
+ /**
+ * Find the value corresponding to <code>key</code> in
+ * <code>props</code>. 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(String key, Properties props) {
+ String value = props.getProperty(key);
+ if (value == null) {
+ return null;
+ }
+
+ try {
+ return substVars(value, props);
+ } catch (IllegalArgumentException e) {
+ LOGGER.error("Bad option value [{}].", value, e);
+ return value;
+ }
+ }
+
+ private static class CharMap {
+ final char key;
+ final char replacement;
+
+ public CharMap(char key, char replacement) {
+ this.key = key;
+ this.replacement = replacement;
+ }
+ }
+}
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 0000000..1779019
--- /dev/null
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/helpers/QuietWriter.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.log4j.helpers;
+
+import org.apache.log4j.spi.ErrorCode;
+import org.apache.log4j.spi.ErrorHandler;
+
+import java.io.FilterWriter;
+import java.io.Writer;
+
+
+/**
+ * QuietWriter does not throw exceptions when things go
+ * wrong. Instead, it delegates error handling to its {@link ErrorHandler}.
+ */
+public class QuietWriter extends FilterWriter {
+
+ protected ErrorHandler errorHandler;
+
+ public QuietWriter(Writer writer, ErrorHandler errorHandler) {
+ super(writer);
+ setErrorHandler(errorHandler);
+ }
+
+ public void write(String string) {
+ if (string != null) {
+ try {
+ out.write(string);
+ } catch (Exception e) {
+ errorHandler.error("Failed to write [" + string + "].", e,
+ ErrorCode.WRITE_FAILURE);
+ }
+ }
+ }
+
+ public void flush() {
+ try {
+ out.flush();
+ } catch (Exception e) {
+ errorHandler.error("Failed to flush writer,", e,
+ ErrorCode.FLUSH_FAILURE);
+ }
+ }
+
+
+ public void setErrorHandler(ErrorHandler eh) {
+ if (eh == null) {
+ // This is a programming error on the part of the enclosing appender.
+ throw new IllegalArgumentException("Attempted to set null ErrorHandler.");
+ } else {
+ this.errorHandler = eh;
+ }
+ }
+}
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 9522b9e..899ebb6 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
@@ -23,13 +23,13 @@
import org.apache.logging.log4j.core.Layout;
import org.apache.logging.log4j.core.LogEvent;
-import org.apache.logging.log4j.core.config.Node;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
-import org.apache.logging.log4j.core.config.plugins.PluginFactory;
import org.apache.logging.log4j.core.layout.AbstractStringLayout;
import org.apache.logging.log4j.core.layout.ByteBufferDestination;
import org.apache.logging.log4j.core.util.Transform;
+import org.apache.logging.log4j.plugins.Node;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginAttribute;
+import org.apache.logging.log4j.plugins.PluginFactory;
import org.apache.logging.log4j.util.BiConsumer;
import org.apache.logging.log4j.util.ReadOnlyStringMap;
import org.apache.logging.log4j.util.Strings;
@@ -48,8 +48,8 @@
@PluginFactory
public static Log4j1XmlLayout createLayout(
// @formatter:off
- @PluginAttribute(value = "locationInfo") final boolean locationInfo,
- @PluginAttribute(value = "properties") final boolean properties
+ @PluginAttribute final boolean locationInfo,
+ @PluginAttribute final boolean properties
// @formatter:on
) {
return new Log4j1XmlLayout(locationInfo, properties);
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 0000000..f9e9f7c
--- /dev/null
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/legacy/core/CategoryUtil.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.legacy.core;
+
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.spi.LoggerContext;
+
+/**
+ * Provide access to Log4j Core Logger methods.
+ */
+public final class CategoryUtil {
+
+ private CategoryUtil() {
+ }
+
+ public static boolean isAdditive(Logger logger) {
+ if (logger instanceof org.apache.logging.log4j.core.Logger) {
+ return ((org.apache.logging.log4j.core.Logger) logger).isAdditive();
+ }
+ return false;
+ }
+
+ public static void setAdditivity(Logger logger, boolean additivity) {
+ if (logger instanceof org.apache.logging.log4j.core.Logger) {
+ ((org.apache.logging.log4j.core.Logger) logger).setAdditive(additivity);
+ }
+ }
+
+ public static Logger getParent(Logger logger) {
+ if (logger instanceof org.apache.logging.log4j.core.Logger) {
+ return ((org.apache.logging.log4j.core.Logger) logger).getParent();
+
+ }
+ return null;
+ }
+
+ public static LoggerContext getLoggerContext(Logger logger) {
+ if (logger instanceof org.apache.logging.log4j.core.Logger) {
+ return ((org.apache.logging.log4j.core.Logger) logger).getContext();
+ }
+ return null;
+ }
+
+ public static void setLevel(Logger logger, Level level) {
+ if (logger instanceof org.apache.logging.log4j.core.Logger) {
+ ((org.apache.logging.log4j.core.Logger) logger).setLevel(level);
+
+ }
+ }
+}
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 0000000..d3b99fa
--- /dev/null
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/legacy/core/ContextUtil.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.legacy.core;
+
+import org.apache.logging.log4j.spi.LoggerContext;
+
+/**
+ * Implements LoggerContext methods specific to log4j-core.
+ */
+public final class ContextUtil {
+
+ private ContextUtil() {
+ }
+
+ public static void reconfigure(LoggerContext ctx) {
+ if (ctx instanceof org.apache.logging.log4j.core.LoggerContext) {
+ ((org.apache.logging.log4j.core.LoggerContext) ctx).reconfigure();
+ }
+ }
+}
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 0000000..4e627c2
--- /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/RendererSupport.java b/log4j-1.2-api/src/main/java/org/apache/log4j/or/RendererSupport.java
new file mode 100644
index 0000000..9b8728d
--- /dev/null
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/or/RendererSupport.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.or;
+
+import java.util.Map;
+
+/**
+ * Interface that indicates the Renderer Map is available. This interface differs
+ */
+public interface RendererSupport {
+ Map<Class<?>, ObjectRenderer> getRendererMap();
+}
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 0000000..08233bf
--- /dev/null
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/or/ThreadGroupRenderer.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.log4j.or;
+
+import org.apache.log4j.Layout;
+
+/**
+ */
+public class ThreadGroupRenderer implements ObjectRenderer {
+
+ public
+ String doRender(Object obj) {
+ if(obj instanceof ThreadGroup) {
+ StringBuilder sb = new StringBuilder();
+ ThreadGroup threadGroup = (ThreadGroup) obj;
+ sb.append("java.lang.ThreadGroup[name=");
+ sb.append(threadGroup.getName());
+ sb.append(", maxpri=");
+ sb.append(threadGroup.getMaxPriority());
+ sb.append("]");
+ Thread[] threads = new Thread[threadGroup.activeCount()];
+ threadGroup.enumerate(threads);
+ for (Thread thread : threads) {
+ sb.append(Layout.LINE_SEP);
+ sb.append(" Thread=[");
+ sb.append(thread.getName());
+ sb.append(",");
+ sb.append(thread.getPriority());
+ sb.append(",");
+ sb.append(thread.isDaemon());
+ sb.append("]");
+ }
+ return sb.toString();
+ } else {
+ 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/pattern/Log4j1MdcPatternConverter.java b/log4j-1.2-api/src/main/java/org/apache/log4j/pattern/Log4j1MdcPatternConverter.java
index b4ae0c5..09e06a6 100644
--- a/log4j-1.2-api/src/main/java/org/apache/log4j/pattern/Log4j1MdcPatternConverter.java
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/pattern/Log4j1MdcPatternConverter.java
@@ -17,7 +17,7 @@
package org.apache.log4j.pattern;
import org.apache.logging.log4j.core.LogEvent;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.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;
@@ -79,7 +79,7 @@
}
}
- private static TriConsumer<String, Object, StringBuilder> APPEND_EACH = new TriConsumer<String, Object, StringBuilder>() {
+ private static final TriConsumer<String, Object, StringBuilder> APPEND_EACH = new TriConsumer<String, Object, StringBuilder>() {
@Override
public void accept(final String key, final Object value, final StringBuilder 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 405db00..7301799 100644
--- a/log4j-1.2-api/src/main/java/org/apache/log4j/pattern/Log4j1NdcPatternConverter.java
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/pattern/Log4j1NdcPatternConverter.java
@@ -17,7 +17,7 @@
package org.apache.log4j.pattern;
import org.apache.logging.log4j.core.LogEvent;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.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;
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 0000000..4f53583
--- /dev/null
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/rewrite/MapRewritePolicy.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.rewrite;
+
+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;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * 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.
+ * <p>
+ * 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}
+ */
+ public LoggingEvent rewrite(final LoggingEvent source) {
+ Object msg = source.getMessage();
+ if (msg instanceof MapMessage || msg instanceof Map) {
+ Map<String, String> props = source.getProperties() != null ? new HashMap<>(source.getProperties())
+ : new HashMap<>();
+ @SuppressWarnings("unchecked")
+ Map<String, Object> eventProps = msg instanceof Map ? (Map) msg : ((MapMessage) msg).getData();
+ //
+ // if the map sent in the logging request
+ // has "message" entry, use that as the message body
+ // otherwise, use the entire map.
+ //
+ Message newMessage = null;
+ Object newMsg = eventProps.get("message");
+ if (newMsg != null) {
+ newMessage = new SimpleMessage(newMsg.toString());
+ for (Map.Entry<String, Object> entry : eventProps.entrySet()) {
+ if (!("message".equals(entry.getKey()))) {
+ props.put(entry.getKey(), entry.getValue().toString());
+ }
+ }
+ } else {
+ return source;
+ }
+
+ LogEvent event;
+ if (source instanceof LogEventAdapter) {
+ event = new Log4jLogEvent.Builder(((LogEventAdapter) source).getEvent())
+ .setMessage(newMessage)
+ .setContextData(new SortedArrayStringMap(props))
+ .build();
+ } else {
+ LocationInfo info = source.getLocationInformation();
+ StackTraceElement element = new StackTraceElement(info.getClassName(), info.getMethodName(),
+ info.getFileName(), Integer.parseInt(info.getLineNumber()));
+ Thread thread = getThread(source.getThreadName());
+ long threadId = thread != null ? thread.getId() : 0;
+ int threadPriority = thread != null ? thread.getPriority() : 0;
+ event = Log4jLogEvent.newBuilder()
+ .setContextData(new SortedArrayStringMap(props))
+ .setLevel(OptionConverter.convertLevel(source.getLevel()))
+ .setLoggerFqcn(source.getFQNOfLoggerClass())
+ .setMarker(null)
+ .setMessage(newMessage)
+ .setSource(element)
+ .setLoggerName(source.getLoggerName())
+ .setThreadName(source.getThreadName())
+ .setThreadId(threadId)
+ .setThreadPriority(threadPriority)
+ .setThrown(source.getThrowableInformation().getThrowable())
+ .setTimeMillis(source.getTimeStamp())
+ .setNanoTime(0)
+ .setThrownProxy(null)
+ .build();
+ }
+ return new LogEventAdapter(event);
+ } else {
+ return source;
+ }
+
+ }
+
+ private Thread getThread(String name) {
+ for (Thread thread : Thread.getAllStackTraces().keySet()) {
+ if (thread.getName().equals(name)) {
+ return thread;
+ }
+ }
+ return null;
+ }
+}
diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/rewrite/PropertyRewritePolicy.java b/log4j-1.2-api/src/main/java/org/apache/log4j/rewrite/PropertyRewritePolicy.java
new file mode 100644
index 0000000..d320d89
--- /dev/null
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/rewrite/PropertyRewritePolicy.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.log4j.rewrite;
+
+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;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.StringTokenizer;
+
+/**
+ * This policy rewrites events by adding
+ * a user-specified list of properties to the event.
+ * Existing properties are not modified.
+ * <p>
+ * 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<String, String> properties = Collections.EMPTY_MAP;
+
+ public PropertyRewritePolicy() {
+ }
+
+ /**
+ * Set a string representing the property name/value pairs.
+ * <p>
+ * Form: propname1=propvalue1,propname2=propvalue2
+ *
+ * @param props The properties.
+ */
+ public void setProperties(String props) {
+ Map hashTable = new HashMap();
+ StringTokenizer pairs = new StringTokenizer(props, ",");
+ while (pairs.hasMoreTokens()) {
+ StringTokenizer entry = new StringTokenizer(pairs.nextToken(), "=");
+ hashTable.put(entry.nextElement().toString().trim(), entry.nextElement().toString().trim());
+ }
+ synchronized (this) {
+ properties = hashTable;
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public LoggingEvent rewrite(final LoggingEvent source) {
+ if (!properties.isEmpty()) {
+ Map<String, String> rewriteProps = source.getProperties() != null ? new HashMap<>(source.getProperties())
+ : new HashMap<>();
+ for (Map.Entry<String, String> entry : properties.entrySet()) {
+ if (!rewriteProps.containsKey(entry.getKey())) {
+ rewriteProps.put(entry.getKey(), entry.getValue());
+ }
+ }
+ LogEvent event;
+ if (source instanceof LogEventAdapter) {
+ event = new Log4jLogEvent.Builder(((LogEventAdapter) source).getEvent())
+ .setContextData(new SortedArrayStringMap(rewriteProps))
+ .build();
+ } else {
+ LocationInfo info = source.getLocationInformation();
+ StackTraceElement element = new StackTraceElement(info.getClassName(), info.getMethodName(),
+ info.getFileName(), Integer.parseInt(info.getLineNumber()));
+ Thread thread = getThread(source.getThreadName());
+ long threadId = thread != null ? thread.getId() : 0;
+ int threadPriority = thread != null ? thread.getPriority() : 0;
+ event = Log4jLogEvent.newBuilder()
+ .setContextData(new SortedArrayStringMap(rewriteProps))
+ .setLevel(OptionConverter.convertLevel(source.getLevel()))
+ .setLoggerFqcn(source.getFQNOfLoggerClass())
+ .setMarker(null)
+ .setMessage(new SimpleMessage(source.getRenderedMessage()))
+ .setSource(element)
+ .setLoggerName(source.getLoggerName())
+ .setThreadName(source.getThreadName())
+ .setThreadId(threadId)
+ .setThreadPriority(threadPriority)
+ .setThrown(source.getThrowableInformation().getThrowable())
+ .setTimeMillis(source.getTimeStamp())
+ .setNanoTime(0)
+ .setThrownProxy(null)
+ .build();
+ }
+ return new LogEventAdapter(event);
+ }
+ return source;
+ }
+
+ private Thread getThread(String name) {
+ for (Thread thread : Thread.getAllStackTraces().keySet()) {
+ if (thread.getName().equals(name)) {
+ return thread;
+ }
+ }
+ return null;
+ }
+}
diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/rewrite/RewritePolicy.java b/log4j-1.2-api/src/main/java/org/apache/log4j/rewrite/RewritePolicy.java
new file mode 100644
index 0000000..9570218
--- /dev/null
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/rewrite/RewritePolicy.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.log4j.rewrite;
+
+import org.apache.log4j.spi.LoggingEvent;
+
+/**
+ * This interface is implemented to provide a rewrite
+ * strategy for RewriteAppender. RewriteAppender will
+ * call the rewrite method with a source logging event.
+ * The strategy may return that event, create a new event
+ * or return null to suppress the logging request.
+ */
+public interface RewritePolicy {
+ /**
+ * Rewrite a logging event.
+ * @param source a logging event that may be returned or
+ * used to create a new logging event.
+ * @return a logging event or null to suppress processing.
+ */
+ LoggingEvent rewrite(final LoggingEvent source);
+}
diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/spi/AppenderAttachable.java b/log4j-1.2-api/src/main/java/org/apache/log4j/spi/AppenderAttachable.java
new file mode 100644
index 0000000..120cb50
--- /dev/null
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/spi/AppenderAttachable.java
@@ -0,0 +1,78 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.spi;
+
+import org.apache.log4j.Appender;
+
+import java.util.Enumeration;
+
+/**
+ * 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<Appender> getAllAppenders();
+
+ /**
+ * Get an appender by name.
+ * @param name The name of the Appender.
+ * @return The Appender.
+ */
+ Appender getAppender(String name);
+
+
+ /**
+ * Returns <code>true</code> if the specified appender is in list of
+ * attached attached, <code>false</code> 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 0000000..55d19da
--- /dev/null
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/spi/Configurator.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.spi;
+
+import java.io.InputStream;
+import java.net.URL;
+
+import org.apache.logging.log4j.core.LoggerContext;
+
+/**
+ * Log4j 1.x Configurator interface.
+ */
+public interface Configurator {
+
+ public static final String INHERITED = "inherited";
+
+ 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 <code>hierarchy</code>
+ parameter.
+
+ @param inputStream The InputStream to parse.
+ @param loggerContext The LoggerContext.
+
+ @since 1.2.17
+ */
+ void doConfigure(InputStream inputStream, final LoggerContext loggerContext);
+
+ /**
+ Interpret a resource pointed by a URL and set up log4j accordingly.
+
+ The configuration is done relative to the <code>hierarchy</code>
+ parameter.
+
+ @param url The URL to parse
+ @param loggerContext The LoggerContext.
+ */
+ void doConfigure(URL url, final LoggerContext loggerContext);
+}
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 0000000..7fbbf95
--- /dev/null
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/spi/ErrorCode.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.spi;
+
+
+/**
+ This interface defines commonly encoutered 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/Filter.java b/log4j-1.2-api/src/main/java/org/apache/log4j/spi/Filter.java
index 9bba50c..997398b 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
@@ -16,10 +16,24 @@
*/
package org.apache.log4j.spi;
+import org.apache.log4j.bridge.FilterAdapter;
+
/**
* @since 0.9.0
*/
public abstract class Filter {
+ private final FilterAdapter adapter;
+
+ public Filter() {
+ FilterAdapter filterAdapter = null;
+ try {
+ Class.forName("org.apache.logging.log4j.core.Filter");
+ filterAdapter = new FilterAdapter(this);
+ } catch(ClassNotFoundException ex) {
+ // Ignore the exception. Log4j Core is not present.
+ }
+ this.adapter = filterAdapter;
+ }
/**
* The log event must be dropped immediately without consulting
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 0000000..df49fa9
--- /dev/null
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/spi/LocationInfo.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;
+
+/**
+ The internal representation of caller location information.
+
+ @since 0.8.3
+ */
+public class LocationInfo implements java.io.Serializable {
+
+ private final StackTraceElement element;
+
+ public String fullInfo;
+
+ /**
+ * Constructor for LocationInfo.
+ * @param element The StackTraceElement representing the caller.
+ */
+ public LocationInfo(StackTraceElement element) {
+ this.element = element;
+ }
+
+ /**
+ When location information is not available the constant
+ <code>NA</code> is returned. Current value of this string
+ constant is <b>?</b>. */
+ public final static String NA = "?";
+
+ static final long serialVersionUID = -1325822038990805636L;
+
+
+ /**
+ Return the fully qualified class name of the caller making the
+ logging request.
+ @return The class name.
+ */
+ public String getClassName() {
+ return element.getClassName();
+ }
+
+ /**
+ Return the file name of the caller.
+ @return the file name.
+ */
+ public String getFileName() {
+ return element.getFileName();
+ }
+
+ /**
+ Returns the line number of the caller.
+ @return The line number.
+ */
+ public String getLineNumber() {
+ return Integer.toString(element.getLineNumber());
+ }
+
+ /**
+ Returns the method name of the caller.
+ @return The method name.
+ */
+ public String getMethodName() {
+ return element.getMethodName();
+ }
+}
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 5f4b172..8d26d26 100644
--- a/log4j-1.2-api/src/main/java/org/apache/log4j/spi/LoggingEvent.java
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/spi/LoggingEvent.java
@@ -16,8 +16,150 @@
*/
package org.apache.log4j.spi;
+import org.apache.log4j.Category;
+import org.apache.log4j.Level;
+import org.apache.log4j.bridge.LogEventAdapter;
+
+import java.util.Map;
+import java.util.Set;
+
/**
- * 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 {
+
+ /**
+ 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;
+ }
+
+ /**
+ * Return the level of this event. Use this form instead of directly
+ * accessing the <code>level</code> field.
+ * @return Always returns null.
+ */
+ public Level getLevel() {
+ return null;
+ }
+
+ /**
+ * Return the name of the logger. Use this form instead of directly
+ * accessing the <code>categoryName</code> field.
+ * @return Always returns null.
+ */
+ public String getLoggerName() {
+ return null;
+ }
+
+ public String getFQNOfLoggerClass() {
+ return null;
+ }
+
+ public final long getTimeStamp() {
+ return 0;
+ }
+
+ /**
+ * 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 message for this logging event.
+
+ <p>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
+ Object getMDC(String key) {
+ return null;
+ }
+
+ /**
+ Obtain a copy of this thread's MDC prior to serialization or
+ asynchronous logging.
+ */
+ public void getMDCCopy() {
+ }
+
+ public String getRenderedMessage() {
+ return null;
+ }
+
+ /**
+ Returns the time when the application started, in milliseconds
+ elapsed since 01.01.1970.
+ @return the JVM start time.
+ */
+ public static long getStartTime() {
+ return LogEventAdapter.getStartTime();
+ }
+
+ public String getThreadName() {
+ return null;
+ }
+
+ /**
+ Returns the throwable information contained within this
+ event. May be <code>null</code> if there is no such information.
+
+ <p>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[] representaion.
+ @return Always returns null.
+ */
+ public String[] getThrowableStrRep() {
+ return null;
+ }
+
+ public void setProperty(final String propName,
+ final String propValue) {
+
+ }
+
+ public String getProperty(final String key) {
+ return null;
+ }
+
+ public Set getPropertyKeySet() {
+ return null;
+ }
+
+ public Map getProperties() {
+ return null;
+ }
+
+ public Object removeProperty(String propName) {
+ return null;
+ }
}
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 0000000..e9c9776
--- /dev/null
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/spi/ThrowableInformation.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.log4j.spi;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.List;
+
+import org.apache.logging.log4j.core.util.Throwables;
+
+/**
+ * Class Description goes here.
+ */
+public class ThrowableInformation implements java.io.Serializable {
+
+ static final long serialVersionUID = -4748765566864322735L;
+
+ private transient Throwable throwable;
+ private final Method toStringList;
+
+ @SuppressWarnings("unchecked")
+ public
+ ThrowableInformation(Throwable throwable) {
+ this.throwable = throwable;
+ Method method = null;
+ try {
+ 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.
+ }
+ this.toStringList = method;
+ }
+
+ public
+ Throwable getThrowable() {
+ return throwable;
+ }
+
+ public synchronized String[] getThrowableStrRep() {
+ if (toStringList != null && throwable != null) {
+ try {
+ @SuppressWarnings("unchecked")
+ List<String> elements = (List<String>) toStringList.invoke(null, throwable);
+ if (elements != null) {
+ return elements.toArray(new String[0]);
+ }
+ } catch (IllegalAccessException | InvocationTargetException ex) {
+ // Ignore the exception.
+ }
+ }
+ return null;
+ }
+}
+
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 0000000..edda022
--- /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 org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.status.StatusLogger;
+import org.xml.sax.EntityResolver;
+import org.xml.sax.InputSource;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+
+/**
+ * An {@link EntityResolver} specifically designed to return
+ * <code>log4j.dtd</code> 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";
+
+ public InputSource resolveEntity(String publicId, String systemId) {
+ if (systemId.endsWith("log4j.dtd") || PUBLIC_ID.equals(publicId)) {
+ Class clazz = getClass();
+ InputStream in = clazz.getResourceAsStream("/org/apache/log4j/xml/log4j.dtd");
+ if (in == null) {
+ LOGGER.warn("Could not find [log4j.dtd] using [{}] class loader, parsed without DTD.",
+ clazz.getClassLoader());
+ in = new ByteArrayInputStream(new byte[0]);
+ }
+ return new InputSource(in);
+ } else {
+ 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 0000000..463d5d9
--- /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 org.w3c.dom.Element;
+import java.util.Properties;
+
+/**
+ * When implemented by an object configured by DOMConfigurator,
+ * the handle method will be called when an unrecognized child
+ * element is encountered. Unrecognized child elements of
+ * the log4j:configuration element will be dispatched to
+ * the logger repository if it supports this interface.
+ *
+ * @since 1.2.15
+ */
+public interface UnrecognizedElementHandler {
+ /**
+ * Called to inform a configured object when
+ * an unrecognized child element is encountered.
+ * @param element element, may not be null.
+ * @param props properties in force, may be null.
+ * @return true if configured object recognized the element
+ * @throws Exception throw an exception to prevent activation
+ * of the configured object.
+ */
+ boolean parseUnrecognizedElement(Element element, Properties props) throws Exception;
+}
\ No newline at end of file
diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/xml/XmlConfiguration.java b/log4j-1.2-api/src/main/java/org/apache/log4j/xml/XmlConfiguration.java
new file mode 100644
index 0000000..8e256bc
--- /dev/null
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/xml/XmlConfiguration.java
@@ -0,0 +1,788 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.Appender;
+import org.apache.log4j.Layout;
+import org.apache.log4j.Level;
+import org.apache.log4j.bridge.AppenderAdapter;
+import org.apache.log4j.bridge.AppenderWrapper;
+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.LoggerContext;
+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.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;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.FactoryConfigurationError;
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.lang.reflect.Method;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+import java.util.function.Consumer;
+
+/**
+ * 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 EMPTY_STR = "";
+ private static final Class[] ONE_STRING_PARAM = new Class[]{String.class};
+ private static final String dbfKey = "javax.xml.parsers.DocumentBuilderFactory";
+ private static final String THROWABLE_RENDERER_TAG = "throwableRenderer";
+
+ public static final long DEFAULT_DELAY = 60000;
+ /**
+ * File name prefix for test configurations.
+ */
+ protected static final String TEST_PREFIX = "log4j-test";
+
+ /**
+ * File name prefix for standard configurations.
+ */
+ protected static final String DEFAULT_PREFIX = "log4j";
+
+ // key: appenderName, value: appender
+ private final Map<String, Appender> appenderMap;
+
+ private final Properties props = null;
+
+ public XmlConfiguration(final LoggerContext loggerContext, final ConfigurationSource source,
+ int monitorIntervalSeconds) {
+ super(loggerContext, source, monitorIntervalSeconds);
+ appenderMap = new HashMap<>();
+ }
+
+ public void addAppenderIfAbsent(Appender appender) {
+ appenderMap.putIfAbsent(appender.getName(), appender);
+ }
+
+ /**
+ * Configure log4j by reading in a log4j.dtd compliant XML
+ * configuration file.
+ */
+ @Override
+ public void doConfigure() throws FactoryConfigurationError {
+ ConfigurationSource source = getConfigurationSource();
+ ParseAction action = new ParseAction() {
+ public Document parse(final DocumentBuilder parser) throws SAXException, IOException {
+ InputSource inputSource = new InputSource(source.getInputStream());
+ inputSource.setSystemId("dummy://log4j.dtd");
+ return parser.parse(inputSource);
+ }
+
+ 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 succeded.");
+ LOGGER.debug("DocumentBuilderFactory is: " + dbf.getClass().getName());
+ } catch (FactoryConfigurationError fce) {
+ Exception e = fce.getException();
+ LOGGER.debug("Could not instantiate a DocumentBuilderFactory.", e);
+ throw fce;
+ }
+
+ try {
+ dbf.setValidating(true);
+
+ DocumentBuilder docBuilder = dbf.newDocumentBuilder();
+
+ docBuilder.setErrorHandler(new SAXErrorHandler());
+ docBuilder.setEntityResolver(new Log4jEntityResolver());
+
+ Document doc = action.parse(docBuilder);
+ parse(doc.getDocumentElement());
+ } catch (Exception e) {
+ if (e instanceof InterruptedException || e instanceof InterruptedIOException) {
+ Thread.currentThread().interrupt();
+ }
+ // I know this is miserable...
+ LOGGER.error("Could not parse " + action.toString() + ".", e);
+ }
+ }
+
+ /**
+ * Delegates unrecognized content to created instance if it supports UnrecognizedElementParser.
+ *
+ * @param instance instance, may be null.
+ * @param element element, may not be null.
+ * @param props properties
+ * @throws IOException thrown if configuration of owner object should be abandoned.
+ */
+ private void parseUnrecognizedElement(final Object instance, final Element element,
+ final Properties props) throws Exception {
+ boolean recognized = false;
+ if (instance instanceof UnrecognizedElementHandler) {
+ recognized = ((UnrecognizedElementHandler) instance).parseUnrecognizedElement(
+ element, props);
+ }
+ if (!recognized) {
+ LOGGER.warn("Unrecognized element {}", element.getNodeName());
+ }
+ }
+
+ /**
+ * Delegates unrecognized content to created instance if
+ * it supports UnrecognizedElementParser and catches and
+ * logs any exception.
+ *
+ * @param instance instance, may be null.
+ * @param element element, may not be null.
+ * @param props properties
+ * @since 1.2.15
+ */
+ private void quietParseUnrecognizedElement(final Object instance,
+ final Element element,
+ final Properties props) {
+ try {
+ parseUnrecognizedElement(instance, element, props);
+ } catch (Exception ex) {
+ if (ex instanceof InterruptedException || ex instanceof InterruptedIOException) {
+ Thread.currentThread().interrupt();
+ }
+ LOGGER.error("Error in extension content: ", ex);
+ }
+ }
+
+ /**
+ * Substitutes property value for any references in expression.
+ *
+ * @param value value from configuration file, may contain
+ * literal text, property references or both
+ * @param props properties.
+ * @return evaluated expression, may still contain expressions
+ * if unable to expand.
+ */
+ public String subst(final String value, final Properties props) {
+ try {
+ return OptionConverter.substVars(value, props);
+ } catch (IllegalArgumentException e) {
+ LOGGER.warn("Could not perform variable substitution.", e);
+ return value;
+ }
+ }
+
+ /**
+ * Sets a parameter based from configuration file content.
+ *
+ * @param elem param element, may not be null.
+ * @param propSetter property setter, may not be null.
+ * @param props properties
+ * @since 1.2.15
+ */
+ public void setParameter(final Element elem, final PropertySetter propSetter, final Properties props) {
+ String name = subst(elem.getAttribute("name"), props);
+ String value = (elem.getAttribute("value"));
+ value = subst(OptionConverter.convertSpecialChars(value), props);
+ propSetter.setProperty(name, value);
+ }
+
+ /**
+ * Creates an object and processes any nested param elements
+ * but does not call activateOptions. If the class also supports
+ * UnrecognizedElementParser, the parseUnrecognizedElement method
+ * will be call for any child elements other than param.
+ *
+ * @param element element, may not be null.
+ * @param props properties
+ * @param expectedClass interface or class expected to be implemented
+ * by created class
+ * @return created class or null.
+ * @throws Exception thrown if the contain object should be abandoned.
+ * @since 1.2.15
+ */
+ public Object parseElement(final Element element, final Properties props,
+ @SuppressWarnings("rawtypes") final Class expectedClass) throws Exception {
+ String clazz = subst(element.getAttribute("class"), props);
+ Object instance = OptionConverter.instantiateByClassName(clazz,
+ expectedClass, null);
+
+ if (instance != null) {
+ PropertySetter propSetter = new PropertySetter(instance);
+ NodeList children = element.getChildNodes();
+ final int length = children.getLength();
+
+ for (int loop = 0; loop < length; loop++) {
+ Node currentNode = children.item(loop);
+ if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
+ Element currentElement = (Element) currentNode;
+ String tagName = currentElement.getTagName();
+ if (tagName.equals("param")) {
+ setParameter(currentElement, propSetter, props);
+ } else {
+ parseUnrecognizedElement(instance, currentElement, props);
+ }
+ }
+ }
+ return instance;
+ }
+ return null;
+ }
+
+ /**
+ * Used internally to parse appenders by IDREF name.
+ */
+ private Appender findAppenderByName(Document doc, String appenderName) {
+ Appender appender = appenderMap.get(appenderName);
+
+ if (appender != null) {
+ return appender;
+ } else {
+ // Doesn't work on DOM Level 1 :
+ // Element element = doc.getElementById(appenderName);
+
+ // Endre's hack:
+ Element element = null;
+ NodeList list = doc.getElementsByTagName("appender");
+ for (int t = 0; t < list.getLength(); t++) {
+ Node node = list.item(t);
+ NamedNodeMap map = node.getAttributes();
+ Node attrNode = map.getNamedItem("name");
+ if (appenderName.equals(attrNode.getNodeValue())) {
+ element = (Element) node;
+ break;
+ }
+ }
+ // Hack finished.
+
+ if (element == null) {
+
+ LOGGER.error("No appender named [{}] could be found.", appenderName);
+ return null;
+ } else {
+ appender = parseAppender(element);
+ if (appender != null) {
+ appenderMap.put(appenderName, appender);
+ }
+ return appender;
+ }
+ }
+ }
+
+ /**
+ * Used internally to parse appenders by IDREF element.
+ * @param appenderRef The Appender Reference Element.
+ * @return The Appender.
+ */
+ public Appender findAppenderByReference(Element appenderRef) {
+ String appenderName = subst(appenderRef.getAttribute(REF_ATTR));
+ Document doc = appenderRef.getOwnerDocument();
+ return findAppenderByName(doc, appenderName);
+ }
+
+ /**
+ * Used internally to parse an appender element.
+ * @param appenderElement The Appender Element.
+ * @return The Appender.
+ */
+ public Appender parseAppender(Element appenderElement) {
+ String className = subst(appenderElement.getAttribute(CLASS_ATTR));
+ LOGGER.debug("Class name: [" + className + ']');
+ Appender appender = manager.parseAppender(className, appenderElement, this);
+ if (appender == null) {
+ appender = buildAppender(className, appenderElement);
+ }
+ return appender;
+ }
+
+ private Appender buildAppender(String className, Element appenderElement) {
+ try {
+ Appender appender = LoaderUtil.newInstanceOf(className);
+ PropertySetter propSetter = new PropertySetter(appender);
+
+ appender.setName(subst(appenderElement.getAttribute(NAME_ATTR)));
+ 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:
+ Filter filter = parseFilters(currentElement);
+ if (filter != null) {
+ LOGGER.debug("Adding filter of type [{}] to appender named [{}]",
+ filter.getClass(), appender.getName());
+ appender.addFilter(filter);
+ }
+ break;
+ case ERROR_HANDLER_TAG:
+ parseErrorHandler(currentElement, appender);
+ break;
+ case APPENDER_REF_TAG:
+ String refName = subst(currentElement.getAttribute(REF_ATTR));
+ if (appender instanceof AppenderAttachable) {
+ AppenderAttachable aa = (AppenderAttachable) appender;
+ Appender child = findAppenderByReference(currentElement);
+ LOGGER.debug("Attaching appender named [{}] to appender named [{}].", refName,
+ appender.getName());
+ aa.addAppender(child);
+ } else {
+ LOGGER.error("Requesting attachment of appender named [{}] to appender named [{}]"
+ + "which does not implement org.apache.log4j.spi.AppenderAttachable.",
+ refName, appender.getName());
+ }
+ break;
+ default:
+ try {
+ parseUnrecognizedElement(appender, currentElement, props);
+ } catch (Exception ex) {
+ throw new ConsumerException(ex);
+ }
+ }
+ });
+ propSetter.activate();
+ return appender;
+ } catch (ConsumerException ex) {
+ Throwable t = ex.getCause();
+ if (t instanceof InterruptedException || t instanceof InterruptedIOException) {
+ Thread.currentThread().interrupt();
+ }
+ LOGGER.error("Could not create an Appender. Reported error follows.", t);
+ } catch (Exception oops) {
+ if (oops instanceof InterruptedException || oops instanceof InterruptedIOException) {
+ Thread.currentThread().interrupt();
+ }
+ LOGGER.error("Could not create an Appender. Reported error follows.", oops);
+ }
+ return null;
+ }
+
+
+ public RewritePolicy parseRewritePolicy(Element rewritePolicyElement) {
+
+ String className = subst(rewritePolicyElement.getAttribute(CLASS_ATTR));
+ LOGGER.debug("Class name: [" + className + ']');
+ RewritePolicy policy = manager.parseRewritePolicy(className, rewritePolicyElement, this);
+ if (policy == null) {
+ policy = buildRewritePolicy(className, rewritePolicyElement);
+ }
+ return policy;
+ }
+
+ private RewritePolicy buildRewritePolicy(String className, Element element) {
+ try {
+ RewritePolicy policy = LoaderUtil.newInstanceOf(className);
+ PropertySetter propSetter = new PropertySetter(policy);
+
+ forEachElement(element.getChildNodes(), (currentElement) -> {
+ if (currentElement.getTagName().equalsIgnoreCase(PARAM_TAG)) {
+ setParameter(currentElement, propSetter);
+ }
+ });
+ propSetter.activate();
+ return policy;
+ } catch (ConsumerException ex) {
+ Throwable t = ex.getCause();
+ if (t instanceof InterruptedException || t instanceof InterruptedIOException) {
+ Thread.currentThread().interrupt();
+ }
+ LOGGER.error("Could not create an RewritePolicy. Reported error follows.", t);
+ } catch (Exception oops) {
+ if (oops instanceof InterruptedException || oops instanceof InterruptedIOException) {
+ Thread.currentThread().interrupt();
+ }
+ LOGGER.error("Could not create an RewritePolicy. Reported error follows.", oops);
+ }
+ return null;
+ }
+
+ /**
+ * Used internally to parse an {@link ErrorHandler} element.
+ */
+ private void parseErrorHandler(Element element, Appender appender) {
+ ErrorHandler eh = (ErrorHandler) OptionConverter.instantiateByClassName(
+ subst(element.getAttribute(CLASS_ATTR)),
+ ErrorHandler.class,
+ null);
+
+ if (eh != null) {
+ eh.setAppender(appender);
+
+ PropertySetter propSetter = new PropertySetter(eh);
+ forEachElement(element.getChildNodes(), (currentElement) -> {
+ String tagName = currentElement.getTagName();
+ if (tagName.equals(PARAM_TAG)) {
+ setParameter(currentElement, propSetter);
+ }
+ });
+ propSetter.activate();
+ appender.setErrorHandler(eh);
+ }
+ }
+
+ /**
+ * Used internally to parse a filter element.
+ * @param filterElement The Filter Element.
+ * @return The Filter.
+ */
+ public Filter parseFilters(Element filterElement) {
+ String className = subst(filterElement.getAttribute(CLASS_ATTR));
+ LOGGER.debug("Class name: [" + className + ']');
+ Filter filter = manager.parseFilter(className, filterElement, this);
+ if (filter == null) {
+ PropertySetter propSetter = new PropertySetter(filter);
+ forEachElement(filterElement.getChildNodes(), (currentElement) -> {
+ String tagName = currentElement.getTagName();
+ if (tagName.equals(PARAM_TAG)) {
+ setParameter(currentElement, propSetter);
+ } else {
+ quietParseUnrecognizedElement(filter, currentElement, props);
+ }
+ });
+ propSetter.activate();
+ }
+ return filter;
+ }
+
+ /**
+ * Used internally to parse an category element.
+ */
+ private void parseCategory(Element loggerElement) {
+ // Create a new org.apache.log4j.Category object from the <category> element.
+ String catName = subst(loggerElement.getAttribute(NAME_ATTR));
+ boolean additivity = OptionConverter.toBoolean(subst(loggerElement.getAttribute(ADDITIVITY_ATTR)), true);
+ LoggerConfig loggerConfig = getLogger(catName);
+ if (loggerConfig == null) {
+ loggerConfig = new LoggerConfig(catName, org.apache.logging.log4j.Level.ERROR, additivity);
+ addLogger(catName, loggerConfig);
+ } else {
+ loggerConfig.setAdditive(additivity);
+ }
+ parseChildrenOfLoggerElement(loggerElement, loggerConfig, false);
+ }
+
+ /**
+ * Used internally to parse the roor category element.
+ */
+ private void parseRoot(Element rootElement) {
+ LoggerConfig root = getRootLogger();
+ parseChildrenOfLoggerElement(rootElement, root, true);
+ }
+
+ /**
+ * Used internally to parse the children of a LoggerConfig element.
+ */
+ private void parseChildrenOfLoggerElement(Element catElement, LoggerConfig loggerConfig, boolean isRoot) {
+
+ final PropertySetter propSetter = new PropertySetter(loggerConfig);
+ loggerConfig.getAppenderRefs().clear();
+ forEachElement(catElement.getChildNodes(), (currentElement) -> {
+ switch (currentElement.getTagName()) {
+ case APPENDER_REF_TAG: {
+ Appender appender = findAppenderByReference(currentElement);
+ String refName = subst(currentElement.getAttribute(REF_ATTR));
+ if (appender != null) {
+ LOGGER.debug("Adding appender named [{}] to loggerConfig [{}].", refName,
+ loggerConfig.getName());
+ loggerConfig.addAppender(getAppender(refName), null, null);
+ } else {
+ LOGGER.debug("Appender named [{}] not found.", refName);
+ }
+ break;
+ }
+ case LEVEL_TAG: case PRIORITY_TAG: {
+ parseLevel(currentElement, loggerConfig, isRoot);
+ break;
+ }
+ case PARAM_TAG: {
+ setParameter(currentElement, propSetter);
+ break;
+ }
+ default: {
+ quietParseUnrecognizedElement(loggerConfig, currentElement, props);
+ }
+ }
+ });
+ propSetter.activate();
+ }
+
+ /**
+ * Used internally to parse a layout element.
+ * @param layoutElement The Layout Element.
+ * @return The Layout.
+ */
+ public Layout parseLayout(Element layoutElement) {
+ String className = subst(layoutElement.getAttribute(CLASS_ATTR));
+ LOGGER.debug("Parsing layout of class: \"{}\"", className);
+ Layout layout = manager.parseLayout(className, layoutElement, this);
+ if (layout == null) {
+ layout = buildLayout(className, layoutElement);
+ }
+ return layout;
+ }
+
+ private Layout buildLayout(String className, Element layout_element) {
+ try {
+ Layout layout = LoaderUtil.newInstanceOf(className);
+ PropertySetter propSetter = new PropertySetter(layout);
+ forEachElement(layout_element.getChildNodes(), (currentElement) -> {
+ String tagName = currentElement.getTagName();
+ if (tagName.equals(PARAM_TAG)) {
+ setParameter(currentElement, propSetter);
+ } else {
+ try {
+ parseUnrecognizedElement(layout, currentElement, props);
+ } catch (Exception ex) {
+ throw new ConsumerException(ex);
+ }
+ }
+ });
+
+ propSetter.activate();
+ return layout;
+ } catch (ConsumerException ce) {
+ Throwable cause = ce.getCause();
+ if (cause instanceof InterruptedException || cause instanceof InterruptedIOException) {
+ Thread.currentThread().interrupt();
+ }
+ LOGGER.error("Could not create the Layout. Reported error follows.", cause);
+ } catch (Exception oops) {
+ if (oops instanceof InterruptedException || oops instanceof InterruptedIOException) {
+ Thread.currentThread().interrupt();
+ }
+ LOGGER.error("Could not create the Layout. Reported error follows.", oops);
+ }
+ return null;
+ }
+
+ /**
+ * Used internally to parse a level element.
+ */
+ private void parseLevel(Element element, LoggerConfig logger, boolean isRoot) {
+ String catName = logger.getName();
+ if (isRoot) {
+ catName = "root";
+ }
+
+ String priStr = subst(element.getAttribute(VALUE_ATTR));
+ LOGGER.debug("Level value for {} is [{}].", catName, priStr);
+
+ if (INHERITED.equalsIgnoreCase(priStr) || NULL.equalsIgnoreCase(priStr)) {
+ if (isRoot) {
+ LOGGER.error("Root level cannot be inherited. Ignoring directive.");
+ } else {
+ logger.setLevel(null);
+ }
+ } else {
+ String className = subst(element.getAttribute(CLASS_ATTR));
+ if (EMPTY_STR.equals(className)) {
+ logger.setLevel(OptionConverter.convertLevel(priStr, org.apache.logging.log4j.Level.DEBUG));
+ } else {
+ LOGGER.debug("Desired Level sub-class: [{}]", className);
+ try {
+ Class<?> clazz = LoaderUtil.loadClass(className);
+ Method toLevelMethod = clazz.getMethod("toLevel", ONE_STRING_PARAM);
+ Level pri = (Level) toLevelMethod.invoke(null, new Object[]{priStr});
+ logger.setLevel(OptionConverter.convertLevel(pri));
+ } catch (Exception oops) {
+ if (oops instanceof InterruptedException || oops instanceof InterruptedIOException) {
+ Thread.currentThread().interrupt();
+ }
+ LOGGER.error("Could not create level [" + priStr +
+ "]. Reported error follows.", oops);
+ return;
+ }
+ }
+ }
+ LOGGER.debug("{} level set to {}", catName, logger.getLevel());
+ }
+
+ private void setParameter(Element elem, PropertySetter propSetter) {
+ String name = subst(elem.getAttribute(NAME_ATTR));
+ String value = (elem.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 <a
+ * href="doc-files/log4j.dtd">log4j.dtd</a>.
+ */
+ private void parse(Element element) {
+ String rootElementName = element.getTagName();
+
+ if (!rootElementName.equals(CONFIGURATION_TAG)) {
+ if (rootElementName.equals(OLD_CONFIGURATION_TAG)) {
+ LOGGER.warn("The <" + OLD_CONFIGURATION_TAG +
+ "> element has been deprecated.");
+ LOGGER.warn("Use the <" + CONFIGURATION_TAG + "> element instead.");
+ } else {
+ LOGGER.error("DOM element is - not a <" + CONFIGURATION_TAG + "> element.");
+ return;
+ }
+ }
+
+
+ String debugAttrib = subst(element.getAttribute(INTERNAL_DEBUG_ATTR));
+
+ LOGGER.debug("debug attribute= \"" + debugAttrib + "\".");
+ // if the log4j.dtd is not specified in the XML file, then the
+ // "debug" attribute is returned as the empty string.
+ String status = "error";
+ if (!debugAttrib.equals("") && !debugAttrib.equals("null")) {
+ status = OptionConverter.toBoolean(debugAttrib, true) ? "debug" : "error";
+
+ } else {
+ LOGGER.debug("Ignoring " + INTERNAL_DEBUG_ATTR + " attribute.");
+ }
+
+ String confDebug = subst(element.getAttribute(CONFIG_DEBUG_ATTR));
+ if (!confDebug.equals("") && !confDebug.equals("null")) {
+ LOGGER.warn("The \"" + CONFIG_DEBUG_ATTR + "\" attribute is deprecated.");
+ LOGGER.warn("Use the \"" + INTERNAL_DEBUG_ATTR + "\" attribute instead.");
+ status = OptionConverter.toBoolean(confDebug, true) ? "debug" : "error";
+ }
+
+ final StatusConfiguration statusConfig = new StatusConfiguration().setStatus(status);
+ statusConfig.initialize();
+
+ 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("Renderers are not supported by Log4j 2 and will be ignored.");
+ break;
+ case THROWABLE_RENDERER_TAG:
+ LOGGER.warn("Throwable Renderers are not supported by Log4j 2 and will be ignored.");
+ break;
+ case CATEGORY_FACTORY_TAG: case LOGGER_FACTORY_TAG:
+ LOGGER.warn("Log4j 1 Logger factories are not supported by Log4j 2 and will be ignored.");
+ break;
+ case APPENDER_TAG:
+ Appender appender = parseAppender(currentElement);
+ appenderMap.put(appender.getName(), appender);
+ if (appender instanceof AppenderWrapper) {
+ addAppender(((AppenderWrapper) appender).getAppender());
+ } else {
+ addAppender(new AppenderAdapter(appender).getAdapter());
+ }
+ break;
+ default:
+ quietParseUnrecognizedElement(null, currentElement, props);
+ }
+ });
+ }
+
+ private String subst(final String value) {
+ return getStrSubstitutor().replace(value);
+ }
+
+ public static void forEachElement(NodeList list, Consumer<Element> consumer) {
+ final int length = list.getLength();
+ for (int loop = 0; loop < length; loop++) {
+ Node currentNode = list.item(loop);
+
+ if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
+ Element currentElement = (Element) currentNode;
+ consumer.accept(currentElement);
+ }
+ }
+ }
+
+ 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();
+
+ public void error(final SAXParseException ex) {
+ emitMessage("Continuable parsing error ", ex);
+ }
+
+ public void fatalError(final SAXParseException ex) {
+ emitMessage("Fatal parsing error ", ex);
+ }
+
+ public void warning(final SAXParseException ex) {
+ emitMessage("Parsing warning ", ex);
+ }
+
+ private static void emitMessage(final String msg, final SAXParseException ex) {
+ LOGGER.warn("{} {} and column {}", msg, ex.getLineNumber(), ex.getColumnNumber());
+ LOGGER.warn(ex.getMessage(), ex.getException());
+ }
+ }
+
+ private static class ConsumerException extends RuntimeException {
+
+ ConsumerException(Exception ex) {
+ super(ex);
+ }
+ }
+}
diff --git a/log4j-1.2-api/src/main/java/org/apache/log4j/xml/XmlConfigurationFactory.java b/log4j-1.2-api/src/main/java/org/apache/log4j/xml/XmlConfigurationFactory.java
new file mode 100644
index 0000000..04b596e
--- /dev/null
+++ b/log4j-1.2-api/src/main/java/org/apache/log4j/xml/XmlConfigurationFactory.java
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.xml;
+
+import org.apache.log4j.config.Log4j1Configuration;
+import org.apache.logging.log4j.core.LoggerContext;
+import org.apache.logging.log4j.core.config.Configuration;
+import org.apache.logging.log4j.core.config.ConfigurationFactory;
+import org.apache.logging.log4j.core.config.ConfigurationSource;
+import org.apache.logging.log4j.core.config.Order;
+import org.apache.logging.log4j.plugins.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 {
+ 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[] {".xml"};
+ }
+
+ @Override
+ public Configuration getConfiguration(LoggerContext loggerContext, ConfigurationSource source) {
+ int interval = PropertiesUtil.getProperties().getIntegerProperty(Log4j1Configuration.MONITOR_INTERVAL, 0);
+ return new XmlConfiguration(loggerContext, source, interval);
+ }
+
+ @Override
+ protected String getTestPrefix() {
+ return TEST_PREFIX;
+ }
+
+ @Override
+ protected String getDefaultPrefix() {
+ return DEFAULT_PREFIX;
+ }
+
+ @Override
+ protected String getVersion() {
+ return LOG4J1_VERSION;
+ }
+}
diff --git a/log4j-1.2-api/src/main/resources/org/apache/log4j/xml/log4j.dtd b/log4j-1.2-api/src/main/resources/org/apache/log4j/xml/log4j.dtd
new file mode 100644
index 0000000..f8e433a
--- /dev/null
+++ b/log4j-1.2-api/src/main/resources/org/apache/log4j/xml/log4j.dtd
@@ -0,0 +1,237 @@
+<?xml version="1.0" encoding="UTF-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 "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!-- Authors: Chris Taylor, Ceki Gulcu. -->
+
+<!-- Version: 1.2 -->
+
+<!-- A configuration element consists of optional renderer
+elements,appender elements, categories and an optional root
+element. -->
+
+<!ELEMENT log4j:configuration (renderer*, throwableRenderer?,
+ appender*,plugin*, (category|logger)*,root?,
+ (categoryFactory|loggerFactory)?)>
+
+<!-- The "threshold" attribute takes a level value below which -->
+<!-- all logging statements are disabled. -->
+
+<!-- Setting the "debug" enable the printing of internal log4j logging -->
+<!-- statements. -->
+
+<!-- By default, debug attribute is "null", meaning that we not do touch -->
+<!-- internal log4j logging settings. The "null" value for the threshold -->
+<!-- attribute can be misleading. The threshold field of a repository -->
+<!-- cannot be set to null. The "null" value for the threshold attribute -->
+<!-- simply means don't touch the threshold field, the threshold field -->
+<!-- keeps its old value. -->
+
+<!ATTLIST log4j:configuration
+ xmlns:log4j CDATA #FIXED "http://jakarta.apache.org/log4j/"
+ threshold (all|trace|debug|info|warn|error|fatal|off|null) "null"
+ debug (true|false|null) "null"
+ reset (true|false) "false"
+>
+
+<!-- renderer elements allow the user to customize the conversion of -->
+<!-- message objects to String. -->
+
+<!ELEMENT renderer EMPTY>
+<!ATTLIST renderer
+ renderedClass CDATA #REQUIRED
+ renderingClass CDATA #REQUIRED
+>
+
+<!-- throwableRenderer allows the user to customize the conversion
+ of exceptions to a string representation. -->
+<!ELEMENT throwableRenderer (param*)>
+<!ATTLIST throwableRenderer
+ class CDATA #REQUIRED
+>
+
+
+<!-- Appenders must have a name and a class. -->
+<!-- Appenders may contain an error handler, a layout, optional parameters -->
+<!-- and filters. They may also reference (or include) other appenders. -->
+<!ELEMENT appender (errorHandler?, param*,
+ rollingPolicy?, triggeringPolicy?, connectionSource?,
+ layout?, filter*, appender-ref*)>
+<!ATTLIST appender
+ name CDATA #REQUIRED
+ class CDATA #REQUIRED
+>
+
+<!ELEMENT layout (param*)>
+<!ATTLIST layout
+ class CDATA #REQUIRED
+>
+
+<!ELEMENT filter (param*)>
+<!ATTLIST filter
+ class CDATA #REQUIRED
+>
+
+<!-- ErrorHandlers can be of any class. They can admit any number of -->
+<!-- parameters. -->
+
+<!ELEMENT errorHandler (param*, root-ref?, logger-ref*, appender-ref?)>
+<!ATTLIST errorHandler
+ class CDATA #REQUIRED
+>
+
+<!ELEMENT root-ref EMPTY>
+
+<!ELEMENT logger-ref EMPTY>
+<!ATTLIST logger-ref
+ ref CDATA #REQUIRED
+>
+
+<!ELEMENT param EMPTY>
+<!ATTLIST param
+ name CDATA #REQUIRED
+ value CDATA #REQUIRED
+>
+
+
+<!-- The priority class is org.apache.log4j.Level by default -->
+<!ELEMENT priority (param*)>
+<!ATTLIST priority
+ class CDATA #IMPLIED
+ value CDATA #REQUIRED
+>
+
+<!-- The level class is org.apache.log4j.Level by default -->
+<!ELEMENT level (param*)>
+<!ATTLIST level
+ class CDATA #IMPLIED
+ value CDATA #REQUIRED
+>
+
+
+<!-- If no level element is specified, then the configurator MUST not -->
+<!-- touch the level of the named category. -->
+<!ELEMENT category (param*,(priority|level)?,appender-ref*)>
+<!ATTLIST category
+ class CDATA #IMPLIED
+ name CDATA #REQUIRED
+ additivity (true|false) "true"
+>
+
+<!-- If no level element is specified, then the configurator MUST not -->
+<!-- touch the level of the named logger. -->
+<!ELEMENT logger (param*,level?,appender-ref*)>
+<!ATTLIST logger
+ class CDATA #IMPLIED
+ name CDATA #REQUIRED
+ additivity (true|false) "true"
+>
+
+
+<!ELEMENT categoryFactory (param*)>
+<!ATTLIST categoryFactory
+ class CDATA #REQUIRED>
+
+<!ELEMENT loggerFactory (param*)>
+<!ATTLIST loggerFactory
+ class CDATA #REQUIRED>
+
+<!ELEMENT appender-ref EMPTY>
+<!ATTLIST appender-ref
+ ref CDATA #REQUIRED
+>
+
+<!-- plugins must have a name and class and can have optional parameters -->
+<!ELEMENT plugin (param*, connectionSource?)>
+<!ATTLIST plugin
+ name CDATA #REQUIRED
+ class CDATA #REQUIRED
+>
+
+<!ELEMENT connectionSource (dataSource?, param*)>
+<!ATTLIST connectionSource
+ class CDATA #REQUIRED
+>
+
+<!ELEMENT dataSource (param*)>
+<!ATTLIST dataSource
+ class CDATA #REQUIRED
+>
+
+<!ELEMENT triggeringPolicy ((param|filter)*)>
+<!ATTLIST triggeringPolicy
+ name CDATA #IMPLIED
+ class CDATA #REQUIRED
+>
+
+<!ELEMENT rollingPolicy (param*)>
+<!ATTLIST rollingPolicy
+ name CDATA #IMPLIED
+ class CDATA #REQUIRED
+>
+
+
+<!-- If no priority element is specified, then the configurator MUST not -->
+<!-- touch the priority of root. -->
+<!-- The root category always exists and cannot be subclassed. -->
+<!ELEMENT root (param*, (priority|level)?, appender-ref*)>
+
+
+<!-- ==================================================================== -->
+<!-- A logging event -->
+<!-- ==================================================================== -->
+<!ELEMENT log4j:eventSet (log4j:event*)>
+<!ATTLIST log4j:eventSet
+ xmlns:log4j CDATA #FIXED "http://jakarta.apache.org/log4j/"
+ version (1.1|1.2) "1.2"
+ includesLocationInfo (true|false) "true"
+>
+
+
+
+<!ELEMENT log4j:event (log4j:message, log4j:NDC?, log4j:throwable?,
+ log4j:locationInfo?, log4j:properties?) >
+
+<!-- The timestamp format is application dependent. -->
+<!ATTLIST log4j:event
+ logger CDATA #REQUIRED
+ level CDATA #REQUIRED
+ thread CDATA #REQUIRED
+ timestamp CDATA #REQUIRED
+ time CDATA #IMPLIED
+>
+
+<!ELEMENT log4j:message (#PCDATA)>
+<!ELEMENT log4j:NDC (#PCDATA)>
+
+<!ELEMENT log4j:throwable (#PCDATA)>
+
+<!ELEMENT log4j:locationInfo EMPTY>
+<!ATTLIST log4j:locationInfo
+ class CDATA #REQUIRED
+ method CDATA #REQUIRED
+ file CDATA #REQUIRED
+ line CDATA #REQUIRED
+>
+
+<!ELEMENT log4j:properties (log4j:data*)>
+
+<!ELEMENT log4j:data EMPTY>
+<!ATTLIST log4j:data
+ name CDATA #REQUIRED
+ value CDATA #REQUIRED
+>
diff --git a/log4j-1.2-api/src/site/markdown/index.md b/log4j-1.2-api/src/site/markdown/index.md
index a80e687..696e0bb 100644
--- a/log4j-1.2-api/src/site/markdown/index.md
+++ b/log4j-1.2-api/src/site/markdown/index.md
@@ -18,12 +18,26 @@
# Log4j 1.2 Bridge
-The Log4j 1.2 Bridge allows applications coded to use Log4j 1.2 API to use
-Log4j 2 instead.
+The Log4j 1.2 Bridge allows applications coded to use Log4j 1.2 API to use Log4j 2 instead.
## Requirements
-The Log4j 1.2 bridge is dependent on the Log4j 2 API and implementation.
+The Log4j 1.2 bridge is dependent on the Log4j 2 API. The following Log4j 1.x methods will behave differently when
+the Log4j 2 Core module is included then when it is not:
+
+| Method | Without log4j-core | With log4j-core |
+| ----------------------------- | ------------------ | ------------------------------------ |
+| Category.getParent() | Returns null | Returns parent logger |
+| Category.setLevel() | NoOp | Sets Logger Level |
+| Category.setPriority() | NoOp | Sets Logger Level |
+| Category.getAdditivity() | Returns false | Returns Logger's additivity setting |
+| Category.setAdditivity() | NoOp | Sets additivity of LoggerConfig |
+| Category.getResourceBundle() | NoOp | Returns the resource bundle associated with the Logger |
+| BasicConfigurator.configure() | NoOp | Reconfigures Log4j 2 |
+
+If log4j-core is not present location information will not be accurate in calls using the Log4j 1.2 API. The config
+package which attempts tp convert Log4j 1.x configurations to Log4j 2 is not supported without Log4j 2.
+
For more information, see [Runtime Dependencies](../runtime-dependencies.html).
## Usage
diff --git a/log4j-1.2-api/src/src/main/java/org/apache/log4j/Appender.java b/log4j-1.2-api/src/src/main/java/org/apache/log4j/Appender.java
new file mode 100644
index 0000000..de89cce
--- /dev/null
+++ b/log4j-1.2-api/src/src/main/java/org/apache/log4j/Appender.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.log4j;
+
+import org.apache.log4j.spi.ErrorHandler;
+import org.apache.log4j.spi.Filter;
+import org.apache.log4j.spi.LoggingEvent;
+
+/**
+ * Implement this interface for your own strategies for outputting log
+ * statements.
+ */
+public interface Appender {
+
+ /**
+ * Add a filter to the end of the filter list.
+ * @param newFilter The filter to add.
+ *
+ * @since 0.9.0
+ */
+ void addFilter(Filter newFilter);
+
+ /**
+ * Returns the head Filter. The Filters are organized in a linked list
+ * and so all Filters on this Appender are available through the result.
+ *
+ * @return the head Filter or null, if no Filters are present
+ * @since 1.1
+ */
+ Filter getFilter();
+
+ /**
+ * Clear the list of filters by removing all the filters in it.
+ *
+ * @since 0.9.0
+ */
+ void clearFilters();
+
+ /**
+ * Release any resources allocated within the appender such as file
+ * handles, network connections, etc.
+ * <p>
+ * It is a programming error to append to a closed appender.
+ * </p>
+ *
+ * @since 0.8.4
+ */
+ void close();
+
+ /**
+ * Log in <code>Appender</code> specific way. When appropriate,
+ * Loggers will call the <code>doAppend</code> method of appender
+ * implementations in order to log.
+ * @param event The LoggingEvent.
+ */
+ void doAppend(LoggingEvent event);
+
+
+ /**
+ * Get the name of this appender.
+ *
+ * @return name, may be null.
+ */
+ String getName();
+
+
+ /**
+ * Set the {@link ErrorHandler} for this appender.
+ * @param errorHandler The error handler.
+ *
+ * @since 0.9.0
+ */
+ void setErrorHandler(ErrorHandler errorHandler);
+
+ /**
+ * Returns the {@link ErrorHandler} for this appender.
+ * @return The error handler.
+ *
+ * @since 1.1
+ */
+ ErrorHandler getErrorHandler();
+
+ /**
+ * Set the {@link Layout} for this appender.
+ * @param layout The Layout.
+ *
+ * @since 0.8.1
+ */
+ void setLayout(Layout layout);
+
+ /**
+ * Returns this appenders layout.
+ * @return the Layout.
+ *
+ * @since 1.1
+ */
+ Layout getLayout();
+
+
+ /**
+ * Set the name of this appender. The name is used by other
+ * components to identify this appender.
+ * @param name The appender name.
+ *
+ * @since 0.8.1
+ */
+ void setName(String name);
+
+ /**
+ * Configurators call this method to determine if the appender
+ * requires a layout. If this method returns {@code true},
+ * meaning that layout is required, then the configurator will
+ * configure an layout using the configuration information at its
+ * disposal. If this method returns {@code false}, meaning that
+ * a layout is not required, then layout configuration will be
+ * skipped even if there is available layout configuration
+ * information at the disposal of the configurator..
+ * <p>
+ * In the rather exceptional case, where the appender
+ * implementation admits a layout but can also work without it, then
+ * the appender should return {@code true}.
+ * </p>
+ * @return true if a Layout is required.
+ *
+ * @since 0.8.4
+ */
+ boolean requiresLayout();
+}
+
diff --git a/log4j-1.2-api/src/src/main/java/org/apache/log4j/AppenderSkeleton.java b/log4j-1.2-api/src/src/main/java/org/apache/log4j/AppenderSkeleton.java
new file mode 100644
index 0000000..1353dae
--- /dev/null
+++ b/log4j-1.2-api/src/src/main/java/org/apache/log4j/AppenderSkeleton.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;
+
+import org.apache.log4j.spi.ErrorHandler;
+import org.apache.log4j.spi.Filter;
+import org.apache.log4j.spi.LoggingEvent;
+import org.apache.log4j.spi.OptionHandler;
+
+/**
+ * The base class for Appenders in Log4j 1. Appenders constructed using this are ignored in Log4j 2.
+ */
+public abstract class AppenderSkeleton implements Appender, OptionHandler {
+
+ protected Layout layout;
+
+ protected String name;
+
+ protected Priority threshold;
+
+ protected ErrorHandler errorHandler = new NoOpErrorHandler();
+
+ protected Filter headFilter;
+
+ protected Filter tailFilter;
+
+ protected boolean closed = false;
+
+ /**
+ * Create new instance.
+ */
+ public AppenderSkeleton() {
+ super();
+ }
+
+ protected AppenderSkeleton(final boolean isActive) {
+ super();
+ }
+
+ @Override
+ public void activateOptions() {
+ }
+
+ @Override
+ public void addFilter(final Filter newFilter) {
+ if(headFilter == null) {
+ headFilter = tailFilter = newFilter;
+ } else {
+ tailFilter.setNext(newFilter);
+ tailFilter = newFilter;
+ }
+ }
+
+ protected abstract void append(LoggingEvent event);
+
+ @Override
+ public void clearFilters() {
+ headFilter = tailFilter = null;
+ }
+
+ @Override
+ public void finalize() {
+ }
+
+ @Override
+ public ErrorHandler getErrorHandler() {
+ return this.errorHandler;
+ }
+
+ @Override
+ public Filter getFilter() {
+ return headFilter;
+ }
+
+ public final Filter getFirstFilter() {
+ return headFilter;
+ }
+
+ @Override
+ public Layout getLayout() {
+ return layout;
+ }
+
+ @Override
+ public final String getName() {
+ return this.name;
+ }
+
+ public Priority getThreshold() {
+ return threshold;
+ }
+
+ 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(final LoggingEvent event) {
+ append(event);
+ }
+
+ /**
+ * Set the {@link ErrorHandler} for this Appender.
+ *
+ * @since 0.9.0
+ */
+ @Override
+ public synchronized void setErrorHandler(final ErrorHandler eh) {
+ if (eh != null) {
+ this.errorHandler = eh;
+ }
+ }
+
+ @Override
+ public void setLayout(final Layout layout) {
+ this.layout = layout;
+ }
+
+ @Override
+ public void setName(final String name) {
+ this.name = name;
+ }
+
+ public void setThreshold(final Priority threshold) {
+ this.threshold = threshold;
+ }
+
+ public static class NoOpErrorHandler implements ErrorHandler {
+ @Override
+ public void setLogger(final Logger logger) {
+
+ }
+
+ @Override
+ public void error(final String message, final Exception e, final int errorCode) {
+
+ }
+
+ @Override
+ public void error(final String message) {
+
+ }
+
+ @Override
+ public void error(final String message, final Exception e, final int errorCode, final LoggingEvent event) {
+
+ }
+
+ @Override
+ public void setAppender(final Appender appender) {
+
+ }
+
+ @Override
+ public void setBackupAppender(final Appender appender) {
+
+ }
+ }
+}
diff --git a/log4j-1.2-api/src/src/main/java/org/apache/log4j/BasicConfigurator.java b/log4j-1.2-api/src/src/main/java/org/apache/log4j/BasicConfigurator.java
new file mode 100644
index 0000000..2b7ec7f
--- /dev/null
+++ b/log4j-1.2-api/src/src/main/java/org/apache/log4j/BasicConfigurator.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.log4j;
+
+/**
+ * Provided for compatibility with Log4j 1.x.
+ */
+public class BasicConfigurator {
+
+ protected BasicConfigurator() {
+ }
+
+ public static void configure() {
+ LogManager.reconfigure();
+ }
+
+ /**
+ * No-op implementation.
+ * @param appender The appender.
+ */
+ public static void configure(final Appender appender) {
+ // no-op
+ }
+
+ /**
+ * No-op implementation.
+ */
+ public static void resetConfiguration() {
+ // no-op
+ }
+}
diff --git a/log4j-1.2-api/src/src/main/java/org/apache/log4j/Category.java b/log4j-1.2-api/src/src/main/java/org/apache/log4j/Category.java
new file mode 100644
index 0000000..e0e5aef
--- /dev/null
+++ b/log4j-1.2-api/src/src/main/java/org/apache/log4j/Category.java
@@ -0,0 +1,571 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.log4j;
+
+import java.util.Enumeration;
+import java.util.Map;
+import java.util.ResourceBundle;
+import java.util.WeakHashMap;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import org.apache.log4j.helpers.NullEnumeration;
+import org.apache.log4j.legacy.core.CategoryUtil;
+import org.apache.log4j.or.ObjectRenderer;
+import org.apache.log4j.or.RendererSupport;
+import org.apache.log4j.spi.LoggerFactory;
+import org.apache.log4j.spi.LoggingEvent;
+import org.apache.logging.log4j.spi.ExtendedLogger;
+import org.apache.logging.log4j.spi.LoggerContext;
+import org.apache.logging.log4j.message.LocalizedMessage;
+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.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<LoggerContext, ConcurrentMap<String, Logger>> CONTEXT_MAP =
+ new WeakHashMap<>();
+
+ private static final String FQCN = Category.class.getName();
+
+ private static final boolean isCoreAvailable;
+
+ private final Map<Class<?>, ObjectRenderer> rendererMap;
+
+ static {
+ boolean available;
+
+ try {
+ available = Class.forName("org.apache.logging.log4j.core.Logger") != null;
+ } catch (Exception ex) {
+ available = false;
+ }
+ isCoreAvailable = available;
+ }
+
+ /**
+ * Resource bundle for localized messages.
+ */
+ protected ResourceBundle bundle = null;
+
+ private final org.apache.logging.log4j.Logger logger;
+
+ /**
+ * 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.logger = context.getLogger(name);
+ rendererMap = ((RendererSupport) LogManager.getLoggerRepository()).getRendererMap();
+ }
+
+ /**
+ * Constructor exposed by Log4j 1.2.
+ * @param name The name of the Logger.
+ */
+ protected Category(final String name) {
+ this(PrivateManager.getContext(), name);
+ }
+
+ private Category(final org.apache.logging.log4j.Logger logger) {
+ this.logger = logger;
+ rendererMap = ((RendererSupport) LogManager.getLoggerRepository()).getRendererMap();
+ }
+
+ public static Category getInstance(final String name) {
+ return getInstance(PrivateManager.getContext(), name, adapter);
+ }
+
+ static Logger getInstance(final LoggerContext context, final String name) {
+ return getInstance(context, name, adapter);
+ }
+
+ static Logger getInstance(final LoggerContext context, final String name, final LoggerFactory factory) {
+ final ConcurrentMap<String, Logger> loggers = getLoggersMap(context);
+ Logger logger = loggers.get(name);
+ if (logger != null) {
+ return logger;
+ }
+ logger = factory.makeNewLoggerInstance(name);
+ final Logger prev = loggers.putIfAbsent(name, logger);
+ return prev == null ? logger : prev;
+ }
+
+ static Logger getInstance(final LoggerContext context, final String name, final PrivateAdapter factory) {
+ final ConcurrentMap<String, Logger> loggers = getLoggersMap(context);
+ Logger logger = loggers.get(name);
+ if (logger != null) {
+ return logger;
+ }
+ logger = factory.newLogger(name, context);
+ final Logger prev = loggers.putIfAbsent(name, logger);
+ return prev == null ? logger : prev;
+ }
+
+ public static Category getInstance(@SuppressWarnings("rawtypes") final Class clazz) {
+ return getInstance(clazz.getName());
+ }
+
+ static Logger getInstance(final LoggerContext context, @SuppressWarnings("rawtypes") final Class clazz) {
+ return getInstance(context, clazz.getName());
+ }
+
+ public final String getName() {
+ return logger.getName();
+ }
+
+ org.apache.logging.log4j.Logger getLogger() {
+ return logger;
+ }
+
+ public final Category getParent() {
+ if (!isCoreAvailable) {
+ return null;
+ }
+ org.apache.logging.log4j.Logger parent = CategoryUtil.getParent(logger);
+ LoggerContext loggerContext = CategoryUtil.getLoggerContext(logger);
+ if (parent == null || loggerContext == null) {
+ return null;
+ }
+ final ConcurrentMap<String, Logger> loggers = getLoggersMap(loggerContext);
+ final Logger l = loggers.get(parent.getName());
+ return l == null ? new Category(parent) : l;
+ }
+
+ public static Category getRoot() {
+ return getInstance(Strings.EMPTY);
+ }
+
+ static Logger getRoot(final LoggerContext context) {
+ return getInstance(context, Strings.EMPTY);
+ }
+
+ private static ConcurrentMap<String, Logger> getLoggersMap(final LoggerContext context) {
+ synchronized (CONTEXT_MAP) {
+ ConcurrentMap<String, Logger> map = CONTEXT_MAP.get(context);
+ if (map == null) {
+ map = new ConcurrentHashMap<>();
+ CONTEXT_MAP.put(context, map);
+ }
+ return map;
+ }
+ }
+
+ /**
+ Returns all the currently defined categories in the default
+ hierarchy as an {@link java.util.Enumeration Enumeration}.
+
+ <p>The root category is <em>not</em> included in the returned
+ {@link Enumeration}.
+ @return and Enumeration of the Categories.
+
+ @deprecated Please use {@link LogManager#getCurrentLoggers()} instead.
+ */
+ @SuppressWarnings("rawtypes")
+ @Deprecated
+ public static Enumeration getCurrentCategories() {
+ return LogManager.getCurrentLoggers();
+ }
+
+ 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;
+ default:
+ // TODO Should this be an IllegalStateException?
+ return Level.OFF;
+ }
+ }
+
+ public final Priority getChainedPriority() {
+ return getEffectiveLevel();
+ }
+
+ public final Level getLevel() {
+ return getEffectiveLevel();
+ }
+
+ public void setLevel(final Level level) {
+ setLevel(level.levelStr);
+ }
+
+ public final Level getPriority() {
+ return getEffectiveLevel();
+ }
+
+ public void setPriority(final Priority priority) {
+ setLevel(priority.levelStr);
+ }
+
+ private void setLevel(final String levelStr) {
+ if (isCoreAvailable) {
+ CategoryUtil.setLevel(logger, org.apache.logging.log4j.Level.toLevel(levelStr));
+ }
+ }
+
+ public void debug(final Object message) {
+ maybeLog(FQCN, org.apache.logging.log4j.Level.DEBUG, message, null);
+ }
+
+ 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);
+ }
+
+ 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);
+ }
+
+ 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);
+ }
+
+ public boolean isTraceEnabled() {
+ return logger.isTraceEnabled();
+ }
+
+ public boolean isEnabledFor(final Priority level) {
+ final org.apache.logging.log4j.Level lvl = org.apache.logging.log4j.Level.toLevel(level.toString());
+ return isEnabledFor(lvl);
+ }
+
+ /**
+ * No-op implementation.
+ * @param appender The Appender to add.
+ */
+ public void addAppender(final Appender appender) {
+ }
+
+ /**
+ * No-op implementation.
+ * @param event The logging event.
+ */
+ public void callAppenders(final LoggingEvent event) {
+ }
+
+ @SuppressWarnings("rawtypes")
+ public Enumeration getAllAppenders() {
+ return NullEnumeration.getInstance();
+ }
+
+ /**
+ * No-op implementation.
+ * @param name The name of the Appender.
+ * @return null.
+ */
+ public Appender getAppender(final String name) {
+ return 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;
+ }
+
+ /**
+ * No-op implementation.
+ */
+ public void removeAllAppenders() {
+ }
+
+ /**
+ * No-op implementation.
+ * @param appender The Appender to remove.
+ */
+ public void removeAppender(final Appender appender) {
+ }
+
+ /**
+ * No-op implementation.
+ * @param name The Appender to remove.
+ */
+ public void removeAppender(final String name) {
+ }
+
+ /**
+ * No-op implementation.
+ */
+ public static void shutdown() {
+ }
+
+ public void forcedLog(final String fqcn, final Priority level, final Object message, final Throwable t) {
+ final org.apache.logging.log4j.Level lvl = org.apache.logging.log4j.Level.toLevel(level.toString());
+ ObjectRenderer renderer = get(message.getClass());
+ final Message msg = message instanceof Message ? (Message) message : renderer != null ?
+ new RenderedMessage(renderer, message) : new ObjectMessage(message);
+ if (logger instanceof ExtendedLogger) {
+ ((ExtendedLogger) logger).logMessage(fqcn, lvl, null, new ObjectMessage(message), t);
+ } else {
+ logger.log(lvl, msg, t);
+ }
+ }
+
+ public boolean exists(final String name) {
+ return PrivateManager.getContext().hasLogger(name);
+ }
+
+ public boolean getAdditivity() {
+ return isCoreAvailable ? CategoryUtil.isAdditive(logger) : false;
+ }
+
+ public void setAdditivity(final boolean additivity) {
+ if (isCoreAvailable) {
+ CategoryUtil.setAdditivity(logger, additivity);
+ }
+ }
+
+ public void setResourceBundle(final ResourceBundle bundle) {
+ this.bundle = bundle;
+ }
+
+ public ResourceBundle getResourceBundle() {
+ if (bundle != null) {
+ return bundle;
+ }
+ String name = logger.getName();
+ if (isCoreAvailable) {
+ LoggerContext ctx = CategoryUtil.getLoggerContext(logger);
+ if (ctx != null) {
+ final ConcurrentMap<String, Logger> loggers = 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;
+ }
+
+ private static String getSubName(final String name) {
+ if (Strings.isEmpty(name)) {
+ return null;
+ }
+ final int i = name.lastIndexOf('.');
+ return i > 0 ? name.substring(0, i) : Strings.EMPTY;
+ }
+
+ /**
+ If <code>assertion</code> parameter is {@code false}, then
+ logs <code>msg</code> as an {@link #error(Object) error} statement.
+
+ <p>The <code>assert</code> method has been renamed to
+ <code>assertLog</code> because <code>assert</code> is a language
+ reserved word in JDK 1.4.
+
+ @param assertion The assertion.
+ @param msg The message to print if <code>assertion</code> is
+ false.
+
+ @since 1.2
+ */
+ public void assertLog(final boolean assertion, final String msg) {
+ if (!assertion) {
+ this.error(msg);
+ }
+ }
+
+ 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 void l7dlog(final Priority priority, final String key, final Object[] params, final Throwable t) {
+ if (isEnabledFor(priority)) {
+ final Message msg = new LocalizedMessage(bundle, key, params);
+ forcedLog(FQCN, priority, msg, t);
+ }
+ }
+
+ public void log(final Priority priority, final Object message, final Throwable t) {
+ if (isEnabledFor(priority)) {
+ final Message msg = new ObjectMessage(message);
+ 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);
+ }
+ }
+
+ public void log(final String fqcn, final Priority priority, final Object message, final Throwable t) {
+ if (isEnabledFor(priority)) {
+ final Message msg = new ObjectMessage(message);
+ forcedLog(fqcn, priority, msg, t);
+ }
+ }
+
+ private void maybeLog(final String fqcn, final org.apache.logging.log4j.Level level,
+ final Object message, final Throwable throwable) {
+ if (logger.isEnabled(level)) {
+ if (logger instanceof ExtendedLogger) {
+ ((ExtendedLogger) logger).logMessage(fqcn, level, null, new ObjectMessage(message), throwable);
+ } else {
+ logger.log(level, message, throwable);
+ }
+ }
+ }
+
+ private static class PrivateAdapter extends AbstractLoggerAdapter<Logger> {
+
+ @Override
+ protected Logger newLogger(final String name, final org.apache.logging.log4j.spi.LoggerContext context) {
+ return new Logger(context, name);
+ }
+
+ @Override
+ protected org.apache.logging.log4j.spi.LoggerContext getContext() {
+ return PrivateManager.getContext();
+ }
+ }
+
+ /**
+ * Private LogManager.
+ */
+ private static class PrivateManager extends org.apache.logging.log4j.LogManager {
+ private static final String FQCN = Category.class.getName();
+
+ public static LoggerContext getContext() {
+ return getContext(FQCN, false);
+ }
+
+ public static org.apache.logging.log4j.Logger getLogger(final String name) {
+ return getLogger(FQCN, name);
+ }
+ }
+
+ private boolean isEnabledFor(final org.apache.logging.log4j.Level level) {
+ return logger.isEnabled(level);
+ }
+
+ private ObjectRenderer get(Class clazz) {
+ ObjectRenderer renderer = null;
+ for(Class c = clazz; c != null; c = c.getSuperclass()) {
+ renderer = rendererMap.get(c);
+ if (renderer != null) {
+ return renderer;
+ }
+ renderer = searchInterfaces(c);
+ if (renderer != null) {
+ return renderer;
+ }
+ }
+ return null;
+ }
+
+ ObjectRenderer searchInterfaces(Class c) {
+ ObjectRenderer renderer = rendererMap.get(c);
+ if(renderer != null) {
+ return renderer;
+ } else {
+ Class[] ia = c.getInterfaces();
+ for (Class clazz : ia) {
+ renderer = searchInterfaces(clazz);
+ if (renderer != null) {
+ return renderer;
+ }
+ }
+ }
+ return null;
+ }
+
+}
diff --git a/log4j-1.2-api/src/src/main/java/org/apache/log4j/ConsoleAppender.java b/log4j-1.2-api/src/src/main/java/org/apache/log4j/ConsoleAppender.java
new file mode 100644
index 0000000..605fac7
--- /dev/null
+++ b/log4j-1.2-api/src/src/main/java/org/apache/log4j/ConsoleAppender.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;
+
+import org.apache.log4j.spi.LoggingEvent;
+
+/**
+ * Console-appender.
+ */
+public class ConsoleAppender extends AppenderSkeleton
+{
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void close()
+ {
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean requiresLayout()
+ {
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ protected void append(final LoggingEvent theEvent)
+ {
+ }
+
+}
diff --git a/log4j-1.2-api/src/src/main/java/org/apache/log4j/Layout.java b/log4j-1.2-api/src/src/main/java/org/apache/log4j/Layout.java
new file mode 100644
index 0000000..dbd2291
--- /dev/null
+++ b/log4j-1.2-api/src/src/main/java/org/apache/log4j/Layout.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.log4j;
+
+import org.apache.log4j.spi.LoggingEvent;
+import org.apache.logging.log4j.util.Strings;
+
+/**
+ *
+ */
+public abstract class Layout {
+
+ public final static String LINE_SEP = Strings.LINE_SEPARATOR;
+
+ /** Note that the line.separator property can be looked up even by applets. */
+ public static final int LINE_SEP_LEN = Strings.LINE_SEPARATOR.length();
+
+ /**
+ * Implement this method to create your own layout format.
+ * @param event The LoggingEvent.
+ * @return The formatted LoggingEvent.
+ */
+ public abstract String format(LoggingEvent event);
+
+ /**
+ * Returns the content type output by this layout. The base class
+ * returns "text/plain".
+ * @return the type of content rendered by the Layout.
+ */
+ public String getContentType() {
+ return "text/plain";
+ }
+
+ /**
+ * Returns the header for the layout format. The base class returns
+ * <code>null</code>.
+ * @return The header.
+ */
+ public String getHeader() {
+ return null;
+ }
+
+ /**
+ * Returns the footer for the layout format. The base class returns
+ * <code>null</code>.
+ * @return The footer.
+ */
+ public String getFooter() {
+ return null;
+ }
+
+
+ /**
+ * If the layout handles the throwable object contained within
+ * {@link LoggingEvent}, then the layout should return
+ * {@code false}. Otherwise, if the layout ignores throwable
+ * object, then the layout should return {@code true}.
+ * If ignoresThrowable is true, the appender is responsible for
+ * rendering the throwable.
+ * <p>
+ * The <a href="/log4j/1.2/apidocs/org/apache/log4j/SimpleLayout.html">SimpleLayout</a>,
+ * <a href="/log4j/1.2/apidocs/org/apache/log4j/TTCCLayout.html">TTCCLayout</a>,
+ * <a href="/log4j/1.2/apidocs/org/apache/log4j/PatternLayout.html">PatternLayout</a>
+ * all return {@code true}. The
+ * <a href="/log4j/1.2/apidocs/org/apache/log4j/xml/XMLLayout.html">XMLLayout</a>
+ * returns {@code false}.
+ * </p>
+ *
+ * @return true if the Layout ignores Throwables.
+ *
+ * @since 0.8.4
+ */
+ public abstract boolean ignoresThrowable();
+}
+
diff --git a/log4j-1.2-api/src/src/main/java/org/apache/log4j/Level.java b/log4j-1.2-api/src/src/main/java/org/apache/log4j/Level.java
new file mode 100644
index 0000000..af53154
--- /dev/null
+++ b/log4j-1.2-api/src/src/main/java/org/apache/log4j/Level.java
@@ -0,0 +1,252 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.log4j;
+
+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.logging.log4j.util.Strings;
+
+/**
+ * Defines the minimum set of levels recognized by the system, that is
+ * <code>OFF</code>, <code>FATAL</code>, <code>ERROR</code>,
+ * <code>WARN</code>, <code>INFO</code>, <code>DEBUG</code>
+ * and <code>ALL</code>.
+ * <p>
+ * The <code>Level</code> class may be subclassed to define a larger
+ * level set.
+ * </p>
+ */
+public class Level extends Priority implements Serializable {
+
+ /**
+ * TRACE level integer value.
+ *
+ * @since 1.2.12
+ */
+ public static final int TRACE_INT = 5000;
+
+ /**
+ * The <code>OFF</code> has the highest possible rank and is
+ * intended to turn off logging.
+ */
+ public static final Level OFF = new Level(OFF_INT, "OFF", 0);
+
+ /**
+ * The <code>FATAL</code> 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);
+
+ /**
+ * The <code>ERROR</code> level designates error events that
+ * might still allow the application to continue running.
+ */
+ public static final Level ERROR = new Level(ERROR_INT, "ERROR", 3);
+
+ /**
+ * The <code>WARN</code> level designates potentially harmful situations.
+ */
+ public static final Level WARN = new Level(WARN_INT, "WARN", 4);
+
+ /**
+ * The <code>INFO</code> 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);
+
+ /**
+ * The <code>DEBUG</code> 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);
+
+ /**
+ * The <code>TRACE</code> Level designates finer-grained
+ * informational events than the <code>DEBUG</code> level.
+ */
+ public static final Level TRACE = new Level(TRACE_INT, "TRACE", 7);
+
+ /**
+ * The <code>ALL</code> has the lowest possible rank and is intended to
+ * turn on all logging.
+ */
+ public static final Level ALL = new Level(ALL_INT, "ALL", 7);
+
+ /**
+ * Serialization version id.
+ */
+ private static final long serialVersionUID = 3491141966387921974L;
+
+ /**
+ * Instantiate a Level object.
+ *
+ * @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);
+ }
+
+
+ /**
+ * Convert the string passed as argument to a level. If the
+ * conversion fails, then this method returns {@link #DEBUG}.
+ *
+ * @param sArg The level name.
+ * @return The Level.
+ */
+ public static Level toLevel(final String sArg) {
+ return toLevel(sArg, Level.DEBUG);
+ }
+
+ /**
+ * Convert an integer passed as argument to a level. If the
+ * conversion fails, then this method returns {@link #DEBUG}.
+ *
+ * @param val The integer value of the Level.
+ * @return The Level.
+ */
+ public static Level toLevel(final int val) {
+ return toLevel(val, Level.DEBUG);
+ }
+
+ /**
+ * Convert an integer passed as argument to a level. If the
+ * conversion fails, then this method returns the specified default.
+ *
+ * @param val The integer value of the Level.
+ * @param defaultLevel the default level if the integer doesn't match.
+ * @return The matching Level.
+ */
+ public static Level toLevel(final int val, final Level defaultLevel) {
+ switch (val) {
+ case ALL_INT:
+ return ALL;
+ case DEBUG_INT:
+ return Level.DEBUG;
+ case INFO_INT:
+ return Level.INFO;
+ case WARN_INT:
+ return Level.WARN;
+ case ERROR_INT:
+ return Level.ERROR;
+ case FATAL_INT:
+ return Level.FATAL;
+ case OFF_INT:
+ return OFF;
+ case TRACE_INT:
+ return Level.TRACE;
+ default:
+ return defaultLevel;
+ }
+ }
+
+ /**
+ * Convert the string passed as argument to a level. If the
+ * conversion fails, then this method returns the value of
+ * <code>defaultLevel</code>.
+ * @param sArg The name of the Level.
+ * @param defaultLevel The default Level to use.
+ * @return the matching Level.
+ */
+ public static Level toLevel(final String sArg, final Level defaultLevel) {
+ if (sArg == null) {
+ return defaultLevel;
+ }
+ final String s = sArg.toUpperCase(Locale.ROOT);
+ 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;
+ }
+ }
+
+ /**
+ * Custom deserialization of Level.
+ *
+ * @param s serialization stream.
+ * @throws IOException if IO exception.
+ * @throws ClassNotFoundException if class not found.
+ */
+ private void readObject(final ObjectInputStream s) throws IOException, ClassNotFoundException {
+ s.defaultReadObject();
+ level = s.readInt();
+ syslogEquivalent = s.readInt();
+ levelStr = s.readUTF();
+ if (levelStr == null) {
+ levelStr = Strings.EMPTY;
+ }
+ }
+
+ /**
+ * Serialize level.
+ *
+ * @param s serialization stream.
+ * @throws IOException if exception during serialization.
+ */
+ private void writeObject(final ObjectOutputStream s) throws IOException {
+ s.defaultWriteObject();
+ s.writeInt(level);
+ s.writeInt(syslogEquivalent);
+ s.writeUTF(levelStr);
+ }
+
+ /**
+ * Resolved deserialized level to one of the stock instances.
+ * May be overridden in classes derived from Level.
+ *
+ * @return resolved object.
+ * @throws ObjectStreamException if exception during resolution.
+ */
+ protected Object readResolve() throws ObjectStreamException {
+ //
+ // if the deserialized object is exactly an instance of Level
+ //
+ if (getClass() == Level.class) {
+ return toLevel(level);
+ }
+ //
+ // extension of Level can't substitute stock item
+ //
+ return this;
+ }
+
+}
+
diff --git a/log4j-1.2-api/src/src/main/java/org/apache/log4j/LogManager.java b/log4j-1.2-api/src/src/main/java/org/apache/log4j/LogManager.java
new file mode 100644
index 0000000..c4966aa
--- /dev/null
+++ b/log4j-1.2-api/src/src/main/java/org/apache/log4j/LogManager.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.log4j;
+
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.log4j.helpers.NullEnumeration;
+import org.apache.log4j.legacy.core.ContextUtil;
+import org.apache.log4j.or.ObjectRenderer;
+import org.apache.log4j.or.RendererSupport;
+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.spi.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 static final boolean isLog4jCore;
+
+ static {
+ boolean core = false;
+ try {
+ if (Class.forName("org.apache.logging.log4j.core.LoggerContext") != null) {
+ core = true;
+ }
+ } catch (Exception ex) {
+ // Ignore the exception;
+ }
+ isLog4jCore = core;
+ }
+
+ 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() {
+ if (isLog4jCore) {
+ final LoggerContext ctx = PrivateManager.getContext();
+ ContextUtil.reconfigure(ctx);
+ }
+ }
+
+ /**
+ * 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, RendererSupport {
+
+ private final Map<Class<?>, ObjectRenderer> rendererMap = new HashMap<>();
+
+ @Override
+ public Map<Class<?>, ObjectRenderer> getRendererMap() {
+ return rendererMap;
+ }
+
+ @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 getContext(FQCN, false);
+ }
+
+ public static org.apache.logging.log4j.Logger getLogger(final String name) {
+ return getLogger(FQCN, name);
+ }
+ }
+}
diff --git a/log4j-1.2-api/src/src/main/java/org/apache/log4j/Logger.java b/log4j-1.2-api/src/src/main/java/org/apache/log4j/Logger.java
new file mode 100644
index 0000000..fe1f26a
--- /dev/null
+++ b/log4j-1.2-api/src/src/main/java/org/apache/log4j/Logger.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;
+
+
+import org.apache.log4j.spi.LoggerFactory;
+import org.apache.logging.log4j.spi.LoggerContext;
+
+/**
+ *
+ */
+public class Logger extends Category {
+
+ protected Logger(final String name) {
+ super(PrivateManager.getContext(), name);
+ }
+
+ Logger(final LoggerContext context, final String name) {
+ super(context, name);
+ }
+
+ 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);
+ }
+
+ public static Logger getRootLogger() {
+ return Category.getRoot(PrivateManager.getContext());
+ }
+
+ public static Logger getLogger(final String name, final LoggerFactory factory) {
+ return Category.getInstance(PrivateManager.getContext(), name, factory);
+ }
+
+ /**
+ * Internal Log Manager.
+ */
+ private static class PrivateManager extends org.apache.logging.log4j.LogManager {
+ private static final String FQCN = Logger.class.getName();
+
+ public static LoggerContext getContext() {
+ return getContext(FQCN, false);
+ }
+
+ public static org.apache.logging.log4j.Logger getLogger(final String name) {
+ return getLogger(FQCN, name);
+ }
+ }
+}
diff --git a/log4j-1.2-api/src/src/main/java/org/apache/log4j/MDC.java b/log4j-1.2-api/src/src/main/java/org/apache/log4j/MDC.java
new file mode 100644
index 0000000..ee7631a
--- /dev/null
+++ b/log4j-1.2-api/src/src/main/java/org/apache/log4j/MDC.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.HashMap;
+import java.util.Hashtable;
+import java.util.Map;
+
+import org.apache.logging.log4j.ThreadContext;
+
+/**
+ * This class behaves just like Log4j's MDC would - and so can cause issues with the redeployment of web
+ * applications if the Objects stored in the threads Map cannot be garbage collected.
+ */
+public final class MDC {
+
+
+ private static ThreadLocal<Map<String, Object>> localMap =
+ new InheritableThreadLocal<Map<String, Object>>() {
+ @Override
+ protected Map<String, Object> initialValue() {
+ return new HashMap<>();
+ }
+
+ @Override
+ protected Map<String, Object> childValue(final Map<String, Object> parentValue) {
+ return parentValue == null ? new HashMap<String, Object>() : 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());
+ }
+
+ public static Object get(final String key) {
+ return localMap.get().get(key);
+ }
+
+ public static void remove(final String key) {
+ localMap.get().remove(key);
+ ThreadContext.remove(key);
+ }
+
+ public static void clear() {
+ localMap.get().clear();
+ ThreadContext.clearMap();
+ }
+
+ public static Hashtable<String, Object> getContext() {
+ return new Hashtable<>(localMap.get());
+ }
+}
diff --git a/log4j-1.2-api/src/src/main/java/org/apache/log4j/NDC.java b/log4j-1.2-api/src/src/main/java/org/apache/log4j/NDC.java
new file mode 100644
index 0000000..a4e23dc
--- /dev/null
+++ b/log4j-1.2-api/src/src/main/java/org/apache/log4j/NDC.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.log4j;
+
+import java.util.Stack;
+
+/**
+ *
+ */
+public final class NDC {
+
+ private NDC() {
+ }
+
+ /**
+ * Clear any nested diagnostic information if any. This method is
+ * useful in cases where the same thread can be potentially used
+ * over and over in different unrelated contexts.
+ * <p>
+ * This method is equivalent to calling the {@link #setMaxDepth}
+ * method with a zero <code>maxDepth</code> argument.
+ * </p>
+ */
+ public static void clear() {
+ org.apache.logging.log4j.ThreadContext.clearStack();
+ }
+
+
+ /**
+ * Clone the diagnostic context for the current thread.
+ * <p>
+ * Internally a diagnostic context is represented as a stack. A
+ * given thread can supply the stack (i.e. diagnostic context) to a
+ * child thread so that the child can inherit the parent thread's
+ * diagnostic context.
+ * </p>
+ * <p>
+ * The child thread uses the {@link #inherit inherit} method to
+ * inherit the parent's diagnostic context.
+ * </p>
+ * @return Stack A clone of the current thread's diagnostic context.
+ */
+ @SuppressWarnings("rawtypes")
+ public static Stack cloneStack() {
+ final Stack<String> stack = new Stack<>();
+ for (final String element : org.apache.logging.log4j.ThreadContext.cloneStack().asList()) {
+ stack.push(element);
+ }
+ return stack;
+ }
+
+
+ /**
+ * Inherit the diagnostic context of another thread.
+ * <p>
+ * The parent thread can obtain a reference to its diagnostic
+ * context using the {@link #cloneStack} method. It should
+ * communicate this information to its child so that it may inherit
+ * the parent's diagnostic context.
+ * </p>
+ * <p>
+ * The parent's diagnostic context is cloned before being
+ * inherited. In other words, once inherited, the two diagnostic
+ * contexts can be managed independently.
+ * </p>
+ * <p>
+ * In java, a child thread cannot obtain a reference to its
+ * parent, unless it is directly handed the reference. Consequently,
+ * there is no client-transparent way of inheriting diagnostic
+ * contexts. Do you know any solution to this problem?
+ * </p>
+ * @param stack The diagnostic context of the parent thread.
+ */
+ public static void inherit(final Stack<String> stack) {
+ org.apache.logging.log4j.ThreadContext.setStack(stack);
+ }
+
+
+ /**
+ * <strong style="color:#FF4040">Never use this method directly.</strong>
+ *
+ * @return The string value of the specified key.
+ */
+ public static String get() {
+ return org.apache.logging.log4j.ThreadContext.peek();
+ }
+
+ /**
+ * Get the current nesting depth of this diagnostic context.
+ * @return int The number of elements in the call stack.
+ * @see #setMaxDepth
+ */
+ public static int getDepth() {
+ return org.apache.logging.log4j.ThreadContext.getDepth();
+ }
+
+ /**
+ * Clients should call this method before leaving a diagnostic
+ * context.
+ * <p>
+ * The returned value is the value that was pushed last. If no
+ * context is available, then the empty string "" is returned.
+ * </p>
+ * @return String The innermost diagnostic context.
+ */
+ public static String pop() {
+ return org.apache.logging.log4j.ThreadContext.pop();
+ }
+
+ /**
+ * Looks at the last diagnostic context at the top of this NDC
+ * without removing it.
+ * <p>
+ * The returned value is the value that was pushed last. If no
+ * context is available, then the empty string "" is returned.
+ * </p>
+ * @return String The innermost diagnostic context.
+ */
+ public static String peek() {
+ return org.apache.logging.log4j.ThreadContext.peek();
+ }
+
+ /**
+ * Push new diagnostic context information for the current thread.
+ * <p>
+ * The contents of the <code>message</code> parameter is
+ * determined solely by the client.
+ * </p>
+ * @param message The new diagnostic context information.
+ */
+ public static void push(final String message) {
+ org.apache.logging.log4j.ThreadContext.push(message);
+ }
+
+ /**
+ * Remove the diagnostic context for this thread.
+ * <p>
+ * Each thread that created a diagnostic context by calling
+ * {@link #push} should call this method before exiting. Otherwise,
+ * the memory used by the <b>thread</b> cannot be reclaimed by the
+ * VM.
+ * </p>
+ * <p>
+ * As this is such an important problem in heavy duty systems and
+ * because it is difficult to always guarantee that the remove
+ * method is called before exiting a thread, this method has been
+ * augmented to lazily remove references to dead threads. In
+ * practice, this means that you can be a little sloppy and
+ * occasionally forget to call {@code remove} before exiting a
+ * thread. However, you must call <code>remove</code> sometime. If
+ * you never call it, then your application is sure to run out of
+ * memory.
+ * </p>
+ */
+ public static void remove() {
+ org.apache.logging.log4j.ThreadContext.removeStack();
+ }
+
+ /**
+ * Set maximum depth of this diagnostic context. If the current
+ * depth is smaller or equal to <code>maxDepth</code>, then no
+ * action is taken.
+ * <p>
+ * This method is a convenient alternative to multiple {@link
+ * #pop} calls. Moreover, it is often the case that at the end of
+ * complex call sequences, the depth of the NDC is
+ * unpredictable. The <code>setMaxDepth</code> method circumvents
+ * this problem.
+ * </p>
+ * <p>
+ * For example, the combination
+ * </p>
+ * <pre>
+ * void foo() {
+ * int depth = NDC.getDepth();
+ *
+ * ... complex sequence of calls
+ *
+ * NDC.setMaxDepth(depth);
+ * }
+ * </pre>
+ * <p>
+ * ensures that between the entry and exit of foo the depth of the
+ * diagnostic stack is conserved.
+ * </p>
+ *
+ * @see #getDepth
+ * @param maxDepth The maximum depth of the stack.
+ */
+ public static void setMaxDepth(final int maxDepth) {
+ org.apache.logging.log4j.ThreadContext.trim(maxDepth);
+ }
+}
diff --git a/log4j-1.2-api/src/src/main/java/org/apache/log4j/PatternLayout.java b/log4j-1.2-api/src/src/main/java/org/apache/log4j/PatternLayout.java
new file mode 100644
index 0000000..c2e1251
--- /dev/null
+++ b/log4j-1.2-api/src/src/main/java/org/apache/log4j/PatternLayout.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.log4j;
+
+import org.apache.log4j.spi.LoggingEvent;
+import org.apache.logging.log4j.util.Strings;
+
+/**
+ *
+ */
+public class PatternLayout extends Layout {
+
+ public PatternLayout(final String pattern) {
+
+ }
+
+ @Override
+ public String format(final LoggingEvent event) {
+ return Strings.EMPTY;
+ }
+
+ @Override
+ public boolean ignoresThrowable() {
+ return true;
+ }
+}
diff --git a/log4j-1.2-api/src/src/main/java/org/apache/log4j/Priority.java b/log4j-1.2-api/src/src/main/java/org/apache/log4j/Priority.java
new file mode 100644
index 0000000..8f6eee9
--- /dev/null
+++ b/log4j-1.2-api/src/src/main/java/org/apache/log4j/Priority.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;
+
+/**
+ * <em style="color:#A44">Refrain from using this class directly, use
+ * the {@link Level} class instead.</em>
+ */
+public class Priority {
+
+ /**
+ * The <code>OFF</code> has the highest possible rank and is
+ * intended to turn off logging.
+ */
+ public static final int OFF_INT = Integer.MAX_VALUE;
+ /**
+ * The <code>FATAL</code> level designates very severe error
+ * events that will presumably lead the application to abort.
+ */
+ public static final int FATAL_INT = 50000;
+ /**
+ * The <code>ERROR</code> level designates error events that
+ * might still allow the application to continue running.
+ */
+ public static final int ERROR_INT = 40000;
+ /**
+ * The <code>WARN</code> level designates potentially harmful situations.
+ */
+ public static final int WARN_INT = 30000;
+ /**
+ * The <code>INFO</code> level designates informational messages
+ * that highlight the progress of the application at coarse-grained
+ * level.
+ */
+ public static final int INFO_INT = 20000;
+ /**
+ * The <code>DEBUG</code> Level designates fine-grained
+ * informational events that are most useful to debug an
+ * application.
+ */
+ public static final int DEBUG_INT = 10000;
+ //public final static int FINE_INT = DEBUG_INT;
+ /**
+ * The <code>ALL</code> has the lowest possible rank and is intended to
+ * turn on all logging.
+ */
+ public static final int ALL_INT = Integer.MIN_VALUE;
+
+ /**
+ * @deprecated Use {@link Level#FATAL} instead.
+ */
+ @Deprecated
+ public static final Priority FATAL = new Level(FATAL_INT, "FATAL", 0);
+
+ /**
+ * @deprecated Use {@link Level#ERROR} instead.
+ */
+ @Deprecated
+ public static final Priority ERROR = new Level(ERROR_INT, "ERROR", 3);
+
+ /**
+ * @deprecated Use {@link Level#WARN} instead.
+ */
+ @Deprecated
+ public static final Priority WARN = new Level(WARN_INT, "WARN", 4);
+
+ /**
+ * @deprecated Use {@link Level#INFO} instead.
+ */
+ @Deprecated
+ public static final Priority INFO = new Level(INFO_INT, "INFO", 6);
+
+ /**
+ * @deprecated Use {@link Level#DEBUG} instead.
+ */
+ @Deprecated
+ public static final Priority DEBUG = new Level(DEBUG_INT, "DEBUG", 7);
+
+ /*
+ * These variables should be private but were not in Log4j 1.2 so are left the same way here.
+ */
+ transient int level;
+ transient String levelStr;
+ transient int syslogEquivalent;
+
+ /**
+ * Default constructor for deserialization.
+ */
+ protected Priority() {
+ level = DEBUG_INT;
+ levelStr = "DEBUG";
+ syslogEquivalent = 7;
+ }
+
+ /**
+ * Instantiate a level object.
+ * @param level The level value.
+ * @param levelStr The level name.
+ * @param syslogEquivalent The equivalent syslog value.
+ */
+ protected Priority(final int level, final String levelStr, final int syslogEquivalent) {
+ this.level = level;
+ this.levelStr = levelStr;
+ this.syslogEquivalent = syslogEquivalent;
+ }
+
+ /**
+ * Two priorities are equal if their level fields are equal.
+ * @param o The Object to check.
+ * @return true if the objects are equal, false otherwise.
+ *
+ * @since 1.2
+ */
+ @Override
+ public boolean equals(final Object o) {
+ if (o instanceof Priority) {
+ final Priority r = (Priority) o;
+ return this.level == r.level;
+ }
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ return this.level;
+ }
+
+ /**
+ * Returns the syslog equivalent of this priority as an integer.
+ * @return The equivalent syslog value.
+ */
+ public
+ final int getSyslogEquivalent() {
+ return syslogEquivalent;
+ }
+
+
+ /**
+ * Returns {@code true} if this level has a higher or equal
+ * level than the level passed as argument, {@code false}
+ * otherwise.
+ * <p>
+ * You should think twice before overriding the default
+ * implementation of <code>isGreaterOrEqual</code> method.
+ * </p>
+ * @param r The Priority to check.
+ * @return true if the current level is greater or equal to the specified Priority.
+ */
+ public boolean isGreaterOrEqual(final Priority r) {
+ return level >= r.level;
+ }
+
+ /**
+ * Returns all possible priorities as an array of Level objects in
+ * descending order.
+ * @return An array of all possible Priorities.
+ *
+ * @deprecated This method will be removed with no replacement.
+ */
+ @Deprecated
+ public static Priority[] getAllPossiblePriorities() {
+ 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.
+ */
+ @Override
+ public final String toString() {
+ return levelStr;
+ }
+
+ /**
+ * Returns the integer representation of this level.
+ * @return The integer value of this level.
+ */
+ public final int toInt() {
+ return level;
+ }
+
+ /**
+ * @param sArg The name of the Priority.
+ * @return The Priority matching the name.
+ * @deprecated Please use the {@link Level#toLevel(String)} method instead.
+ */
+ @Deprecated
+ public static Priority toPriority(final String sArg) {
+ return Level.toLevel(sArg);
+ }
+
+ /**
+ * @param val The value of the Priority.
+ * @return The Priority matching the value.
+ * @deprecated Please use the {@link Level#toLevel(int)} method instead.
+ */
+ @Deprecated
+ public static Priority toPriority(final int val) {
+ return toPriority(val, Priority.DEBUG);
+ }
+
+ /**
+ * @param val The value of the Priority.
+ * @param defaultPriority The default Priority to use if the value is invalid.
+ * @return The Priority matching the value or the default Priority if no match is found.
+ * @deprecated Please use the {@link Level#toLevel(int, Level)} method instead.
+ */
+ @Deprecated
+ public static Priority toPriority(final int val, final Priority defaultPriority) {
+ return Level.toLevel(val, (Level) defaultPriority);
+ }
+
+ /**
+ * @param sArg The name of the Priority.
+ * @param defaultPriority The default Priority to use if the name is not found.
+ * @return The Priority matching the name or the default Priority if no match is found.
+ * @deprecated Please use the {@link Level#toLevel(String, Level)} method instead.
+ */
+ @Deprecated
+ public static Priority toPriority(final String sArg, final Priority defaultPriority) {
+ return Level.toLevel(sArg, (Level) defaultPriority);
+ }
+}
diff --git a/log4j-1.2-api/src/src/main/java/org/apache/log4j/PropertyConfigurator.java b/log4j-1.2-api/src/src/main/java/org/apache/log4j/PropertyConfigurator.java
new file mode 100644
index 0000000..0fe1fe0
--- /dev/null
+++ b/log4j-1.2-api/src/src/main/java/org/apache/log4j/PropertyConfigurator.java
@@ -0,0 +1,126 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.log4j;
+
+import java.io.InputStream;
+import java.net.URL;
+import java.util.Properties;
+
+import org.apache.log4j.spi.LoggerRepository;
+
+/**
+ * A configurator for properties.
+ */
+public class PropertyConfigurator {
+
+ /**
+ * Read configuration options from configuration file.
+ *
+ * @param configFileName The configuration file
+ * @param hierarchy The hierarchy
+ */
+ public void doConfigure(final String configFileName, final LoggerRepository hierarchy) {
+
+ }
+
+ /**
+ * Read configuration options from <code>properties</code>.
+ *
+ * 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) {
+ }
+
+ /**
+ * Read configuration options from an InputStream.
+ *
+ * @param inputStream The input stream
+ * @param hierarchy The hierarchy
+ */
+ public void doConfigure(final InputStream inputStream, final LoggerRepository hierarchy) {
+ }
+
+ /**
+ * Read configuration options from url <code>configURL</code>.
+ *
+ * @param configURL The configuration URL
+ * @param hierarchy The hierarchy
+ */
+ public void doConfigure(final URL configURL, final LoggerRepository hierarchy) {
+ }
+
+ /**
+ * Read configuration options from configuration file.
+ *
+ * @param configFileName The configuration file.
+ */
+ public static void configure(final String configFileName) {
+ }
+
+ /**
+ * Read configuration options from url <code>configURL</code>.
+ *
+ * @param configURL The configuration URL
+ */
+ public static void configure(final URL configURL) {
+ }
+
+ /**
+ * Reads configuration options from an InputStream.
+ *
+ * @param inputStream The input stream
+ */
+ public static void configure(final InputStream inputStream) {
+ }
+
+ /**
+ * Read configuration options from <code>properties</code>.
+ *
+ * See {@link #doConfigure(String, LoggerRepository)} for the expected format.
+ *
+ * @param properties The properties
+ */
+ public static void configure(final Properties properties) {
+ }
+
+ /**
+ * Like {@link #configureAndWatch(String, long)} except that the
+ * default delay as defined by FileWatchdog.DEFAULT_DELAY is
+ * used.
+ *
+ * @param configFilename A file in key=value format.
+ */
+ public static void configureAndWatch(final String configFilename) {
+ }
+
+ /**
+ * Read the configuration file <code>configFilename</code> if it
+ * exists. Moreover, a thread will be created that will periodically
+ * check if <code>configFilename</code> has been created or
+ * modified. The period is determined by the <code>delay</code>
+ * argument. If a change or file creation is detected, then
+ * <code>configFilename</code> is read to configure log4j.
+ *
+ * @param configFilename A file in key=value format.
+ * @param delay The delay in milliseconds to wait between each check.
+ */
+ public static void configureAndWatch(final String configFilename, final long delay) {
+ }
+}
diff --git a/log4j-1.2-api/src/src/main/java/org/apache/log4j/RenderedMessage.java b/log4j-1.2-api/src/src/main/java/org/apache/log4j/RenderedMessage.java
new file mode 100644
index 0000000..422a565
--- /dev/null
+++ b/log4j-1.2-api/src/src/main/java/org/apache/log4j/RenderedMessage.java
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.log4j;
+
+import org.apache.log4j.or.ObjectRenderer;
+import org.apache.logging.log4j.message.Message;
+
+/**
+ * Implements object rendering for Log4j 1.x compatibility.
+ */
+public class RenderedMessage implements Message {
+
+ private final ObjectRenderer renderer;
+ private final Object object;
+ private String rendered = null;
+
+ public RenderedMessage(ObjectRenderer renderer, Object object) {
+ this.renderer = renderer;
+ this.object = object;
+ }
+
+
+ @Override
+ public String getFormattedMessage() {
+ if (rendered == null) {
+ rendered = renderer.doRender(object);
+ }
+
+ return rendered;
+ }
+
+ @Override
+ public String getFormat() {
+ return getFormattedMessage();
+ }
+
+ @Override
+ public Object[] getParameters() {
+ return null;
+ }
+
+ @Override
+ public Throwable getThrowable() {
+ return null;
+ }
+}
diff --git a/log4j-1.2-api/src/src/main/java/org/apache/log4j/SimpleLayout.java b/log4j-1.2-api/src/src/main/java/org/apache/log4j/SimpleLayout.java
new file mode 100644
index 0000000..c77b9be
--- /dev/null
+++ b/log4j-1.2-api/src/src/main/java/org/apache/log4j/SimpleLayout.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;
+
+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/src/main/java/org/apache/log4j/WriterAppender.java b/log4j-1.2-api/src/src/main/java/org/apache/log4j/WriterAppender.java
new file mode 100644
index 0000000..f026bfa
--- /dev/null
+++ b/log4j-1.2-api/src/src/main/java/org/apache/log4j/WriterAppender.java
@@ -0,0 +1,378 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.LoggingEvent;
+import org.apache.logging.log4j.status.StatusLogger;
+
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+
+
+/**
+ * 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 <code>immediateFlush</code> is set to
+ * <code>false</code>, 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.
+ *
+ * <p>The <code>immediateFlush</code> variable is set to
+ * <code>true</code> by default.
+ */
+ protected boolean immediateFlush = true;
+
+ /**
+ * The encoding to use when writing. <p>The
+ * <code>encoding</code> variable is set to <code>null</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 <code>os</code>
+ * as its {@link OutputStream}.
+ */
+ public WriterAppender(Layout layout, OutputStream os) {
+ this(layout, new OutputStreamWriter(os));
+ }
+
+ /**
+ * Instantiate a WriterAppender and set the output destination to
+ * <code>writer</code>.
+ *
+ * <p>The <code>writer</code> must have been previously opened by
+ * the user.
+ */
+ public WriterAppender(Layout layout, Writer writer) {
+ this.layout = layout;
+ this.setWriter(writer);
+ }
+
+ /**
+ * Returns value of the <b>ImmediateFlush</b> option.
+ */
+ public boolean getImmediateFlush() {
+ return immediateFlush;
+ }
+
+ /**
+ * If the <b>ImmediateFlush</b> option is set to
+ * <code>true</code>, the appender will flush at the end of each
+ * write. This is the default behavior. If the option is set to
+ * <code>false</code>, then the underlying stream can defer writing
+ * to physical medium to a later time.
+ *
+ * <p>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.
+ */
+ public void setImmediateFlush(boolean value) {
+ immediateFlush = value;
+ }
+
+ /**
+ * Does nothing.
+ */
+ public void activateOptions() {
+ }
+
+
+ /**
+ * This method is called by the {@link AppenderSkeleton#doAppend}
+ * method.
+ *
+ * <p>If the output stream exists and is writable then write a log
+ * statement to the output stream. Otherwise, write a single warning
+ * message to <code>System.err</code>.
+ *
+ * <p>The format of the output will depend on this appender's
+ * layout.
+ */
+ public void append(LoggingEvent event) {
+
+ // Reminder: the nesting of calls is:
+ //
+ // doAppend()
+ // - check threshold
+ // - filter
+ // - append();
+ // - checkEntryConditions();
+ // - subAppend();
+
+ if (!checkEntryConditions()) {
+ return;
+ }
+ subAppend(event);
+ }
+
+ /**
+ * This method determines if there is a sense in attempting to append.
+ *
+ * <p>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 <code>false</code> is returned.
+ */
+ 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.
+ *
+ * <p>Closed appenders cannot be reused.
+ *
+ * @see #setWriter
+ * @since 0.8.4
+ */
+ 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
+ * <code>encoding</code> 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.
+ */
+ protected OutputStreamWriter createWriter(OutputStream os) {
+ OutputStreamWriter retval = null;
+
+ String enc = getEncoding();
+ if (enc != null) {
+ try {
+ retval = new OutputStreamWriter(os, enc);
+ } catch (IOException e) {
+ if (e instanceof InterruptedIOException) {
+ Thread.currentThread().interrupt();
+ }
+ LOGGER.warn("Error initializing output writer.");
+ LOGGER.warn("Unsupported encoding?");
+ }
+ }
+ if (retval == null) {
+ retval = new OutputStreamWriter(os);
+ }
+ return retval;
+ }
+
+ public String getEncoding() {
+ return encoding;
+ }
+
+ public void setEncoding(String value) {
+ encoding = value;
+ }
+
+
+ /**
+ * Set the {@link ErrorHandler} for this WriterAppender and also the
+ * underlying {@link QuietWriter} if any.
+ */
+ public synchronized void setErrorHandler(ErrorHandler eh) {
+ if (eh == null) {
+ LOGGER.warn("You have tried to set a null error-handler.");
+ } else {
+ this.errorHandler = eh;
+ if (this.qw != null) {
+ this.qw.setErrorHandler(eh);
+ }
+ }
+ }
+
+ /**
+ * <p>Sets the Writer where the log output will go. The
+ * specified Writer must be opened by the user and be
+ * writable.
+ *
+ * <p>The <code>java.io.Writer</code> will be closed when the
+ * appender instance is closed.
+ *
+ *
+ * <p><b>WARNING:</b> Logging to an unopened Writer will fail.
+ * <p>
+ *
+ * @param writer An already opened Writer.
+ */
+ public synchronized void setWriter(Writer writer) {
+ reset();
+ this.qw = new QuietWriter(writer, errorHandler);
+ //this.tp = new TracerPrintWriter(qw);
+ writeHeader();
+ }
+
+
+ /**
+ * Actual writing occurs here.
+ *
+ * <p>Most subclasses of <code>WriterAppender</code> will need to
+ * override this method.
+ *
+ * @since 0.9.0
+ */
+ protected void subAppend(LoggingEvent event) {
+ this.qw.write(this.layout.format(event));
+
+ if (layout.ignoresThrowable()) {
+ String[] s = event.getThrowableStrRep();
+ if (s != null) {
+ int len = s.length;
+ for (int i = 0; i < len; i++) {
+ this.qw.write(s[i]);
+ this.qw.write(Layout.LINE_SEP);
+ }
+ }
+ }
+
+ if (shouldFlush(event)) {
+ this.qw.flush();
+ }
+ }
+
+
+ /**
+ * The WriterAppender requires a layout. Hence, this method returns
+ * <code>true</code>.
+ */
+ public boolean requiresLayout() {
+ return true;
+ }
+
+ /**
+ * Clear internal references to the writer and other variables.
+ * <p>
+ * Subclasses can override this method for an alternate closing
+ * behavior.
+ */
+ protected void reset() {
+ closeWriter();
+ this.qw = null;
+ //this.tp = null;
+ }
+
+
+ /**
+ * Write a footer as produced by the embedded layout's {@link
+ * Layout#getFooter} method.
+ */
+ protected void writeFooter() {
+ if (layout != null) {
+ String f = layout.getFooter();
+ if (f != null && this.qw != null) {
+ this.qw.write(f);
+ this.qw.flush();
+ }
+ }
+ }
+
+ /**
+ * Write a header as produced by the embedded layout's {@link
+ * Layout#getHeader} method.
+ */
+ protected void writeHeader() {
+ if (layout != null) {
+ String h = layout.getHeader();
+ if (h != null && this.qw != null) {
+ this.qw.write(h);
+ }
+ }
+ }
+
+ /**
+ * Determines whether the writer should be flushed after
+ * this event is written.
+ *
+ * @since 1.2.16
+ */
+ protected boolean shouldFlush(final LoggingEvent event) {
+ return immediateFlush;
+ }
+}
diff --git a/log4j-1.2-api/src/src/main/java/org/apache/log4j/bridge/AppenderAdapter.java b/log4j-1.2-api/src/src/main/java/org/apache/log4j/bridge/AppenderAdapter.java
new file mode 100644
index 0000000..f2dd2c1
--- /dev/null
+++ b/log4j-1.2-api/src/src/main/java/org/apache/log4j/bridge/AppenderAdapter.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.bridge;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.List;
+
+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.core.filter.CompositeFilter;
+
+/**
+ * Binds a Log4j 1.x Appender to Log4j 2.
+ */
+public class AppenderAdapter {
+
+ private final Appender appender;
+ private final Adapter adapter;
+
+ /**
+ * Constructor.
+ */
+ public AppenderAdapter(Appender appender) {
+ this.appender = appender;
+ org.apache.logging.log4j.core.Filter appenderFilter = null;
+ if (appender.getFilter() != null) {
+ if (appender.getFilter().getNext() != null) {
+ org.apache.log4j.spi.Filter filter = appender.getFilter();
+ List<org.apache.logging.log4j.core.Filter> filters = new ArrayList<>();
+ while (filter != null) {
+ filters.add(new FilterAdapter(filter));
+ filter = filter.getNext();
+ }
+ appenderFilter = CompositeFilter.createFilters(filters.toArray(new Filter[0]));
+ } else {
+ appenderFilter = new FilterAdapter(appender.getFilter());
+ }
+ }
+ this.adapter = new Adapter(appender.getName(), appenderFilter, null, true, null);
+ }
+
+ public Adapter getAdapter() {
+ return adapter;
+ }
+
+ public class Adapter extends AbstractAppender {
+
+ protected Adapter(final String name, final Filter filter, final Layout<? extends Serializable> layout,
+ final boolean ignoreExceptions, final Property[] properties) {
+ super(name, filter, layout, ignoreExceptions, properties);
+ }
+
+ @Override
+ public void append(LogEvent event) {
+ appender.doAppend(new LogEventAdapter(event));
+ }
+
+ @Override
+ public void stop() {
+ appender.close();
+ }
+
+ public Appender getAppender() {
+ return appender;
+ }
+ }
+}
diff --git a/log4j-1.2-api/src/src/main/java/org/apache/log4j/bridge/AppenderWrapper.java b/log4j-1.2-api/src/src/main/java/org/apache/log4j/bridge/AppenderWrapper.java
new file mode 100644
index 0000000..f06f909
--- /dev/null
+++ b/log4j-1.2-api/src/src/main/java/org/apache/log4j/bridge/AppenderWrapper.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 org.apache.log4j.Appender;
+import org.apache.log4j.Layout;
+import org.apache.log4j.spi.ErrorHandler;
+import org.apache.log4j.spi.Filter;
+import org.apache.log4j.spi.LoggingEvent;
+
+/**
+ * Holds 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 final org.apache.logging.log4j.core.Appender appender;
+
+ public AppenderWrapper(org.apache.logging.log4j.core.Appender appender) {
+ this.appender = appender;
+ }
+
+ public org.apache.logging.log4j.core.Appender getAppender() {
+ return appender;
+ }
+
+ @Override
+ public void addFilter(Filter newFilter) {
+ }
+
+ @Override
+ public Filter getFilter() {
+ return null;
+ }
+
+ @Override
+ public void clearFilters() {
+
+ }
+
+ @Override
+ public void close() {
+ // Not supported with Log4j 2.
+ }
+
+ @Override
+ public void doAppend(LoggingEvent event) {
+ if (event instanceof LogEventAdapter) {
+ appender.append(((LogEventAdapter) event).getEvent());
+ }
+ }
+
+ @Override
+ public String getName() {
+ return appender.getName();
+ }
+
+ @Override
+ public void setErrorHandler(ErrorHandler errorHandler) {
+ appender.setHandler(new ErrorHandlerAdapter(errorHandler));
+ }
+
+ @Override
+ public ErrorHandler getErrorHandler() {
+ return ((ErrorHandlerAdapter)appender.getHandler()).getHandler();
+ }
+
+ @Override
+ public void setLayout(Layout layout) {
+ // Log4j 2 doesn't support this.
+ }
+
+ @Override
+ public Layout getLayout() {
+ return new LayoutWrapper(appender.getLayout());
+ }
+
+ @Override
+ public void setName(String name) {
+ // Log4j 2 doesn't support this.
+ }
+
+ @Override
+ public boolean requiresLayout() {
+ return false;
+ }
+}
diff --git a/log4j-1.2-api/src/src/main/java/org/apache/log4j/bridge/ErrorHandlerAdapter.java b/log4j-1.2-api/src/src/main/java/org/apache/log4j/bridge/ErrorHandlerAdapter.java
new file mode 100644
index 0000000..1f166a2
--- /dev/null
+++ b/log4j-1.2-api/src/src/main/java/org/apache/log4j/bridge/ErrorHandlerAdapter.java
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.log4j.bridge;
+
+import org.apache.log4j.spi.ErrorHandler;
+import org.apache.logging.log4j.core.LogEvent;
+
+/**
+ * Makes a Log4j 1 ErrorHandler usable by a Log4j 2 Appender.
+ */
+public class ErrorHandlerAdapter implements org.apache.logging.log4j.core.ErrorHandler {
+
+ private final ErrorHandler errorHandler;
+
+ public ErrorHandlerAdapter(ErrorHandler errorHandler) {
+ this.errorHandler = errorHandler;
+ }
+
+ public ErrorHandler getHandler() {
+ return errorHandler;
+ }
+
+ @Override
+ public void error(String msg) {
+ errorHandler.error(msg);
+ }
+
+ @Override
+ public void error(String msg, Throwable t) {
+ if (t instanceof Exception) {
+ errorHandler.error(msg, (Exception) t, 0);
+ } else {
+ errorHandler.error(msg);
+ }
+ }
+
+ @Override
+ public void error(String msg, LogEvent event, Throwable t) {
+ if (t == null || t instanceof Exception) {
+ errorHandler.error(msg, (Exception) t, 0, new LogEventAdapter(event));
+ } else {
+ errorHandler.error(msg);
+ }
+ }
+}
diff --git a/log4j-1.2-api/src/src/main/java/org/apache/log4j/bridge/FilterAdapter.java b/log4j-1.2-api/src/src/main/java/org/apache/log4j/bridge/FilterAdapter.java
new file mode 100644
index 0000000..2dff272
--- /dev/null
+++ b/log4j-1.2-api/src/src/main/java/org/apache/log4j/bridge/FilterAdapter.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.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;
+
+/**
+ * Binds a Log4j 1.x Filter with Log4j 2.
+ */
+public class FilterAdapter extends AbstractFilter {
+
+ private final Filter filter;
+
+ public FilterAdapter(Filter filter) {
+ this.filter = filter;
+ }
+
+ @Override
+ public void start() {
+ filter.activateOptions();
+ }
+
+ @Override
+ public Result filter(LogEvent event) {
+ LoggingEvent loggingEvent = new LogEventAdapter(event);
+ Filter next = filter;
+ while (next != null) {
+ switch (filter.decide(loggingEvent)) {
+ case Filter.ACCEPT:
+ return Result.ACCEPT;
+ case Filter.DENY:
+ return Result.DENY;
+ default:
+ }
+ next = filter.getNext();
+ }
+ return Result.NEUTRAL;
+ }
+}
diff --git a/log4j-1.2-api/src/src/main/java/org/apache/log4j/bridge/FilterWrapper.java b/log4j-1.2-api/src/src/main/java/org/apache/log4j/bridge/FilterWrapper.java
new file mode 100644
index 0000000..b2855cd
--- /dev/null
+++ b/log4j-1.2-api/src/src/main/java/org/apache/log4j/bridge/FilterWrapper.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.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;
+
+ public FilterWrapper(org.apache.logging.log4j.core.Filter filter) {
+ this.filter = filter;
+ }
+
+ public org.apache.logging.log4j.core.Filter getFilter() {
+ return filter;
+ }
+
+ /**
+ * This method is never called.
+ * @param event The LoggingEvent to decide upon.
+ * @return 0
+ */
+ @Override
+ public int decide(LoggingEvent event) {
+ return 0;
+ }
+}
diff --git a/log4j-1.2-api/src/src/main/java/org/apache/log4j/bridge/LayoutAdapter.java b/log4j-1.2-api/src/src/main/java/org/apache/log4j/bridge/LayoutAdapter.java
new file mode 100644
index 0000000..5494c92
--- /dev/null
+++ b/log4j-1.2-api/src/src/main/java/org/apache/log4j/bridge/LayoutAdapter.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.bridge;
+
+import org.apache.log4j.Layout;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.layout.ByteBufferDestination;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Class Description goes here.
+ */
+public class LayoutAdapter implements org.apache.logging.log4j.core.Layout<String> {
+ private Layout layout;
+
+ public LayoutAdapter(Layout layout) {
+ this.layout = layout;
+ }
+
+
+ @Override
+ public byte[] getFooter() {
+ return layout.getFooter() == null ? null : layout.getFooter().getBytes();
+ }
+
+ @Override
+ public byte[] getHeader() {
+ return layout.getHeader() == null ? null : layout.getHeader().getBytes();
+ }
+
+ @Override
+ public byte[] toByteArray(LogEvent event) {
+ String result = layout.format(new LogEventAdapter(event));
+ return result == null ? null : result.getBytes();
+ }
+
+ @Override
+ public String toSerializable(LogEvent event) {
+ return layout.format(new LogEventAdapter(event));
+ }
+
+ @Override
+ public String getContentType() {
+ return layout.getContentType();
+ }
+
+ @Override
+ public Map<String, String> getContentFormat() {
+ return new HashMap<>();
+ }
+
+ @Override
+ public void encode(LogEvent event, ByteBufferDestination destination) {
+ final byte[] data = toByteArray(event);
+ destination.writeBytes(data, 0, data.length);
+ }
+}
diff --git a/log4j-1.2-api/src/src/main/java/org/apache/log4j/bridge/LayoutWrapper.java b/log4j-1.2-api/src/src/main/java/org/apache/log4j/bridge/LayoutWrapper.java
new file mode 100644
index 0000000..fc1e72f
--- /dev/null
+++ b/log4j-1.2-api/src/src/main/java/org/apache/log4j/bridge/LayoutWrapper.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.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;
+
+ public LayoutWrapper(org.apache.logging.log4j.core.Layout<?> layout) {
+ this.layout = layout;
+ }
+
+ @Override
+ public String format(LoggingEvent event) {
+ return layout.toSerializable(((LogEventAdapter)event).getEvent()).toString();
+ }
+
+ @Override
+ public boolean ignoresThrowable() {
+ return false;
+ }
+
+ public org.apache.logging.log4j.core.Layout<?> getLayout() {
+ return this.layout;
+ }
+}
diff --git a/log4j-1.2-api/src/src/main/java/org/apache/log4j/bridge/LogEventAdapter.java b/log4j-1.2-api/src/src/main/java/org/apache/log4j/bridge/LogEventAdapter.java
new file mode 100644
index 0000000..9ad4d27
--- /dev/null
+++ b/log4j-1.2-api/src/src/main/java/org/apache/log4j/bridge/LogEventAdapter.java
@@ -0,0 +1,217 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.log4j.bridge;
+
+import java.lang.reflect.Method;
+
+import org.apache.log4j.Category;
+import org.apache.log4j.Level;
+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.spi.StandardLevel;
+import org.apache.logging.log4j.status.StatusLogger;
+
+/**
+ * Converts a Log4j 2 LogEvent into the components needed by a Log4j 1.x LoggingEvent.
+ * This class requires Log4j 2.
+ */
+public class LogEventAdapter extends LoggingEvent {
+
+ private static final long JVM_START_TIME = initStartTime();
+
+ private final LogEvent event;
+
+ public LogEventAdapter(LogEvent event) {
+ this.event = event;
+ }
+
+ 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 <code>level</code> field. */
+ @Override
+ public Level getLevel() {
+ switch (StandardLevel.getStandardLevel(event.getLevel().intLevel())) {
+ 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;
+ case ALL:
+ return Level.ALL;
+ default:
+ return Level.ERROR;
+ }
+ }
+
+ /**
+ * Return the name of the logger. Use this form instead of directly
+ * accessing the <code>categoryName</code> field.
+ */
+ @Override
+ public String getLoggerName() {
+ return event.getLoggerName();
+ }
+
+ /**
+ * 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().getFormattedMessage();
+ }
+
+ /*
+ * This method returns the NDC for this event.
+ */
+ @Override
+ public
+ String getNDC() {
+ return event.getContextStack().toString();
+ }
+
+
+ /*
+ Returns the the context corresponding to the <code>key</code> parameter.
+ */
+ @Override
+ public
+ Object getMDC(String key) {
+ if (event.getContextData() != null) {
+ return event.getContextData().getValue(key);
+ } else {
+ 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();
+ }
+
+ /**
+ Returns the time when the application started, in milliseconds
+ elapsed since 01.01.1970. */
+ public static long getStartTime() {
+ return JVM_START_TIME;
+ }
+
+ @Override
+ public
+ String getThreadName() {
+ return event.getThreadName();
+ }
+
+ /**
+ Returns the throwable information contained within this
+ event. May be <code>null</code> if there is no such information.
+
+ <p>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(new String[0]);
+ }
+ return null;
+ }
+
+ /**
+ * 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();
+ }
+ }
+}
diff --git a/log4j-1.2-api/src/src/main/java/org/apache/log4j/builders/BooleanHolder.java b/log4j-1.2-api/src/src/main/java/org/apache/log4j/builders/BooleanHolder.java
new file mode 100644
index 0000000..16e46d3
--- /dev/null
+++ b/log4j-1.2-api/src/src/main/java/org/apache/log4j/builders/BooleanHolder.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.log4j.builders;
+
+/**
+ * Holds Boolean values created inside of a Lambda expression.
+ */
+public class BooleanHolder extends Holder<Boolean> {
+ public BooleanHolder() {
+ super(Boolean.FALSE);
+ }
+
+ @Override
+ public void set(Boolean value) {
+ if (value != null) {
+ super.set(value);
+ }
+ }
+}
diff --git a/log4j-1.2-api/src/src/main/java/org/apache/log4j/builders/BuilderManager.java b/log4j-1.2-api/src/src/main/java/org/apache/log4j/builders/BuilderManager.java
new file mode 100644
index 0000000..c677513
--- /dev/null
+++ b/log4j-1.2-api/src/src/main/java/org/apache/log4j/builders/BuilderManager.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;
+
+import org.apache.log4j.Appender;
+import org.apache.log4j.Layout;
+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.spi.Filter;
+import org.apache.log4j.xml.XmlConfigurationFactory;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.core.config.plugins.util.PluginManager;
+import org.apache.logging.log4j.core.config.plugins.util.PluginType;
+import org.apache.logging.log4j.status.StatusLogger;
+import org.apache.logging.log4j.util.LoaderUtil;
+import org.w3c.dom.Element;
+
+import java.lang.reflect.InvocationTargetException;
+import java.util.Map;
+
+/**
+ *
+ */
+public class BuilderManager {
+
+ public static final String CATEGORY = "Log4j Builder";
+ private static final Logger LOGGER = StatusLogger.getLogger();
+ private final Map<String, PluginType<?>> plugins;
+
+ public BuilderManager() {
+ final PluginManager manager = new PluginManager(CATEGORY);
+ manager.collectPlugins();
+ plugins = manager.getPlugins();
+ }
+
+ public Appender parseAppender(String className, Element appenderElement, XmlConfigurationFactory factory) {
+ PluginType<?> plugin = plugins.get(className.toLowerCase());
+ if (plugin != null) {
+ try {
+ @SuppressWarnings("unchecked")
+ AppenderBuilder builder = (AppenderBuilder) LoaderUtil.newInstanceOf(plugin.getPluginClass());
+ return builder.parseAppender(appenderElement, factory);
+ } catch (InstantiationException | IllegalAccessException | InvocationTargetException ex) {
+ LOGGER.warn("Unable to load plugin: {} due to: {}", plugin.getKey(), ex.getMessage());
+ }
+ }
+ return null;
+ }
+
+ public Filter parseFilter(String className, Element filterElement, XmlConfigurationFactory factory) {
+ PluginType<?> plugin = plugins.get(className.toLowerCase());
+ if (plugin != null) {
+ try {
+ @SuppressWarnings("unchecked")
+ FilterBuilder builder = (FilterBuilder) LoaderUtil.newInstanceOf(plugin.getPluginClass());
+ return builder.parseFilter(filterElement, factory);
+ } catch (InstantiationException | IllegalAccessException | InvocationTargetException ex) {
+ LOGGER.warn("Unable to load plugin: {} due to: {}", plugin.getKey(), ex.getMessage());
+ }
+ }
+ return null;
+ }
+
+
+ public Layout parseLayout(String className, Element layoutElement, XmlConfigurationFactory factory) {
+ PluginType<?> plugin = plugins.get(className.toLowerCase());
+ if (plugin != null) {
+ try {
+ @SuppressWarnings("unchecked")
+ LayoutBuilder builder = (LayoutBuilder) LoaderUtil.newInstanceOf(plugin.getPluginClass());
+ return builder.parseLayout(layoutElement, factory);
+ } catch (InstantiationException | IllegalAccessException | InvocationTargetException ex) {
+ LOGGER.warn("Unable to load plugin: {} due to: {}", plugin.getKey(), ex.getMessage());
+ }
+ }
+ return null;
+ }
+
+}
diff --git a/log4j-1.2-api/src/src/main/java/org/apache/log4j/builders/Holder.java b/log4j-1.2-api/src/src/main/java/org/apache/log4j/builders/Holder.java
new file mode 100644
index 0000000..b9ce2bf
--- /dev/null
+++ b/log4j-1.2-api/src/src/main/java/org/apache/log4j/builders/Holder.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;
+
+/**
+ * Provides a place to hold values generated inside of a Lambda expression.
+ */
+public class Holder<T> {
+ private T value;
+
+ public Holder() {
+ }
+
+ public Holder(T defaultValue) {
+ this.value = defaultValue;
+ }
+
+ public void set(T value) {
+ this.value = value;
+ }
+
+ public T get() {
+ return value;
+ }
+}
diff --git a/log4j-1.2-api/src/src/main/java/org/apache/log4j/builders/appender/AppenderBuilder.java b/log4j-1.2-api/src/src/main/java/org/apache/log4j/builders/appender/AppenderBuilder.java
new file mode 100644
index 0000000..2b72efd
--- /dev/null
+++ b/log4j-1.2-api/src/src/main/java/org/apache/log4j/builders/appender/AppenderBuilder.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.log4j.builders.appender;
+
+import org.apache.log4j.Appender;
+import org.apache.log4j.xml.XmlConfigurationFactory;
+import org.apache.logging.log4j.core.config.ConfigurationFactory;
+import org.w3c.dom.Element;
+
+import java.util.function.BiFunction;
+
+/**
+ * Define an Appender Builder.
+ */
+public interface AppenderBuilder {
+
+ Appender parseAppender(Element element, XmlConfigurationFactory factory);
+
+}
diff --git a/log4j-1.2-api/src/src/main/java/org/apache/log4j/builders/appender/ConsoleAppenderBuilder.java b/log4j-1.2-api/src/src/main/java/org/apache/log4j/builders/appender/ConsoleAppenderBuilder.java
new file mode 100644
index 0000000..bfb7ff8
--- /dev/null
+++ b/log4j-1.2-api/src/src/main/java/org/apache/log4j/builders/appender/ConsoleAppenderBuilder.java
@@ -0,0 +1,111 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.log4j.builders.appender;
+
+import org.apache.log4j.Appender;
+import org.apache.log4j.Layout;
+import org.apache.log4j.bridge.AppenderWrapper;
+import org.apache.log4j.bridge.FilterAdapter;
+import org.apache.log4j.bridge.FilterWrapper;
+import org.apache.log4j.bridge.LayoutAdapter;
+import org.apache.log4j.bridge.LayoutWrapper;
+import org.apache.log4j.builders.Holder;
+import org.apache.log4j.spi.Filter;
+import org.apache.log4j.xml.XmlConfigurationFactory;
+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;
+
+import static org.apache.log4j.builders.BuilderManager.CATEGORY;
+import static org.apache.log4j.xml.XmlConfigurationFactory.*;
+
+/**
+ * Build a Console Appender
+ */
+@Plugin(name = "org.apache.log4j.ConsoleAppender", category = CATEGORY)
+public class ConsoleAppenderBuilder implements AppenderBuilder {
+ private static final String SYSTEM_OUT = "System.out";
+ private static final String SYSTEM_ERR = "System.err";
+ private static final String TARGET = "target";
+
+ private static final Logger LOGGER = StatusLogger.getLogger();
+
+ @Override
+ public Appender parseAppender(Element appenderElement, XmlConfigurationFactory factory) {
+ String name = appenderElement.getAttribute(XmlConfigurationFactory.NAME_ATTR);
+ Holder<String> target = new Holder<>(SYSTEM_OUT);
+ Holder<Layout> layout = new Holder<>();
+ Holder<Filter> filter = new Holder<>();
+ forEachElement(appenderElement.getChildNodes(), (currentElement) -> {
+ switch (currentElement.getTagName()) {
+ case LAYOUT_TAG:
+ layout.set(factory.parseLayout(currentElement));
+ break;
+ case FILTER_TAG:
+ filter.set(factory.parseFilters(currentElement));
+ break;
+ case PARAM_TAG: {
+ if (currentElement.getAttribute(NAME_ATTR).equalsIgnoreCase(TARGET)) {
+ String value = currentElement.getAttribute(VALUE_ATTR);
+ 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 System.out",
+ value);
+ }
+ }
+ }
+ break;
+ }
+ }
+ });
+ org.apache.logging.log4j.core.Layout<?> consoleLayout = null;
+ org.apache.logging.log4j.core.Filter consoleFilter = null;
+
+ if (layout.get() instanceof LayoutWrapper) {
+ consoleLayout = ((LayoutWrapper) layout.get()).getLayout();
+ } else if (layout.get() != null) {
+ consoleLayout = new LayoutAdapter(layout.get());
+ }
+ if (filter.get() != null) {
+ if (filter.get() instanceof FilterWrapper) {
+ consoleFilter = ((FilterWrapper) filter.get()).getFilter();
+ } else {
+ consoleFilter = new FilterAdapter(filter.get());
+ }
+ }
+ ConsoleAppender.Target consoleTarget = SYSTEM_ERR.equals(target.get())
+ ? ConsoleAppender.Target.SYSTEM_ERR : ConsoleAppender.Target.SYSTEM_OUT;
+ return new AppenderWrapper(ConsoleAppender.newBuilder()
+ .setName(name)
+ .setTarget(consoleTarget)
+ .setLayout(consoleLayout)
+ .setFilter(consoleFilter)
+ .setConfiguration(factory.getConfiguration())
+ .build());
+ }
+}
diff --git a/log4j-1.2-api/src/src/main/java/org/apache/log4j/builders/appender/DailyRollingFileAppenderBuilder.java b/log4j-1.2-api/src/src/main/java/org/apache/log4j/builders/appender/DailyRollingFileAppenderBuilder.java
new file mode 100644
index 0000000..878020f
--- /dev/null
+++ b/log4j-1.2-api/src/src/main/java/org/apache/log4j/builders/appender/DailyRollingFileAppenderBuilder.java
@@ -0,0 +1,146 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.log4j.builders.appender;
+
+import org.apache.log4j.Appender;
+import org.apache.log4j.Layout;
+import org.apache.log4j.bridge.AppenderWrapper;
+import org.apache.log4j.bridge.FilterAdapter;
+import org.apache.log4j.bridge.FilterWrapper;
+import org.apache.log4j.bridge.LayoutAdapter;
+import org.apache.log4j.bridge.LayoutWrapper;
+import org.apache.log4j.builders.BooleanHolder;
+import org.apache.log4j.builders.Holder;
+import org.apache.log4j.spi.Filter;
+import org.apache.log4j.xml.XmlConfigurationFactory;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.core.appender.FileAppender;
+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.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;
+
+import static org.apache.log4j.builders.BuilderManager.CATEGORY;
+import static org.apache.log4j.xml.XmlConfigurationFactory.*;
+
+/**
+ * Build a Daily Rolling File Appender
+ */
+@Plugin(name = "org.apache.log4j.DailyRollingFileAppender", category = CATEGORY)
+public class DailyRollingFileAppenderBuilder implements AppenderBuilder {
+
+ private static final Logger LOGGER = StatusLogger.getLogger();
+
+ @Override
+ public Appender parseAppender(Element appenderElement, XmlConfigurationFactory factory) {
+ String name = appenderElement.getAttribute(NAME_ATTR);
+ Holder<Layout> layout = new Holder<>();
+ Holder<Filter> filter = new Holder<>();
+ Holder<String> fileName = new Holder<>();
+ Holder<Boolean> immediateFlush = new BooleanHolder();
+ Holder<Boolean> append = new BooleanHolder();
+ Holder<Boolean> bufferedIo = new BooleanHolder();
+ Holder<Integer> bufferSize = new Holder<>(8192);
+ forEachElement(appenderElement.getChildNodes(), (currentElement) -> {
+ switch (currentElement.getTagName()) {
+ case LAYOUT_TAG:
+ layout.set(factory.parseLayout(currentElement));
+ break;
+ case FILTER_TAG:
+ filter.set(factory.parseFilters(currentElement));
+ break;
+ case PARAM_TAG: {
+ switch (currentElement.getAttribute(NAME_ATTR).toLowerCase()) {
+ case FILE_PARAM:
+ fileName.set(currentElement.getAttribute(VALUE_ATTR));
+ break;
+ case APPEND_PARAM: {
+ String bool = currentElement.getAttribute(VALUE_ATTR);
+ if (bool != null) {
+ append.set(Boolean.parseBoolean(bool));
+ } else {
+ LOGGER.warn("No value provided for append parameter");
+ }
+ break;
+ }
+ case BUFFERED_IO_PARAM: {
+ String bool = currentElement.getAttribute(VALUE_ATTR);
+ if (bool != null) {
+ bufferedIo.set(Boolean.parseBoolean(bool));
+ } else {
+ LOGGER.warn("No value provided for bufferedIo parameter");
+ }
+ break;
+ }
+ case BUFFER_SIZE_PARAM:
+ String size = currentElement.getAttribute(VALUE_ATTR);
+ if (size != null) {
+ bufferSize.set(Integer.parseInt(size));
+ } else {
+ LOGGER.warn("No value provide for bufferSize parameter");
+ }
+ break;
+ }
+ break;
+ }
+ }
+ });
+
+ org.apache.logging.log4j.core.Layout<?> fileLayout = null;
+ org.apache.logging.log4j.core.Filter fileFilter = null;
+ if (bufferedIo.get()) {
+ immediateFlush.set(Boolean.TRUE);
+ }
+ if (layout.get() instanceof LayoutWrapper) {
+ fileLayout = ((LayoutWrapper) layout.get()).getLayout();
+ } else if (layout.get() != null) {
+ fileLayout = new LayoutAdapter(layout.get());
+ }
+ if (filter.get() != null) {
+ if (filter.get() instanceof FilterWrapper) {
+ fileFilter = ((FilterWrapper) filter.get()).getFilter();
+ } else {
+ fileFilter = new FilterAdapter(filter.get());
+ }
+ }
+ if (fileName.get() == null) {
+ LOGGER.warn("Unable to create File Appender, no file name provided");
+ return null;
+ }
+ String filePattern = fileName.get() +"%d{yyy-MM-dd}";
+ TriggeringPolicy policy = TimeBasedTriggeringPolicy.newBuilder().withModulate(true).build();
+ RolloverStrategy strategy = DefaultRolloverStrategy.newBuilder()
+ .withConfig(factory.getConfiguration())
+ .withMax(Integer.toString(Integer.MAX_VALUE))
+ .build();
+ return new AppenderWrapper(RollingFileAppender.newBuilder()
+ .setName(name)
+ .setConfiguration(factory.getConfiguration())
+ .setLayout(fileLayout)
+ .setFilter(fileFilter)
+ .withFileName(fileName.get())
+ .withImmediateFlush(immediateFlush.get())
+ .withFilePattern(filePattern)
+ .withPolicy(policy)
+ .withStrategy(strategy)
+ .build());
+ }
+}
diff --git a/log4j-1.2-api/src/src/main/java/org/apache/log4j/builders/appender/FileAppenderBuilder.java b/log4j-1.2-api/src/src/main/java/org/apache/log4j/builders/appender/FileAppenderBuilder.java
new file mode 100644
index 0000000..4224c5d
--- /dev/null
+++ b/log4j-1.2-api/src/src/main/java/org/apache/log4j/builders/appender/FileAppenderBuilder.java
@@ -0,0 +1,135 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.log4j.builders.appender;
+
+import org.apache.log4j.Appender;
+import org.apache.log4j.Layout;
+import org.apache.log4j.bridge.AppenderWrapper;
+import org.apache.log4j.bridge.FilterAdapter;
+import org.apache.log4j.bridge.FilterWrapper;
+import org.apache.log4j.bridge.LayoutAdapter;
+import org.apache.log4j.bridge.LayoutWrapper;
+import org.apache.log4j.builders.BooleanHolder;
+import org.apache.log4j.builders.Holder;
+import org.apache.log4j.spi.Filter;
+import org.apache.log4j.xml.XmlConfigurationFactory;
+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;
+
+import static org.apache.log4j.builders.BuilderManager.CATEGORY;
+import static org.apache.log4j.xml.XmlConfigurationFactory.*;
+
+/**
+ * Build a File Appender
+ */
+@Plugin(name = "org.apache.log4j.FileAppender", category = CATEGORY)
+public class FileAppenderBuilder implements AppenderBuilder {
+
+ private static final Logger LOGGER = StatusLogger.getLogger();
+
+ @Override
+ public Appender parseAppender(Element appenderElement, XmlConfigurationFactory factory) {
+ String name = appenderElement.getAttribute(NAME_ATTR);
+ Holder<Layout> layout = new Holder<>();
+ Holder<Filter> filter = new Holder<>();
+ Holder<String> fileName = new Holder<>();
+ Holder<Boolean> immediateFlush = new BooleanHolder();
+ Holder<Boolean> append = new BooleanHolder();
+ Holder<Boolean> bufferedIo = new BooleanHolder();
+ Holder<Integer> bufferSize = new Holder<>(8192);
+ forEachElement(appenderElement.getChildNodes(), (currentElement) -> {
+ switch (currentElement.getTagName()) {
+ case LAYOUT_TAG:
+ layout.set(factory.parseLayout(currentElement));
+ break;
+ case FILTER_TAG:
+ filter.set(factory.parseFilters(currentElement));
+ break;
+ case PARAM_TAG: {
+ switch (currentElement.getAttribute(NAME_ATTR).toLowerCase()) {
+ case FILE_PARAM:
+ fileName.set(currentElement.getAttribute(VALUE_ATTR));
+ break;
+ case APPEND_PARAM: {
+ String bool = currentElement.getAttribute(VALUE_ATTR);
+ if (bool != null) {
+ append.set(Boolean.parseBoolean(bool));
+ } else {
+ LOGGER.warn("No value provided for append parameter");
+ }
+ break;
+ }
+ case BUFFERED_IO_PARAM: {
+ String bool = currentElement.getAttribute(VALUE_ATTR);
+ if (bool != null) {
+ bufferedIo.set(Boolean.parseBoolean(bool));
+ } else {
+ LOGGER.warn("No value provided for bufferedIo parameter");
+ }
+ break;
+ }
+ case BUFFER_SIZE_PARAM:
+ String size = currentElement.getAttribute(VALUE_ATTR);
+ if (size != null) {
+ bufferSize.set(Integer.parseInt(size));
+ } else {
+ LOGGER.warn("No value provide for bufferSize parameter");
+ }
+ break;
+ }
+ break;
+ }
+ }
+ });
+
+ org.apache.logging.log4j.core.Layout<?> fileLayout = null;
+ org.apache.logging.log4j.core.Filter fileFilter = null;
+ if (bufferedIo.get()) {
+ immediateFlush.set(Boolean.TRUE);
+ }
+ if (layout.get() instanceof LayoutWrapper) {
+ fileLayout = ((LayoutWrapper) layout.get()).getLayout();
+ } else if (layout.get() != null) {
+ fileLayout = new LayoutAdapter(layout.get());
+ }
+ if (filter.get() != null) {
+ if (filter.get() instanceof FilterWrapper) {
+ fileFilter = ((FilterWrapper) filter.get()).getFilter();
+ } else {
+ fileFilter = new FilterAdapter(filter.get());
+ }
+ }
+ if (fileName.get() == null) {
+ LOGGER.warn("Unable to create File Appender, no file name provided");
+ return null;
+ }
+ return new AppenderWrapper(FileAppender.newBuilder()
+ .setName(name)
+ .setConfiguration(factory.getConfiguration())
+ .setLayout(fileLayout)
+ .setFilter(fileFilter)
+ .withFileName(fileName.get())
+ .withImmediateFlush(immediateFlush.get())
+ .withAppend(append.get())
+ .withBufferedIo(bufferedIo.get())
+ .withBufferSize(bufferSize.get())
+ .build());
+ }
+}
diff --git a/log4j-1.2-api/src/src/main/java/org/apache/log4j/builders/appender/NullAppenderBuilder.java b/log4j-1.2-api/src/src/main/java/org/apache/log4j/builders/appender/NullAppenderBuilder.java
new file mode 100644
index 0000000..857203e
--- /dev/null
+++ b/log4j-1.2-api/src/src/main/java/org/apache/log4j/builders/appender/NullAppenderBuilder.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.builders.appender;
+
+import org.apache.log4j.Appender;
+import org.apache.log4j.bridge.AppenderWrapper;
+import org.apache.log4j.xml.XmlConfigurationFactory;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.core.appender.NullAppender;
+import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.status.StatusLogger;
+import org.w3c.dom.Element;
+
+import static org.apache.log4j.builders.BuilderManager.CATEGORY;
+
+/**
+ * Build a Null Appender
+ */
+@Plugin(name = "org.apache.log4j.varia.NullAppender", category = CATEGORY)
+public class NullAppenderBuilder implements AppenderBuilder {
+
+ private static final Logger LOGGER = StatusLogger.getLogger();
+
+ @Override
+ public Appender parseAppender(Element appenderElement, XmlConfigurationFactory factory) {
+ String name = appenderElement.getAttribute("name");
+ return new AppenderWrapper(NullAppender.createAppender(name));
+ }
+}
diff --git a/log4j-1.2-api/src/src/main/java/org/apache/log4j/builders/appender/RollingFileAppenderBuilder.java b/log4j-1.2-api/src/src/main/java/org/apache/log4j/builders/appender/RollingFileAppenderBuilder.java
new file mode 100644
index 0000000..08a5129
--- /dev/null
+++ b/log4j-1.2-api/src/src/main/java/org/apache/log4j/builders/appender/RollingFileAppenderBuilder.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.builders.appender;
+
+import org.apache.log4j.Appender;
+import org.apache.log4j.Layout;
+import org.apache.log4j.bridge.AppenderWrapper;
+import org.apache.log4j.bridge.FilterAdapter;
+import org.apache.log4j.bridge.FilterWrapper;
+import org.apache.log4j.bridge.LayoutAdapter;
+import org.apache.log4j.bridge.LayoutWrapper;
+import org.apache.log4j.builders.BooleanHolder;
+import org.apache.log4j.builders.Holder;
+import org.apache.log4j.spi.Filter;
+import org.apache.log4j.xml.XmlConfigurationFactory;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.core.appender.RollingFileAppender;
+import org.apache.logging.log4j.core.appender.rolling.CompositeTriggeringPolicy;
+import org.apache.logging.log4j.core.appender.rolling.DefaultRolloverStrategy;
+import org.apache.logging.log4j.core.appender.rolling.RolloverStrategy;
+import org.apache.logging.log4j.core.appender.rolling.SizeBasedTriggeringPolicy;
+import org.apache.logging.log4j.core.appender.rolling.TimeBasedTriggeringPolicy;
+import org.apache.logging.log4j.core.appender.rolling.TriggeringPolicy;
+import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.status.StatusLogger;
+import org.w3c.dom.Element;
+
+import static org.apache.log4j.builders.BuilderManager.CATEGORY;
+import static org.apache.log4j.xml.XmlConfigurationFactory.*;
+
+/**
+ * Build a File Appender
+ */
+@Plugin(name = "org.apache.log4j.RollingFileAppender", category = CATEGORY)
+public class RollingFileAppenderBuilder implements AppenderBuilder {
+
+ private static final Logger LOGGER = StatusLogger.getLogger();
+
+ @Override
+ public Appender parseAppender(Element appenderElement, XmlConfigurationFactory factory) {
+ String name = appenderElement.getAttribute(NAME_ATTR);
+ Holder<Layout> layout = new Holder<>();
+ Holder<Filter> filter = new Holder<>();
+ Holder<String> fileName = new Holder<>();
+ Holder<Boolean> immediateFlush = new BooleanHolder();
+ Holder<Boolean> append = new BooleanHolder();
+ Holder<Boolean> bufferedIo = new BooleanHolder();
+ Holder<Integer> bufferSize = new Holder<>(8192);
+ Holder<String> maxSize = new Holder<>();
+ Holder<String> maxBackups = new Holder<>();
+ forEachElement(appenderElement.getChildNodes(), (currentElement) -> {
+ switch (currentElement.getTagName()) {
+ case LAYOUT_TAG:
+ layout.set(factory.parseLayout(currentElement));
+ break;
+ case FILTER_TAG:
+ filter.set(factory.parseFilters(currentElement));
+ break;
+ case PARAM_TAG: {
+ switch (currentElement.getAttribute(NAME_ATTR).toLowerCase()) {
+ case FILE_PARAM:
+ fileName.set(currentElement.getAttribute(VALUE_ATTR));
+ break;
+ case APPEND_PARAM: {
+ String bool = currentElement.getAttribute(VALUE_ATTR);
+ if (bool != null) {
+ append.set(Boolean.parseBoolean(bool));
+ } else {
+ LOGGER.warn("No value provided for append parameter");
+ }
+ break;
+ }
+ case BUFFERED_IO_PARAM: {
+ String bool = currentElement.getAttribute(VALUE_ATTR);
+ if (bool != null) {
+ bufferedIo.set(Boolean.parseBoolean(bool));
+ } else {
+ LOGGER.warn("No value provided for bufferedIo parameter");
+ }
+ break;
+ }
+ case BUFFER_SIZE_PARAM: {
+ String size = currentElement.getAttribute(VALUE_ATTR);
+ if (size != null) {
+ bufferSize.set(Integer.parseInt(size));
+ } else {
+ LOGGER.warn("No value provide for bufferSize parameter");
+ }
+ break;
+ }
+ case MAX_BACKUP_INDEX: {
+ String size = currentElement.getAttribute(VALUE_ATTR);
+ if (size != null) {
+ maxBackups.set(size);
+ } else {
+ LOGGER.warn("No value provide for maxBackupIndex parameter");
+ }
+ break;
+ }
+ case MAX_SIZE_PARAM: {
+ String size = currentElement.getAttribute(VALUE_ATTR);
+ if (size != null) {
+ maxSize.set(size);
+ } else {
+ LOGGER.warn("No value provide for bufferSize parameter");
+ }
+ break;
+ }
+ }
+ break;
+ }
+ }
+ });
+
+ org.apache.logging.log4j.core.Layout<?> fileLayout = null;
+ org.apache.logging.log4j.core.Filter fileFilter = null;
+ if (bufferedIo.get()) {
+ immediateFlush.set(Boolean.TRUE);
+ }
+ if (layout.get() instanceof LayoutWrapper) {
+ fileLayout = ((LayoutWrapper) layout.get()).getLayout();
+ } else if (layout.get() != null) {
+ fileLayout = new LayoutAdapter(layout.get());
+ }
+ if (filter.get() != null) {
+ if (filter.get() instanceof FilterWrapper) {
+ fileFilter = ((FilterWrapper) filter.get()).getFilter();
+ } else {
+ fileFilter = new FilterAdapter(filter.get());
+ }
+ }
+ if (fileName.get() == null) {
+ LOGGER.warn("Unable to create File Appender, no file name provided");
+ return null;
+ }
+ String filePattern = fileName.get() +"%d{yyy-MM-dd}";
+ TriggeringPolicy timePolicy = TimeBasedTriggeringPolicy.newBuilder().withModulate(true).build();
+ SizeBasedTriggeringPolicy sizePolicy = SizeBasedTriggeringPolicy.createPolicy(maxSize.get());
+ CompositeTriggeringPolicy policy = CompositeTriggeringPolicy.createPolicy(sizePolicy, timePolicy);
+ RolloverStrategy strategy = DefaultRolloverStrategy.newBuilder()
+ .withConfig(factory.getConfiguration())
+ .withMax(maxBackups.get())
+ .build();
+ return new AppenderWrapper(RollingFileAppender.newBuilder()
+ .setName(name)
+ .setConfiguration(factory.getConfiguration())
+ .setLayout(fileLayout)
+ .setFilter(fileFilter)
+ .withImmediateFlush(immediateFlush.get())
+ .withFileName(fileName.get())
+ .withFilePattern(filePattern)
+ .withPolicy(policy)
+ .withStrategy(strategy)
+ .build());
+ }
+}
diff --git a/log4j-1.2-api/src/src/main/java/org/apache/log4j/builders/filter/DenyAllFilterBuilder.java b/log4j-1.2-api/src/src/main/java/org/apache/log4j/builders/filter/DenyAllFilterBuilder.java
new file mode 100644
index 0000000..e942b34
--- /dev/null
+++ b/log4j-1.2-api/src/src/main/java/org/apache/log4j/builders/filter/DenyAllFilterBuilder.java
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.log4j.builders.filter;
+
+import org.apache.log4j.Layout;
+import org.apache.log4j.bridge.FilterWrapper;
+import org.apache.log4j.bridge.LayoutWrapper;
+import org.apache.log4j.builders.layout.LayoutBuilder;
+import org.apache.log4j.spi.Filter;
+import org.apache.log4j.xml.XmlConfigurationFactory;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.core.filter.DenyAllFilter;
+import org.apache.logging.log4j.core.layout.PatternLayout;
+import org.apache.logging.log4j.status.StatusLogger;
+import org.w3c.dom.Element;
+
+import static org.apache.log4j.builders.BuilderManager.CATEGORY;
+
+/**
+ * Build a Pattern Layout
+ */
+@Plugin(name = "org.apache.log4j.varia.DenyAllFilter", category = CATEGORY)
+public class DenyAllFilterBuilder implements FilterBuilder {
+
+ private static final Logger LOGGER = StatusLogger.getLogger();
+
+ @Override
+ public Filter parseFilter(Element filterElement, XmlConfigurationFactory factory) {
+ return new FilterWrapper(DenyAllFilter.newBuilder().build());
+ }
+}
diff --git a/log4j-1.2-api/src/src/main/java/org/apache/log4j/builders/filter/FilterBuilder.java b/log4j-1.2-api/src/src/main/java/org/apache/log4j/builders/filter/FilterBuilder.java
new file mode 100644
index 0000000..fbeaefb
--- /dev/null
+++ b/log4j-1.2-api/src/src/main/java/org/apache/log4j/builders/filter/FilterBuilder.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.builders.filter;
+
+import org.apache.log4j.spi.Filter;
+import org.apache.log4j.xml.XmlConfigurationFactory;
+import org.w3c.dom.Element;
+
+/**
+ * Define a Filter Builder.
+ */
+public interface FilterBuilder {
+
+ Filter parseFilter(Element element, XmlConfigurationFactory factory);
+
+}
diff --git a/log4j-1.2-api/src/src/main/java/org/apache/log4j/builders/filter/LevelMatchFilterBuilder.java b/log4j-1.2-api/src/src/main/java/org/apache/log4j/builders/filter/LevelMatchFilterBuilder.java
new file mode 100644
index 0000000..b5df7ff
--- /dev/null
+++ b/log4j-1.2-api/src/src/main/java/org/apache/log4j/builders/filter/LevelMatchFilterBuilder.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.filter;
+
+import org.apache.log4j.bridge.FilterWrapper;
+import org.apache.log4j.builders.BooleanHolder;
+import org.apache.log4j.builders.Holder;
+import org.apache.log4j.spi.Filter;
+import org.apache.log4j.xml.XmlConfigurationFactory;
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.core.filter.LevelMatchFilter;
+import org.apache.logging.log4j.status.StatusLogger;
+import org.w3c.dom.Element;
+
+import static org.apache.log4j.builders.BuilderManager.CATEGORY;
+import static org.apache.log4j.xml.XmlConfigurationFactory.*;
+import static org.apache.log4j.xml.XmlConfigurationFactory.VALUE_ATTR;
+
+/**
+ * Build a Level match failter.
+ */
+@Plugin(name = "org.apache.log4j.varia.LevelMatchFilter", category = CATEGORY)
+public class LevelMatchFilterBuilder implements FilterBuilder {
+
+ private static final Logger LOGGER = StatusLogger.getLogger();
+ private static final String LEVEL = "level";
+ private static final String ACCEPT_ON_MATCH = "acceptonmatch";
+
+ @Override
+ public Filter parseFilter(Element filterElement, XmlConfigurationFactory factory) {
+ final Holder<String> level = new Holder<>();
+ final Holder<Boolean> acceptOnMatch = new BooleanHolder();
+ forEachElement(filterElement.getElementsByTagName("param"), (currentElement) -> {
+ if (currentElement.getTagName().equals("param")) {
+ switch (currentElement.getAttribute(NAME_ATTR).toLowerCase()) {
+ case LEVEL:
+ level.set(currentElement.getAttribute(VALUE_ATTR));
+ break;
+ case ACCEPT_ON_MATCH:
+ acceptOnMatch.set(Boolean.parseBoolean(currentElement.getAttribute(VALUE_ATTR)));
+ break;
+ }
+ }
+ });
+ Level lvl = Level.ERROR;
+ if (level.get() != null) {
+ lvl = Level.toLevel(level.get(), Level.ERROR);
+ }
+ org.apache.logging.log4j.core.Filter.Result onMatch = acceptOnMatch.get() != null && acceptOnMatch.get()
+ ? org.apache.logging.log4j.core.Filter.Result.ACCEPT
+ : org.apache.logging.log4j.core.Filter.Result.DENY;
+ return new FilterWrapper(LevelMatchFilter.newBuilder()
+ .setLevel(lvl)
+ .setOnMatch(onMatch)
+ .setOnMismatch(org.apache.logging.log4j.core.Filter.Result.NEUTRAL)
+ .build());
+ }
+}
diff --git a/log4j-1.2-api/src/src/main/java/org/apache/log4j/builders/filter/LevelRangeFilterBuilder.java b/log4j-1.2-api/src/src/main/java/org/apache/log4j/builders/filter/LevelRangeFilterBuilder.java
new file mode 100644
index 0000000..1082245
--- /dev/null
+++ b/log4j-1.2-api/src/src/main/java/org/apache/log4j/builders/filter/LevelRangeFilterBuilder.java
@@ -0,0 +1,81 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.log4j.builders.filter;
+
+import org.apache.log4j.bridge.FilterWrapper;
+import org.apache.log4j.builders.BooleanHolder;
+import org.apache.log4j.builders.Holder;
+import org.apache.log4j.spi.Filter;
+import org.apache.log4j.xml.XmlConfigurationFactory;
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.core.filter.LevelRangeFilter;
+import org.apache.logging.log4j.status.StatusLogger;
+import org.w3c.dom.Element;
+
+
+import static org.apache.log4j.builders.BuilderManager.CATEGORY;
+import static org.apache.log4j.xml.XmlConfigurationFactory.*;
+
+/**
+ * Build a Level match failter.
+ */
+@Plugin(name = "org.apache.log4j.varia.LevelRangeFilter", category = CATEGORY)
+public class LevelRangeFilterBuilder implements FilterBuilder {
+
+ private static final Logger LOGGER = StatusLogger.getLogger();
+ private static final String LEVEL_MAX = "levelmax";
+ private static final String LEVEL_MIN = "levelmin";
+ private static final String ACCEPT_ON_MATCH = "acceptonmatch";
+
+ @Override
+ public Filter parseFilter(Element filterElement, XmlConfigurationFactory factory) {
+ final Holder<String> levelMax = new Holder<>();
+ final Holder<String> levelMin = new Holder<>();
+ final Holder<Boolean> acceptOnMatch = new BooleanHolder();
+ forEachElement(filterElement.getElementsByTagName("param"), (currentElement) -> {
+ if (currentElement.getTagName().equals("param")) {
+ switch (currentElement.getAttribute(NAME_ATTR).toLowerCase()) {
+ case LEVEL_MAX:
+ levelMax.set(currentElement.getAttribute(VALUE_ATTR));
+ break;
+ case LEVEL_MIN:
+ levelMax.set(currentElement.getAttribute(VALUE_ATTR));
+ break;
+ case ACCEPT_ON_MATCH:
+ acceptOnMatch.set(Boolean.parseBoolean(currentElement.getAttribute(VALUE_ATTR)));
+ break;
+ }
+ }
+ });
+ Level max = Level.FATAL;
+ Level min = Level.TRACE;
+ if (levelMax.get() != null) {
+ max = Level.toLevel(levelMax.get(), Level.FATAL);
+ }
+ if (levelMin.get() != null) {
+ min = Level.toLevel(levelMin.get(), Level.DEBUG);
+ }
+ org.apache.logging.log4j.core.Filter.Result onMatch = acceptOnMatch.get() != null && acceptOnMatch.get()
+ ? org.apache.logging.log4j.core.Filter.Result.ACCEPT
+ : org.apache.logging.log4j.core.Filter.Result.NEUTRAL;
+
+ return new FilterWrapper(LevelRangeFilter.createFilter(min, max, onMatch,
+ org.apache.logging.log4j.core.Filter.Result.DENY));
+ }
+}
diff --git a/log4j-1.2-api/src/src/main/java/org/apache/log4j/builders/filter/StringMatchFilterBuilder.java b/log4j-1.2-api/src/src/main/java/org/apache/log4j/builders/filter/StringMatchFilterBuilder.java
new file mode 100644
index 0000000..8e4f71a
--- /dev/null
+++ b/log4j-1.2-api/src/src/main/java/org/apache/log4j/builders/filter/StringMatchFilterBuilder.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.builders.filter;
+
+import org.apache.log4j.bridge.FilterWrapper;
+import org.apache.log4j.builders.BooleanHolder;
+import org.apache.log4j.builders.Holder;
+import org.apache.log4j.spi.Filter;
+import org.apache.log4j.xml.XmlConfigurationFactory;
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.core.filter.LevelMatchFilter;
+import org.apache.logging.log4j.core.filter.StringMatchFilter;
+import org.apache.logging.log4j.status.StatusLogger;
+import org.w3c.dom.Element;
+
+import static org.apache.log4j.builders.BuilderManager.CATEGORY;
+import static org.apache.log4j.xml.XmlConfigurationFactory.*;
+
+/**
+ * Build a String match filter.
+ */
+@Plugin(name = "org.apache.log4j.varia.StringMatchFilter", category = CATEGORY)
+public class StringMatchFilterBuilder 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";
+
+ @Override
+ public Filter parseFilter(Element filterElement, XmlConfigurationFactory factory) {
+ final Holder<Boolean> acceptOnMatch = new BooleanHolder();
+ final Holder<String> text = new Holder<>();
+ forEachElement(filterElement.getElementsByTagName("param"), (currentElement) -> {
+ if (currentElement.getTagName().equals("param")) {
+ switch (currentElement.getAttribute(NAME_ATTR).toLowerCase()) {
+ case STRING_TO_MATCH:
+ text.set(currentElement.getAttribute(VALUE_ATTR));
+ break;
+ case ACCEPT_ON_MATCH:
+ acceptOnMatch.set(Boolean.parseBoolean(currentElement.getAttribute(VALUE_ATTR)));
+ break;
+
+ }
+ }
+ });
+ if (text.get() == null) {
+ LOGGER.warn("No text provided for StringMatchFilter");
+ return null;
+ }
+ org.apache.logging.log4j.core.Filter.Result onMatch = acceptOnMatch.get() != null && acceptOnMatch.get()
+ ? org.apache.logging.log4j.core.Filter.Result.ACCEPT
+ : org.apache.logging.log4j.core.Filter.Result.DENY;
+ return new FilterWrapper(StringMatchFilter.newBuilder()
+ .setMatchString(text.get())
+ .setOnMatch(onMatch)
+ .setOnMismatch(org.apache.logging.log4j.core.Filter.Result.NEUTRAL)
+ .build());
+ }
+}
diff --git a/log4j-1.2-api/src/src/main/java/org/apache/log4j/builders/layout/HtmlLayoutBuilder.java b/log4j-1.2-api/src/src/main/java/org/apache/log4j/builders/layout/HtmlLayoutBuilder.java
new file mode 100644
index 0000000..78b5f40
--- /dev/null
+++ b/log4j-1.2-api/src/src/main/java/org/apache/log4j/builders/layout/HtmlLayoutBuilder.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.builders.layout;
+
+import org.apache.log4j.Layout;
+import org.apache.log4j.bridge.LayoutWrapper;
+import org.apache.log4j.builders.BooleanHolder;
+import org.apache.log4j.builders.Holder;
+import org.apache.log4j.xml.XmlConfigurationFactory;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.core.layout.HtmlLayout;
+import org.apache.logging.log4j.status.StatusLogger;
+import org.w3c.dom.Element;
+
+import static org.apache.log4j.builders.BuilderManager.CATEGORY;
+import static org.apache.log4j.xml.XmlConfigurationFactory.*;
+
+/**
+ * Build a Pattern Layout
+ */
+@Plugin(name = "org.apache.log4j.HTMLLayout", category = CATEGORY)
+public class HtmlLayoutBuilder implements LayoutBuilder {
+
+ private static final Logger LOGGER = StatusLogger.getLogger();
+
+
+ @Override
+ public Layout parseLayout(Element layoutElement, XmlConfigurationFactory factory) {
+ final Holder<String> title = new Holder<>();
+ final Holder<Boolean> locationInfo = new BooleanHolder();
+ forEachElement(layoutElement.getElementsByTagName("param"), (currentElement) -> {
+ if (currentElement.getTagName().equals("param")) {
+ if ("title".equalsIgnoreCase(currentElement.getAttribute("name"))) {
+ title.set(currentElement.getAttribute("value"));
+ } else if ("locationInfo".equalsIgnoreCase(currentElement.getAttribute("name"))) {
+ locationInfo.set(Boolean.parseBoolean(currentElement.getAttribute("value")));
+ }
+ }
+ });
+ return new LayoutWrapper(HtmlLayout.newBuilder()
+ .withTitle(title.get())
+ .withLocationInfo(locationInfo.get())
+ .build());
+ }
+}
diff --git a/log4j-1.2-api/src/src/main/java/org/apache/log4j/builders/layout/LayoutBuilder.java b/log4j-1.2-api/src/src/main/java/org/apache/log4j/builders/layout/LayoutBuilder.java
new file mode 100644
index 0000000..9dc88f3
--- /dev/null
+++ b/log4j-1.2-api/src/src/main/java/org/apache/log4j/builders/layout/LayoutBuilder.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.builders.layout;
+
+import org.apache.log4j.Layout;
+import org.apache.log4j.xml.XmlConfigurationFactory;
+import org.w3c.dom.Element;
+
+/**
+ * Define a Layout Builder.
+ */
+public interface LayoutBuilder {
+
+ Layout parseLayout(Element element, XmlConfigurationFactory factory);
+
+}
diff --git a/log4j-1.2-api/src/src/main/java/org/apache/log4j/builders/layout/PatternLayoutBuilder.java b/log4j-1.2-api/src/src/main/java/org/apache/log4j/builders/layout/PatternLayoutBuilder.java
new file mode 100644
index 0000000..b6a1964
--- /dev/null
+++ b/log4j-1.2-api/src/src/main/java/org/apache/log4j/builders/layout/PatternLayoutBuilder.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.builders.layout;
+
+import org.apache.log4j.Layout;
+import org.apache.log4j.bridge.LayoutWrapper;
+import org.apache.log4j.xml.XmlConfigurationFactory;
+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;
+
+import static org.apache.log4j.builders.BuilderManager.CATEGORY;
+
+/**
+ * Build a Pattern Layout
+ */
+@Plugin(name = "org.apache.log4j.PatternLayout", category = CATEGORY)
+@PluginAliases("org.apache.log4j.EnhancedPatternLayout")
+public class PatternLayoutBuilder implements LayoutBuilder {
+
+ private static final Logger LOGGER = StatusLogger.getLogger();
+
+ @Override
+ public Layout parseLayout(Element layoutElement, XmlConfigurationFactory factory) {
+ NodeList params = layoutElement.getElementsByTagName("param");
+ final int length = params.getLength();
+ String pattern = null;
+ for (int index = 0; index < length; ++ index) {
+ Node currentNode = params.item(index);
+ if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
+ Element currentElement = (Element) currentNode;
+ if (currentElement.getTagName().equals("param")) {
+ if ("conversionPattern".equalsIgnoreCase(currentElement.getAttribute("name"))) {
+ pattern = currentElement.getAttribute("value")
+ // 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");
+ break;
+ }
+ }
+ }
+ }
+ return new LayoutWrapper(PatternLayout.newBuilder()
+ .withPattern(pattern)
+ .withConfiguration(factory.getConfiguration())
+ .build());
+ }
+}
diff --git a/log4j-1.2-api/src/src/main/java/org/apache/log4j/builders/layout/SimpleLayoutBuilder.java b/log4j-1.2-api/src/src/main/java/org/apache/log4j/builders/layout/SimpleLayoutBuilder.java
new file mode 100644
index 0000000..4f09bc2
--- /dev/null
+++ b/log4j-1.2-api/src/src/main/java/org/apache/log4j/builders/layout/SimpleLayoutBuilder.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.layout;
+
+import org.apache.log4j.Layout;
+import org.apache.log4j.bridge.LayoutWrapper;
+import org.apache.log4j.xml.XmlConfigurationFactory;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.core.layout.PatternLayout;
+import org.apache.logging.log4j.status.StatusLogger;
+import org.w3c.dom.Element;
+
+import static org.apache.log4j.builders.BuilderManager.CATEGORY;
+import static org.apache.log4j.xml.XmlConfigurationFactory.*;
+
+/**
+ * Build a Pattern Layout
+ */
+@Plugin(name = "org.apache.log4j.SimpleLayout", category = CATEGORY)
+public class SimpleLayoutBuilder implements LayoutBuilder {
+
+ private static final Logger LOGGER = StatusLogger.getLogger();
+
+ @Override
+ public Layout parseLayout(Element layoutElement, XmlConfigurationFactory factory) {
+ return new LayoutWrapper(PatternLayout.newBuilder()
+ .withPattern("%level - %m%n")
+ .withConfiguration(factory.getConfiguration())
+ .build());
+ }
+}
diff --git a/log4j-1.2-api/src/src/main/java/org/apache/log4j/builders/layout/TTCCLayoutBuilder.java b/log4j-1.2-api/src/src/main/java/org/apache/log4j/builders/layout/TTCCLayoutBuilder.java
new file mode 100644
index 0000000..b15d351
--- /dev/null
+++ b/log4j-1.2-api/src/src/main/java/org/apache/log4j/builders/layout/TTCCLayoutBuilder.java
@@ -0,0 +1,103 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.log4j.builders.layout;
+
+import org.apache.log4j.Layout;
+import org.apache.log4j.bridge.LayoutWrapper;
+import org.apache.log4j.builders.BooleanHolder;
+import org.apache.log4j.builders.Holder;
+import org.apache.log4j.xml.XmlConfigurationFactory;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.core.layout.PatternLayout;
+import org.apache.logging.log4j.status.StatusLogger;
+import org.w3c.dom.Element;
+
+import static org.apache.log4j.builders.BuilderManager.CATEGORY;
+import static org.apache.log4j.xml.XmlConfigurationFactory.*;
+
+/**
+ * Build a Pattern Layout
+ */
+@Plugin(name = "org.apache.log4j.TTCCLayout", category = CATEGORY)
+public class TTCCLayoutBuilder implements LayoutBuilder {
+
+ private static final Logger LOGGER = StatusLogger.getLogger();
+
+ 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";
+
+ @Override
+ public Layout parseLayout(Element layoutElement, XmlConfigurationFactory factory) {
+ final Holder<Boolean> threadPrinting = new BooleanHolder();
+ final Holder<Boolean> categoryPrefixing = new BooleanHolder();
+ final Holder<Boolean> contextPrinting = new BooleanHolder();
+ final Holder<String> dateFormat = new Holder<>();
+ final Holder<String> timezone = new Holder<>();
+ forEachElement(layoutElement.getElementsByTagName("param"), (currentElement) -> {
+ if (currentElement.getTagName().equals("param")) {
+ switch (currentElement.getAttribute(NAME_ATTR).toLowerCase()) {
+ case THREAD_PRINTING_PARAM:
+ threadPrinting.set(Boolean.parseBoolean(currentElement.getAttribute(VALUE_ATTR)));
+ break;
+ case CATEGORY_PREFIXING_PARAM:
+ categoryPrefixing.set(Boolean.parseBoolean(currentElement.getAttribute(VALUE_ATTR)));
+ break;
+ case CONTEXT_PRINTING_PARAM:
+ contextPrinting.set(Boolean.parseBoolean(currentElement.getAttribute(VALUE_ATTR)));
+ break;
+ case DATE_FORMAT_PARAM:
+ dateFormat.set(currentElement.getAttribute(VALUE_ATTR));
+ break;
+ case TIMEZONE_FORMAT:
+ timezone.set(currentElement.getAttribute(VALUE_ATTR));
+ break;
+ }
+ }
+ });
+ StringBuilder sb = new StringBuilder();
+ if (dateFormat.get() != null) {
+ if (RELATIVE.equalsIgnoreCase(dateFormat.get())) {
+ sb.append("%r ");
+ } else {
+ sb.append("%d{").append(dateFormat.get()).append("}");
+ if (timezone.get() != null) {
+ sb.append("{").append(timezone.get()).append("}");
+ }
+ sb.append(" ");
+ }
+ }
+ if (threadPrinting.get()) {
+ sb.append("[%t] ");
+ }
+ sb.append("%p ");
+ if (categoryPrefixing.get()) {
+ sb.append("%c ");
+ }
+ if (contextPrinting.get()) {
+ sb.append("%notEmpty{%ndc }");
+ }
+ sb.append("- %m%n");
+ return new LayoutWrapper(PatternLayout.newBuilder()
+ .withPattern(sb.toString())
+ .withConfiguration(factory.getConfiguration())
+ .build());
+ }
+}
diff --git a/log4j-1.2-api/src/src/main/java/org/apache/log4j/builders/layout/XmlLayoutBuilder.java b/log4j-1.2-api/src/src/main/java/org/apache/log4j/builders/layout/XmlLayoutBuilder.java
new file mode 100644
index 0000000..5436e5c
--- /dev/null
+++ b/log4j-1.2-api/src/src/main/java/org/apache/log4j/builders/layout/XmlLayoutBuilder.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.builders.layout;
+
+import org.apache.log4j.Layout;
+import org.apache.log4j.bridge.LayoutWrapper;
+import org.apache.log4j.builders.BooleanHolder;
+import org.apache.log4j.builders.Holder;
+import org.apache.log4j.xml.XmlConfigurationFactory;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.core.layout.HtmlLayout;
+import org.apache.logging.log4j.core.layout.XmlLayout;
+import org.apache.logging.log4j.status.StatusLogger;
+import org.w3c.dom.Element;
+
+import static org.apache.log4j.builders.BuilderManager.CATEGORY;
+import static org.apache.log4j.xml.XmlConfigurationFactory.forEachElement;
+
+/**
+ * Build a Pattern Layout
+ */
+@Plugin(name = "org.apache.log4j.xml.XMLLayout", category = CATEGORY)
+public class XmlLayoutBuilder implements LayoutBuilder {
+
+ private static final Logger LOGGER = StatusLogger.getLogger();
+
+
+ @Override
+ public Layout parseLayout(Element layoutElement, XmlConfigurationFactory factory) {
+ final Holder<Boolean> properties = new BooleanHolder();
+ final Holder<Boolean> locationInfo = new BooleanHolder();
+ forEachElement(layoutElement.getElementsByTagName("param"), (currentElement) -> {
+ if ("properties".equalsIgnoreCase(currentElement.getAttribute("name"))) {
+ properties.set(Boolean.parseBoolean(currentElement.getAttribute("value")));
+ } else if ("locationInfo".equalsIgnoreCase(currentElement.getAttribute("name"))) {
+ locationInfo.set(Boolean.parseBoolean(currentElement.getAttribute("value")));
+ }
+ });
+ return new LayoutWrapper(XmlLayout.newBuilder()
+ .setLocationInfo(locationInfo.get())
+ .setProperties(properties.get())
+ .build());
+ }
+}
diff --git a/log4j-1.2-api/src/src/main/java/org/apache/log4j/config/InputStreamWrapper.java b/log4j-1.2-api/src/src/main/java/org/apache/log4j/config/InputStreamWrapper.java
new file mode 100644
index 0000000..19bf9a9
--- /dev/null
+++ b/log4j-1.2-api/src/src/main/java/org/apache/log4j/config/InputStreamWrapper.java
@@ -0,0 +1,92 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.log4j.config;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+class InputStreamWrapper extends InputStream {
+
+ private final String description;
+ private final InputStream input;
+
+ public InputStreamWrapper(final InputStream input, final String description) {
+ this.input = input;
+ this.description = description;
+ }
+
+ @Override
+ public int available() throws IOException {
+ return input.available();
+ }
+
+ @Override
+ public void close() throws IOException {
+ input.close();
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ return input.equals(obj);
+ }
+
+ @Override
+ public int hashCode() {
+ return input.hashCode();
+ }
+
+ @Override
+ public synchronized void mark(final int readlimit) {
+ input.mark(readlimit);
+ }
+
+ @Override
+ public boolean markSupported() {
+ return input.markSupported();
+ }
+
+ @Override
+ public int read() throws IOException {
+ return input.read();
+ }
+
+ @Override
+ public int read(final byte[] b) throws IOException {
+ return input.read(b);
+ }
+
+ @Override
+ public int read(final byte[] b, final int off, final int len) throws IOException {
+ return input.read(b, off, len);
+ }
+
+ @Override
+ public synchronized void reset() throws IOException {
+ input.reset();
+ }
+
+ @Override
+ public long skip(final long n) throws IOException {
+ return input.skip(n);
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName() + " [description=" + description + ", input=" + input + "]";
+ }
+
+}
diff --git a/log4j-1.2-api/src/src/main/java/org/apache/log4j/config/Log4j1Configuration.java b/log4j-1.2-api/src/src/main/java/org/apache/log4j/config/Log4j1Configuration.java
new file mode 100644
index 0000000..5a6b50d
--- /dev/null
+++ b/log4j-1.2-api/src/src/main/java/org/apache/log4j/config/Log4j1Configuration.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.log4j.config;
+
+import org.apache.logging.log4j.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.Node;
+import org.apache.logging.log4j.core.config.Reconfigurable;
+
+/**
+ * Class Description goes here.
+ */
+public class Log4j1Configuration extends AbstractConfiguration implements Reconfigurable {
+
+ public Log4j1Configuration(final LoggerContext loggerContext, final ConfigurationSource source,
+ int monitorIntervalSeconds) {
+ super(loggerContext, source);
+ initializeWatchers(this, source, monitorIntervalSeconds);
+ }
+
+ @Override
+ protected void doConfigure() {
+ super.getScheduler().start();
+
+ }
+
+ /**
+ * Initialize the configuration.
+ */
+ @Override
+ public void initialize() {
+ doConfigure();
+ setState(State.INITIALIZED);
+ LOGGER.debug("Configuration {} initialized", this);
+ }
+
+ @Override
+ public Configuration reconfigure() {
+ return null;
+ }
+}
diff --git a/log4j-1.2-api/src/src/main/java/org/apache/log4j/config/Log4j1ConfigurationConverter.java b/log4j-1.2-api/src/src/main/java/org/apache/log4j/config/Log4j1ConfigurationConverter.java
new file mode 100644
index 0000000..4475f6a
--- /dev/null
+++ b/log4j-1.2-api/src/src/main/java/org/apache/log4j/config/Log4j1ConfigurationConverter.java
@@ -0,0 +1,220 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.io.OutputStream;
+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.concurrent.atomic.AtomicInteger;
+
+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.tools.BasicCommandLineArguments;
+import org.apache.logging.log4j.core.tools.picocli.CommandLine;
+import org.apache.logging.log4j.core.tools.picocli.CommandLine.Command;
+import org.apache.logging.log4j.core.tools.picocli.CommandLine.Option;
+
+/**
+ * Tool for converting a Log4j 1.x properties configuration file to Log4j 2.x XML configuration file.
+ *
+ * <p>
+ * Run with "--help" on the command line.
+ * </p>
+ *
+ * <p>
+ * Example:
+ * </p>
+ *
+ * <pre>
+ * java org.apache.log4j.config.Log4j1ConfigurationConverter --recurse
+ * E:\vcs\git\apache\logging\logging-log4j2\log4j-1.2-api\src\test\resources\config-1.2\hadoop --in log4j.properties --verbose
+ * </pre>
+ */
+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.")
+ private boolean failFast;
+
+ @Option(names = { "--in", "-i" }, description = "Specifies the input file.")
+ private Path pathIn;
+
+ @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")
+ private Path recurseIntoPath;
+
+ @Option(names = { "--verbose", "-v" }, description = "Be verbose.")
+ private boolean verbose;
+
+ public Path getPathIn() {
+ return pathIn;
+ }
+
+ public Path getPathOut() {
+ return pathOut;
+ }
+
+ public Path getRecurseIntoPath() {
+ return recurseIntoPath;
+ }
+
+ public boolean isFailFast() {
+ return failFast;
+ }
+
+ public boolean isVerbose() {
+ return verbose;
+ }
+
+ public void setFailFast(final boolean failFast) {
+ this.failFast = failFast;
+ }
+
+ public void setPathIn(final Path pathIn) {
+ this.pathIn = pathIn;
+ }
+
+ public void setPathOut(final Path pathOut) {
+ this.pathOut = pathOut;
+ }
+
+ public void setRecurseIntoPath(final Path recurseIntoPath) {
+ this.recurseIntoPath = recurseIntoPath;
+ }
+
+ public void setVerbose(final boolean verbose) {
+ this.verbose = verbose;
+ }
+
+ @Override
+ public void run() {
+ if (isHelp()) {
+ CommandLine.usage(this, System.err);
+ return;
+ }
+ new Log4j1ConfigurationConverter(this).run();
+ }
+
+ @Override
+ public String toString() {
+ return "CommandLineArguments [recurseIntoPath=" + recurseIntoPath + ", verbose=" + verbose + ", pathIn="
+ + pathIn + ", pathOut=" + pathOut + "]";
+ }
+ }
+
+ private static final String FILE_EXT_XML = ".xml";
+
+ public static void main(final String[] args) {
+ CommandLine.run(new CommandLineArguments(), System.err, args);
+ }
+
+ public static Log4j1ConfigurationConverter run(final CommandLineArguments cla) {
+ final Log4j1ConfigurationConverter log4j1ConfigurationConverter = new Log4j1ConfigurationConverter(cla);
+ log4j1ConfigurationConverter.run();
+ return log4j1ConfigurationConverter;
+ }
+
+ private final CommandLineArguments cla;
+
+ private Log4j1ConfigurationConverter(final CommandLineArguments cla) {
+ this.cla = cla;
+ }
+
+ protected void convert(final InputStream input, final OutputStream output) throws IOException {
+ final ConfigurationBuilder<BuiltConfiguration> builder = new Log4j1ConfigurationParser()
+ .buildConfigurationBuilder(input);
+ builder.writeXmlConfiguration(output);
+ }
+
+ InputStream getInputStream() throws IOException {
+ final Path pathIn = cla.getPathIn();
+ return pathIn == null ? System.in : new InputStreamWrapper(Files.newInputStream(pathIn), pathIn.toString());
+ }
+
+ OutputStream getOutputStream() throws IOException {
+ final Path pathOut = cla.getPathOut();
+ return pathOut == null ? System.out : Files.newOutputStream(pathOut);
+ }
+
+ private void run() {
+ if (cla.getRecurseIntoPath() != null) {
+ final AtomicInteger countOKs = new AtomicInteger();
+ final AtomicInteger countFails = new AtomicInteger();
+ try {
+ Files.walkFileTree(cla.getRecurseIntoPath(), new SimpleFileVisitor<Path>() {
+ @Override
+ public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs)
+ throws IOException {
+ if (cla.getPathIn() == null || file.getFileName().equals(cla.getPathIn())) {
+ verbose("Reading %s", file);
+ String newFile = file.getFileName().toString();
+ final int lastIndex = newFile.lastIndexOf(".");
+ newFile = lastIndex < 0 ? newFile + FILE_EXT_XML
+ : newFile.substring(0, lastIndex) + FILE_EXT_XML;
+ final Path resolved = file.resolveSibling(newFile);
+ try (final InputStream input = new InputStreamWrapper(Files.newInputStream(file), file.toString());
+ final OutputStream output = Files.newOutputStream(resolved)) {
+ try {
+ convert(input, output);
+ countOKs.incrementAndGet();
+ } catch (ConfigurationException | IOException e) {
+ countFails.incrementAndGet();
+ if (cla.isFailFast()) {
+ throw e;
+ }
+ e.printStackTrace();
+ }
+ verbose("Wrote %s", resolved);
+ }
+ }
+ return FileVisitResult.CONTINUE;
+ }
+ });
+ } catch (final IOException e) {
+ throw new ConfigurationException(e);
+ } finally {
+ 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()) {
+ convert(input, output);
+ } catch (final IOException e) {
+ throw new ConfigurationException(e);
+ }
+ verbose("Wrote %s", cla.getPathOut());
+ }
+ }
+
+ private void verbose(final String template, final Object... args) {
+ if (cla.isVerbose()) {
+ System.err.println(String.format(template, args));
+ }
+ }
+
+}
diff --git a/log4j-1.2-api/src/src/main/java/org/apache/log4j/config/Log4j1ConfigurationFactory.java b/log4j-1.2-api/src/src/main/java/org/apache/log4j/config/Log4j1ConfigurationFactory.java
new file mode 100644
index 0000000..83f6675
--- /dev/null
+++ b/log4j-1.2-api/src/src/main/java/org/apache/log4j/config/Log4j1ConfigurationFactory.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.log4j.config;
+
+import 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;
+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.ConfigurationBuilder;
+import org.apache.logging.log4j.core.config.builder.impl.BuiltConfiguration;
+
+/**
+ * Experimental ConfigurationFactory for Log4j 1.2 properties configuration files.
+ */
+// TODO
+// @Plugin(name = "Log4j1ConfigurationFactory", category = ConfigurationFactory.CATEGORY)
+//
+// Best Value?
+// @Order(50)
+public class Log4j1ConfigurationFactory extends ConfigurationFactory {
+
+ private static final String[] SUFFIXES = {".properties"};
+
+ @Override
+ public Configuration getConfiguration(final LoggerContext loggerContext, final ConfigurationSource source) {
+ final ConfigurationBuilder<BuiltConfiguration> builder;
+ try (final InputStream configStream = source.getInputStream()) {
+ builder = new Log4j1ConfigurationParser().buildConfigurationBuilder(configStream);
+ } catch (final IOException e) {
+ throw new ConfigurationException("Unable to load " + source, e);
+ }
+ return builder.build();
+ }
+
+ @Override
+ protected String[] getSupportedTypes() {
+ return SUFFIXES;
+ }
+
+}
diff --git a/log4j-1.2-api/src/src/main/java/org/apache/log4j/config/Log4j1ConfigurationParser.java b/log4j-1.2-api/src/src/main/java/org/apache/log4j/config/Log4j1ConfigurationParser.java
new file mode 100644
index 0000000..112ab42
--- /dev/null
+++ b/log4j-1.2-api/src/src/main/java/org/apache/log4j/config/Log4j1ConfigurationParser.java
@@ -0,0 +1,446 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Properties;
+import java.util.TreeMap;
+
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.core.appender.ConsoleAppender;
+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.config.ConfigurationException;
+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.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;
+
+/**
+ * Experimental parser for Log4j 1.2 properties configuration files.
+ *
+ * This class is not thread-safe.
+ *
+ * <p>
+ * From the Log4j 1.2 Javadocs:
+ * </p>
+ * <p>
+ * All option values admit variable substitution. The syntax of variable substitution is similar to that of Unix shells. The string between
+ * an opening "${" and closing "}" is interpreted as a key. The value of the substituted variable can be defined as a system property or in
+ * the configuration file itself. The value of the key is first searched in the system properties, and if not found there, it is then
+ * searched in the configuration file being parsed. The corresponding value replaces the ${variableName} sequence. For example, if java.home
+ * system property is set to /home/xyz, then every occurrence of the sequence ${java.home} will be interpreted as /home/xyz.
+ * </p>
+ */
+public class Log4j1ConfigurationParser {
+
+ private static final String COMMA_DELIMITED_RE = "\\s*,\\s*";
+ private static final String ROOTLOGGER = "rootLogger";
+ private static final String ROOTCATEGORY = "rootCategory";
+ private static final String TRUE = "true";
+ private static final String FALSE = "false";
+
+ private final Properties properties = new Properties();
+ private StrSubstitutor strSubstitutorProperties;
+ private StrSubstitutor strSubstitutorSystem;
+
+ private final ConfigurationBuilder<BuiltConfiguration> 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}
+ * @throws IOException
+ * if unable to read the input
+ * @throws ConfigurationException
+ * if the input does not contain a valid configuration
+ */
+ public ConfigurationBuilder<BuiltConfiguration> buildConfigurationBuilder(final InputStream input)
+ 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) {
+ // This is not a Log4j 1 properties configuration file.
+ warn("Missing " + ROOTCATEGORY + " or " + ROOTLOGGER + " in " + input);
+ // throw new ConfigurationException(
+ // "Missing " + ROOTCATEGORY + " or " + ROOTLOGGER + " in " + input);
+ }
+ builder.setConfigurationName("Log4j1");
+ // DEBUG
+ final String debugValue = getLog4jValue("debug");
+ if (Boolean.valueOf(debugValue)) {
+ builder.setStatusLevel(Level.DEBUG);
+ }
+ // Root
+ buildRootLogger(getLog4jValue(ROOTCATEGORY));
+ buildRootLogger(getLog4jValue(ROOTLOGGER));
+ // Appenders
+ final Map<String, String> appenderNameToClassName = buildClassToPropertyPrefixMap();
+ for (final Map.Entry<String, String> entry : appenderNameToClassName.entrySet()) {
+ final String appenderName = entry.getKey();
+ final String appenderClass = entry.getValue();
+ buildAppender(appenderName, appenderClass);
+ }
+ // Loggers
+ buildLoggers("log4j.category.");
+ buildLoggers("log4j.logger.");
+ buildProperties();
+ return builder;
+ } catch (final IllegalArgumentException e) {
+ throw new ConfigurationException(e);
+ }
+ }
+
+ private void buildProperties() {
+ for (final Map.Entry<Object, Object> entry : new TreeMap<>(properties).entrySet()) {
+ final String key = entry.getKey().toString();
+ if (!key.startsWith("log4j.") && !key.equals(ROOTCATEGORY) && !key.equals(ROOTLOGGER)) {
+ builder.addProperty(key, Objects.toString(entry.getValue(), Strings.EMPTY));
+ }
+ }
+ }
+
+ private void warn(final String string) {
+ System.err.println(string);
+ }
+
+ private Map<String, String> buildClassToPropertyPrefixMap() {
+ final String prefix = "log4j.appender.";
+ final int preLength = prefix.length();
+ final Map<String, String> map = new HashMap<>();
+ for (final Map.Entry<Object, Object> entry : properties.entrySet()) {
+ final Object keyObj = entry.getKey();
+ if (keyObj != null) {
+ final String key = keyObj.toString();
+ 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());
+ }
+ }
+ }
+ }
+ }
+ return map;
+ }
+
+ 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);
+ }
+ }
+
+ private void buildConsoleAppender(final String appenderName) {
+ final AppenderComponentBuilder appenderBuilder = builder.newAppender(appenderName, ConsoleAppender.PLUGIN_NAME);
+ final String targetValue = getLog4jAppenderValue(appenderName, "Target", "System.out");
+ 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;
+ }
+ if (target != null) {
+ appenderBuilder.addAttribute("target", target);
+ }
+ }
+ buildAttribute(appenderName, appenderBuilder, "Follow", "follow");
+ if (FALSE.equalsIgnoreCase(getLog4jAppenderValue(appenderName, "ImmediateFlush"))) {
+ reportWarning("ImmediateFlush=false is not supported on Console appender");
+ }
+ buildAppenderLayout(appenderName, appenderBuilder);
+ builder.add(appenderBuilder);
+ }
+
+ private void buildFileAppender(final String appenderName) {
+ final AppenderComponentBuilder appenderBuilder = builder.newAppender(appenderName, FileAppender.PLUGIN_NAME);
+ buildFileAppender(appenderName, appenderBuilder);
+ builder.add(appenderBuilder);
+ }
+
+ private void buildFileAppender(final String appenderName, final AppenderComponentBuilder appenderBuilder) {
+ buildMandatoryAttribute(appenderName, appenderBuilder, "File", "fileName");
+ buildAttribute(appenderName, appenderBuilder, "Append", "append");
+ buildAttribute(appenderName, appenderBuilder, "BufferedIO", "bufferedIo");
+ buildAttribute(appenderName, appenderBuilder, "BufferSize", "bufferSize");
+ buildAttribute(appenderName, appenderBuilder, "ImmediateFlush", "immediateFlush");
+ buildAppenderLayout(appenderName, appenderBuilder);
+ }
+
+ private void buildDailyRollingFileAppender(final String appenderName) {
+ 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");
+ 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));
+ builder.add(appenderBuilder);
+ }
+
+ private void buildRollingFileAppender(final String appenderName) {
+ 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));
+ appenderBuilder.addComponent(triggeringPolicy);
+ appenderBuilder.addComponent(
+ builder.newComponent("DefaultRolloverStrategy").addAttribute("max", maxBackupIndexString));
+ builder.add(appenderBuilder);
+ }
+
+ 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) {
+ final String attributeValue = getLog4jAppenderValue(componentName, sourceAttributeName);
+ if (attributeValue != null) {
+ componentBuilder.addAttribute(targetAttributeName, attributeValue);
+ } else {
+ reportWarning("Missing " + sourceAttributeName + " for " + componentName);
+ }
+ }
+
+ private void buildNullAppender(final String appenderName) {
+ final AppenderComponentBuilder appenderBuilder = builder.newAppender(appenderName, NullAppender.PLUGIN_NAME);
+ builder.add(appenderBuilder);
+ }
+
+ private void buildAppenderLayout(final String name, final AppenderComponentBuilder appenderBuilder) {
+ 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] ";
+ }
+ 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;
+ }
+ 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);
+ }
+ }
+ }
+
+ private LayoutComponentBuilder newPatternLayout(final String pattern) {
+ final LayoutComponentBuilder layoutBuilder = builder.newLayout("PatternLayout");
+ if (pattern != null) {
+ layoutBuilder.addAttribute("pattern", pattern);
+ }
+ return layoutBuilder;
+ }
+
+ private void buildRootLogger(final String rootLoggerValue) {
+ if (rootLoggerValue == null) {
+ return;
+ }
+ final String[] rootLoggerParts = rootLoggerValue.split(COMMA_DELIMITED_RE);
+ final String rootLoggerLevel = getLevelString(rootLoggerParts, Level.ERROR.name());
+ final RootLoggerComponentBuilder loggerBuilder = builder.newRootLogger(rootLoggerLevel);
+ //
+ final String[] sortedAppenderNames = Arrays.copyOfRange(rootLoggerParts, 1, rootLoggerParts.length);
+ Arrays.sort(sortedAppenderNames);
+ for (final String appender : sortedAppenderNames) {
+ loggerBuilder.add(builder.newAppenderRef(appender));
+ }
+ builder.add(loggerBuilder);
+ }
+
+ private String getLevelString(final String[] loggerParts, final String defaultLevel) {
+ return loggerParts.length > 0 ? loggerParts[0] : defaultLevel;
+ }
+
+ private void buildLoggers(final String prefix) {
+ final int preLength = prefix.length();
+ for (final Map.Entry<Object, Object> entry : properties.entrySet()) {
+ final Object keyObj = entry.getKey();
+ if (keyObj != null) {
+ final String key = keyObj.toString();
+ 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[] split = valueStr.split(COMMA_DELIMITED_RE);
+ final String level = getLevelString(split, null);
+ if (level == null) {
+ warn("Level is missing for entry " + entry);
+ } else {
+ final LoggerComponentBuilder newLogger = builder.newLogger(name, level);
+ if (split.length > 1) {
+ // Add Appenders to this logger
+ final String[] sortedAppenderNames = Arrays.copyOfRange(split, 1, split.length);
+ Arrays.sort(sortedAppenderNames);
+ for (final String appenderName : sortedAppenderNames) {
+ newLogger.add(builder.newAppenderRef(appenderName));
+ }
+ }
+ builder.add(newLogger);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private String getLog4jAppenderValue(final String appenderName, final String attributeName) {
+ return getProperty("log4j.appender." + appenderName + "." + attributeName);
+ }
+
+ private String getProperty(final String key) {
+ final String value = properties.getProperty(key);
+ final String sysValue = strSubstitutorSystem.replace(value);
+ return strSubstitutorProperties.replace(sysValue);
+ }
+
+ private String getProperty(final String key, final String defaultValue) {
+ final String value = getProperty(key);
+ return value == null ? defaultValue : value;
+ }
+
+ private String getLog4jAppenderValue(final String appenderName, final String attributeName,
+ final String defaultValue) {
+ return getProperty("log4j.appender." + appenderName + "." + attributeName, defaultValue);
+ }
+
+ private String getLog4jValue(final String key) {
+ return getProperty("log4j." + key);
+ }
+
+ private void reportWarning(final String msg) {
+ StatusLogger.getLogger().warn("Log4j 1 configuration parser: " + msg);
+ }
+
+}
diff --git a/log4j-1.2-api/src/src/main/java/org/apache/log4j/config/PropertySetter.java b/log4j-1.2-api/src/src/main/java/org/apache/log4j/config/PropertySetter.java
new file mode 100644
index 0000000..5982278
--- /dev/null
+++ b/log4j-1.2-api/src/src/main/java/org/apache/log4j/config/PropertySetter.java
@@ -0,0 +1,287 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Contributors: Georg Lundesgaard
+
+package org.apache.log4j.config;
+
+import org.apache.log4j.Appender;
+import org.apache.log4j.Level;
+import org.apache.log4j.Priority;
+import org.apache.log4j.spi.ErrorHandler;
+import org.apache.log4j.spi.OptionHandler;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.core.util.OptionConverter;
+import org.apache.logging.log4j.status.StatusLogger;
+
+import java.beans.BeanInfo;
+import java.beans.IntrospectionException;
+import java.beans.Introspector;
+import java.beans.PropertyDescriptor;
+import java.io.InterruptedIOException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Properties;
+
+/**
+ * General purpose Object property setter. Clients repeatedly invokes
+ * {@link #setProperty setProperty(name,value)} in order to invoke setters
+ * on the Object specified in the constructor. This class relies on the
+ * JavaBeans {@link Introspector} to analyze the given Object Class using
+ * reflection.
+ *
+ * <p>Usage:
+ * <pre>
+ * PropertySetter ps = new PropertySetter(anObject);
+ * ps.set("name", "Joe");
+ * ps.set("age", "32");
+ * ps.set("isMale", "true");
+ * </pre>
+ * 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 Logger LOGGER = StatusLogger.getLogger();
+ protected Object obj;
+ protected PropertyDescriptor[] props;
+
+ /**
+ * Create a new PropertySetter for the specified Object. This is done
+ * in prepartion for invoking {@link #setProperty} one or more times.
+ *
+ * @param obj the object for which to set properties
+ */
+ public PropertySetter(Object obj) {
+ this.obj = obj;
+ }
+
+ /**
+ * Set the properties of an object passed as a parameter in one
+ * go. The <code>properties</code> are parsed relative to a
+ * <code>prefix</code>.
+ *
+ * @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(Object obj, Properties properties, String prefix) {
+ new PropertySetter(obj).setProperties(properties, prefix);
+ }
+
+ /**
+ * Uses JavaBeans {@link Introspector} to computer setters of object to be
+ * configured.
+ */
+ protected void introspect() {
+ try {
+ BeanInfo bi = Introspector.getBeanInfo(obj.getClass());
+ props = bi.getPropertyDescriptors();
+ } catch (IntrospectionException ex) {
+ LOGGER.error("Failed to introspect {}: {}", obj, ex.getMessage());
+ props = new PropertyDescriptor[0];
+ }
+ }
+
+ /**
+ * Set the properites for the object that match the
+ * <code>prefix</code> passed as parameter.
+ */
+ public void setProperties(Properties properties, String prefix) {
+ int len = prefix.length();
+
+ for (String key : properties.stringPropertyNames()) {
+
+ // handle only properties that start with the desired frefix.
+ if (key.startsWith(prefix)) {
+
+
+ // ignore key if it contains dots after the prefix
+ if (key.indexOf('.', len + 1) > 0) {
+ continue;
+ }
+
+ String value = OptionConverter.findAndSubst(key, properties);
+ key = key.substring(len);
+ if (("layout".equals(key) || "errorhandler".equals(key)) && obj instanceof Appender) {
+ continue;
+ }
+ //
+ // if the property type is an OptionHandler
+ // (for example, triggeringPolicy of org.apache.log4j.rolling.RollingFileAppender)
+ PropertyDescriptor prop = getPropertyDescriptor(Introspector.decapitalize(key));
+ if (prop != null
+ && OptionHandler.class.isAssignableFrom(prop.getPropertyType())
+ && prop.getWriteMethod() != null) {
+ OptionHandler opt = (OptionHandler)
+ OptionConverter.instantiateByKey(properties, prefix + key,
+ prop.getPropertyType(),
+ null);
+ PropertySetter setter = new PropertySetter(opt);
+ setter.setProperties(properties, prefix + key + ".");
+ try {
+ prop.getWriteMethod().invoke(this.obj, opt);
+ } catch (InvocationTargetException ex) {
+ if (ex.getTargetException() instanceof InterruptedException
+ || ex.getTargetException() instanceof InterruptedIOException) {
+ Thread.currentThread().interrupt();
+ }
+ LOGGER.warn("Failed to set property [{}] to value \"{}\".", key, value, ex);
+ } catch (IllegalAccessException | RuntimeException ex) {
+ LOGGER.warn("Failed to set property [{}] to value \"{}\".", key, value, ex);
+ }
+ continue;
+ }
+
+ setProperty(key, value);
+ }
+ }
+ activate();
+ }
+
+ /**
+ * Set a property on this PropertySetter's Object. If successful, this
+ * method will invoke a setter method on the underlying Object. The
+ * setter is the one for the specified property name and the value is
+ * determined partly from the setter argument type and partly from the
+ * value specified in the call to this method.
+ *
+ * <p>If the setter expects a String no conversion is necessary.
+ * If it expects an int, then an attempt is made to convert 'value'
+ * 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
+ */
+ public void setProperty(String name, String value) {
+ if (value == null) {
+ return;
+ }
+
+ name = Introspector.decapitalize(name);
+ PropertyDescriptor prop = getPropertyDescriptor(name);
+
+ //LOGGER.debug("---------Key: "+name+", type="+prop.getPropertyType());
+
+ if (prop == null) {
+ LOGGER.warn("No such property [" + name + "] in " +
+ obj.getClass().getName() + ".");
+ } else {
+ try {
+ setProperty(prop, name, value);
+ } catch (PropertySetterException ex) {
+ LOGGER.warn("Failed to set property [{}] to value \"{}\".", name, value, ex.rootCause);
+ }
+ }
+ }
+
+ /**
+ * Set the named property given a {@link PropertyDescriptor}.
+ *
+ * @param prop A PropertyDescriptor describing the characteristics
+ * of the property to set.
+ * @param name The named of the property to set.
+ * @param value The value of the property.
+ */
+ public void setProperty(PropertyDescriptor prop, String name, String value)
+ throws PropertySetterException {
+ Method setter = prop.getWriteMethod();
+ if (setter == null) {
+ throw new PropertySetterException("No setter for property [" + name + "].");
+ }
+ Class[] paramTypes = setter.getParameterTypes();
+ if (paramTypes.length != 1) {
+ throw new PropertySetterException("#params for setter != 1");
+ }
+
+ Object arg;
+ try {
+ arg = convertArg(value, paramTypes[0]);
+ } catch (Throwable t) {
+ throw new PropertySetterException("Conversion to type [" + paramTypes[0] +
+ "] failed. Reason: " + t);
+ }
+ if (arg == null) {
+ throw new PropertySetterException(
+ "Conversion to type [" + paramTypes[0] + "] failed.");
+ }
+ LOGGER.debug("Setting property [" + name + "] to [" + arg + "].");
+ try {
+ setter.invoke(obj, arg);
+ } catch (InvocationTargetException ex) {
+ if (ex.getTargetException() instanceof InterruptedException
+ || ex.getTargetException() instanceof InterruptedIOException) {
+ Thread.currentThread().interrupt();
+ }
+ throw new PropertySetterException(ex);
+ } catch (IllegalAccessException | RuntimeException ex) {
+ throw new PropertySetterException(ex);
+ }
+ }
+
+
+ /**
+ * Convert <code>val</code> a String parameter to an object of a
+ * given type.
+ */
+ protected Object convertArg(String val, Class type) {
+ if (val == null) {
+ return null;
+ }
+
+ String v = val.trim();
+ if (String.class.isAssignableFrom(type)) {
+ return val;
+ } else if (Integer.TYPE.isAssignableFrom(type)) {
+ return Integer.parseInt(v);
+ } else if (Long.TYPE.isAssignableFrom(type)) {
+ return Long.parseLong(v);
+ } else if (Boolean.TYPE.isAssignableFrom(type)) {
+ if ("true".equalsIgnoreCase(v)) {
+ return Boolean.TRUE;
+ } else if ("false".equalsIgnoreCase(v)) {
+ return Boolean.FALSE;
+ }
+ } else if (Priority.class.isAssignableFrom(type)) {
+ return org.apache.log4j.helpers.OptionConverter.toLevel(v, Level.DEBUG);
+ } 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/src/main/java/org/apache/log4j/config/PropertySetterException.java b/log4j-1.2-api/src/src/main/java/org/apache/log4j/config/PropertySetterException.java
new file mode 100644
index 0000000..c9dc4cf
--- /dev/null
+++ b/log4j-1.2-api/src/src/main/java/org/apache/log4j/config/PropertySetterException.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.config;
+
+/**
+ * Thrown when an error is encountered whilst attempting to set a property
+ * using the {@link PropertySetter} utility class.
+ *
+ * @since 1.1
+ */
+public class PropertySetterException extends Exception {
+ private static final long serialVersionUID = -1352613734254235861L;
+
+ /**
+ * The root cause.
+ */
+ protected Throwable rootCause;
+
+ /**
+ * Construct the exception with the given message.
+ *
+ * @param msg The message
+ */
+ public PropertySetterException(final String msg) {
+ super(msg);
+ }
+
+ /**
+ * Construct the exception with the given root cause.
+ *
+ * @param rootCause The root cause
+ */
+ public PropertySetterException(final Throwable rootCause) {
+ super();
+ this.rootCause = rootCause;
+ }
+
+ /**
+ * Returns descriptive text on the cause of this exception.
+ *
+ * @return the descriptive text.
+ */
+ @Override
+ public String getMessage() {
+ String msg = super.getMessage();
+ if (msg == null && rootCause != null) {
+ msg = rootCause.getMessage();
+ }
+ return msg;
+ }
+}
diff --git a/log4j-1.2-api/src/src/main/java/org/apache/log4j/config/package-info.java b/log4j-1.2-api/src/src/main/java/org/apache/log4j/config/package-info.java
new file mode 100644
index 0000000..7f96630
--- /dev/null
+++ b/log4j-1.2-api/src/src/main/java/org/apache/log4j/config/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+/**
+ * Log4j 1.x compatibility layer.
+ */
+package org.apache.log4j.config;
diff --git a/log4j-1.2-api/src/src/main/java/org/apache/log4j/helpers/NullEnumeration.java b/log4j-1.2-api/src/src/main/java/org/apache/log4j/helpers/NullEnumeration.java
new file mode 100644
index 0000000..d064004
--- /dev/null
+++ b/log4j-1.2-api/src/src/main/java/org/apache/log4j/helpers/NullEnumeration.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.helpers;
+
+import java.util.Enumeration;
+import java.util.NoSuchElementException;
+
+/**
+ * An always-empty Enumerator.
+ *
+ * @since version 1.0
+ */
+@SuppressWarnings("rawtypes")
+public final class NullEnumeration implements Enumeration {
+ private static final NullEnumeration INSTANCE = new NullEnumeration();
+
+ private NullEnumeration() {
+ }
+
+ public static NullEnumeration getInstance() {
+ return INSTANCE;
+ }
+
+ @Override
+ public boolean hasMoreElements() {
+ return false;
+ }
+
+ @Override
+ public Object nextElement() {
+ throw new NoSuchElementException();
+ }
+}
diff --git a/log4j-1.2-api/src/src/main/java/org/apache/log4j/helpers/OptionConverter.java b/log4j-1.2-api/src/src/main/java/org/apache/log4j/helpers/OptionConverter.java
new file mode 100644
index 0000000..8ee0ea5
--- /dev/null
+++ b/log4j-1.2-api/src/src/main/java/org/apache/log4j/helpers/OptionConverter.java
@@ -0,0 +1,345 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.helpers;
+
+import org.apache.log4j.Level;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.util.LoaderUtil;
+
+import java.io.InterruptedIOException;
+import java.lang.reflect.InvocationTargetException;
+import java.util.Properties;
+
+/**
+ * A convenience class to convert property values to specific types.
+ */
+public class OptionConverter {
+
+ 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 = LogManager.getLogger(OptionConverter.class);
+ 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('\\', '\\')
+ };
+
+ /**
+ * OptionConverter is a static class.
+ */
+ private OptionConverter() {
+ }
+
+ public static String[] concatanateArrays(String[] l, String[] r) {
+ int len = l.length + r.length;
+ String[] a = new String[len];
+
+ System.arraycopy(l, 0, a, 0, l.length);
+ System.arraycopy(r, 0, a, l.length, r.length);
+
+ return a;
+ }
+
+ public static String convertSpecialChars(String s) {
+ char c;
+ int len = s.length();
+ StringBuilder sbuf = new StringBuilder(len);
+
+ int i = 0;
+ while (i < len) {
+ c = s.charAt(i++);
+ if (c == '\\') {
+ c = s.charAt(i++);
+ for (CharMap entry : charMap) {
+ if (entry.key == c) {
+ c = entry.replacement;
+ }
+ }
+ }
+ sbuf.append(c);
+ }
+ return sbuf.toString();
+ }
+
+
+ /**
+ * Very similar to <code>System.getProperty</code> 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(String key, String def) {
+ try {
+ return System.getProperty(key, def);
+ } catch (Throwable e) { // MS-Java throws com.ms.security.SecurityExceptionEx
+ LOGGER.debug("Was not allowed to read system property \"{}\".", key);
+ return def;
+ }
+ }
+
+ /**
+ * If <code>value</code> is "true", then <code>true</code> is
+ * returned. If <code>value</code> is "false", then
+ * <code>true</code> is returned. Otherwise, <code>default</code> is
+ * returned.
+ *
+ * <p>Case of value is unimportant.
+ */
+ public static boolean toBoolean(String value, boolean dEfault) {
+ if (value == null) {
+ return dEfault;
+ }
+ String trimmedVal = value.trim();
+ if ("true".equalsIgnoreCase(trimmedVal)) {
+ return true;
+ }
+ if ("false".equalsIgnoreCase(trimmedVal)) {
+ return false;
+ }
+ return dEfault;
+ }
+
+ /**
+ * Converts a standard or custom priority level to a Level
+ * object. <p> If <code>value</code> 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.
+ *
+ * <p>As a special case, if the <code>value</code> parameter is
+ * equal to the string "NULL", then the value <code>null</code> will
+ * be returned.
+ *
+ * <p> If any error occurs while converting the value to a level,
+ * the <code>defaultValue</code> parameter, which may be
+ * <code>null</code>, is returned.
+ *
+ * <p> Case of <code>value</code> is insignificant for the level level, but is
+ * significant for the class name part, if present.
+ *
+ * @since 1.1
+ */
+ public static Level toLevel(String value, Level defaultValue) {
+ if (value == null) {
+ return defaultValue;
+ }
+
+ value = value.trim();
+
+ int hashIndex = value.indexOf('#');
+ if (hashIndex == -1) {
+ if ("NULL".equalsIgnoreCase(value)) {
+ return null;
+ } else {
+ // no class name specified : use standard Level class
+ return Level.toLevel(value, defaultValue);
+ }
+ }
+
+ Level result = defaultValue;
+
+ String clazz = value.substring(hashIndex + 1);
+ String levelName = value.substring(0, hashIndex);
+
+ // This is degenerate case but you never know.
+ if ("NULL".equalsIgnoreCase(levelName)) {
+ return null;
+ }
+
+ LOGGER.debug("toLevel" + ":class=[" + clazz + "]"
+ + ":pri=[" + levelName + "]");
+
+ try {
+ Class customLevel = LoaderUtil.loadClass(clazz);
+
+ // get a ref to the specified class' static method
+ // toLevel(String, org.apache.log4j.Level)
+ Class[] paramTypes = new Class[]{String.class,
+ org.apache.log4j.Level.class
+ };
+ java.lang.reflect.Method toLevelMethod =
+ customLevel.getMethod("toLevel", paramTypes);
+
+ // now call the toLevel method, passing level string + default
+ Object[] params = new Object[]{levelName, defaultValue};
+ Object o = toLevelMethod.invoke(null, params);
+
+ result = (Level) o;
+ } catch (ClassNotFoundException e) {
+ LOGGER.warn("custom level class [" + clazz + "] not found.");
+ } catch (NoSuchMethodException e) {
+ LOGGER.warn("custom level class [" + clazz + "]"
+ + " does not have a class function toLevel(String, Level)", e);
+ } catch (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 (ClassCastException e) {
+ LOGGER.warn("class [" + clazz
+ + "] is not a subclass of org.apache.log4j.Level", e);
+ } catch (IllegalAccessException e) {
+ LOGGER.warn("class [" + clazz +
+ "] cannot be instantiated due to access restrictions", e);
+ } catch (RuntimeException e) {
+ LOGGER.warn("class [" + clazz + "], level [" + levelName +
+ "] conversion failed.", e);
+ }
+ return result;
+ }
+
+ /**
+ * Instantiate an object given a class name. Check that the
+ * <code>className</code> is a subclass of
+ * <code>superClass</code>. If that test fails or the object could
+ * not be instantiated, then <code>defaultValue</code> 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
+ */
+ public static Object instantiateByClassName(String className, Class<?> superClass,
+ Object defaultValue) {
+ if (className != null) {
+ try {
+ 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 (ClassNotFoundException | NoSuchMethodException | IllegalAccessException
+ | InstantiationException | InvocationTargetException e) {
+ LOGGER.error("Could not instantiate class [" + className + "].", e);
+ }
+ }
+ return defaultValue;
+ }
+
+
+ /**
+ * Perform variable substitution in string <code>val</code> from the
+ * values of keys found in the system propeties.
+ *
+ * <p>The variable substitution delimeters are <b>${</b> and <b>}</b>.
+ *
+ * <p>For example, if the System properties contains "key=value", then
+ * the call
+ * <pre>
+ * String s = OptionConverter.substituteVars("Value of key is ${key}.");
+ * </pre>
+ * <p>
+ * will set the variable <code>s</code> to "Value of key is value.".
+ *
+ * <p>If no value could be found for the specified key, then the
+ * <code>props</code> parameter is searched, if the value could not
+ * be found there, then substitution defaults to the empty string.
+ *
+ * <p>For example, if system propeties contains no value for the key
+ * "inexistentKey", then the call
+ *
+ * <pre>
+ * String s = OptionConverter.subsVars("Value of inexistentKey is [${inexistentKey}]");
+ * </pre>
+ * will set <code>s</code> to "Value of inexistentKey is []"
+ *
+ * <p>An {@link IllegalArgumentException} is thrown if
+ * <code>val</code> contains a start delimeter "${" which is not
+ * balanced by a stop delimeter "}". </p>
+ *
+ * <p><b>Author</b> Avy Sharell</a></p>
+ *
+ * @param val The string on which variable substitution is performed.
+ * @throws IllegalArgumentException if <code>val</code> is malformed.
+ */
+ public static String substVars(String val, Properties props) throws IllegalArgumentException {
+
+ StringBuilder sbuf = new StringBuilder();
+
+ int i = 0;
+ int j, 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;
+ } else { // add the tail string which contails no variables and return the result.
+ sbuf.append(val.substring(i, val.length()));
+ return sbuf.toString();
+ }
+ } else {
+ sbuf.append(val.substring(i, j));
+ k = val.indexOf(DELIM_STOP, j);
+ if (k == -1) {
+ throw new IllegalArgumentException('"' + val +
+ "\" has no closing brace. Opening brace at position " + j
+ + '.');
+ } else {
+ j += DELIM_START_LEN;
+ String key = val.substring(j, k);
+ // first try in System properties
+ String replacement = getSystemProperty(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}
+ String recursiveReplacement = substVars(replacement, props);
+ sbuf.append(recursiveReplacement);
+ }
+ i = k + DELIM_STOP_LEN;
+ }
+ }
+ }
+ }
+
+ private static class CharMap {
+ final char key;
+ final char replacement;
+
+ public CharMap(char key, char replacement) {
+ this.key = key;
+ this.replacement = replacement;
+ }
+ }
+}
diff --git a/log4j-1.2-api/src/src/main/java/org/apache/log4j/helpers/QuietWriter.java b/log4j-1.2-api/src/src/main/java/org/apache/log4j/helpers/QuietWriter.java
new file mode 100644
index 0000000..1779019
--- /dev/null
+++ b/log4j-1.2-api/src/src/main/java/org/apache/log4j/helpers/QuietWriter.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.log4j.helpers;
+
+import org.apache.log4j.spi.ErrorCode;
+import org.apache.log4j.spi.ErrorHandler;
+
+import java.io.FilterWriter;
+import java.io.Writer;
+
+
+/**
+ * QuietWriter does not throw exceptions when things go
+ * wrong. Instead, it delegates error handling to its {@link ErrorHandler}.
+ */
+public class QuietWriter extends FilterWriter {
+
+ protected ErrorHandler errorHandler;
+
+ public QuietWriter(Writer writer, ErrorHandler errorHandler) {
+ super(writer);
+ setErrorHandler(errorHandler);
+ }
+
+ public void write(String string) {
+ if (string != null) {
+ try {
+ out.write(string);
+ } catch (Exception e) {
+ errorHandler.error("Failed to write [" + string + "].", e,
+ ErrorCode.WRITE_FAILURE);
+ }
+ }
+ }
+
+ public void flush() {
+ try {
+ out.flush();
+ } catch (Exception e) {
+ errorHandler.error("Failed to flush writer,", e,
+ ErrorCode.FLUSH_FAILURE);
+ }
+ }
+
+
+ public void setErrorHandler(ErrorHandler eh) {
+ if (eh == null) {
+ // This is a programming error on the part of the enclosing appender.
+ throw new IllegalArgumentException("Attempted to set null ErrorHandler.");
+ } else {
+ this.errorHandler = eh;
+ }
+ }
+}
diff --git a/log4j-1.2-api/src/src/main/java/org/apache/log4j/helpers/package-info.java b/log4j-1.2-api/src/src/main/java/org/apache/log4j/helpers/package-info.java
new file mode 100644
index 0000000..00d0e12
--- /dev/null
+++ b/log4j-1.2-api/src/src/main/java/org/apache/log4j/helpers/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+/**
+ * Log4j 1.x compatibility layer.
+ */
+package org.apache.log4j.helpers;
diff --git a/log4j-1.2-api/src/src/main/java/org/apache/log4j/layout/Log4j1XmlLayout.java b/log4j-1.2-api/src/src/main/java/org/apache/log4j/layout/Log4j1XmlLayout.java
new file mode 100644
index 0000000..9522b9e
--- /dev/null
+++ b/log4j-1.2-api/src/src/main/java/org/apache/log4j/layout/Log4j1XmlLayout.java
@@ -0,0 +1,159 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.log4j.layout;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+
+import org.apache.logging.log4j.core.Layout;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.config.Node;
+import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
+import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+import org.apache.logging.log4j.core.layout.AbstractStringLayout;
+import org.apache.logging.log4j.core.layout.ByteBufferDestination;
+import org.apache.logging.log4j.core.util.Transform;
+import org.apache.logging.log4j.util.BiConsumer;
+import org.apache.logging.log4j.util.ReadOnlyStringMap;
+import org.apache.logging.log4j.util.Strings;
+
+/**
+ * Port of XMLLayout in Log4j 1.x. Provided for compatibility with existing Log4j 1 configurations.
+ *
+ * Originally developed by Ceki Gülcü, Mathias Bogaert.
+ */
+@Plugin(name = "Log4j1XmlLayout", category = Node.CATEGORY, elementType = Layout.ELEMENT_TYPE, printObject = true)
+public final class Log4j1XmlLayout extends AbstractStringLayout {
+
+ private final boolean locationInfo;
+ private final boolean properties;
+
+ @PluginFactory
+ public static Log4j1XmlLayout createLayout(
+ // @formatter:off
+ @PluginAttribute(value = "locationInfo") final boolean locationInfo,
+ @PluginAttribute(value = "properties") final boolean properties
+ // @formatter:on
+ ) {
+ return new Log4j1XmlLayout(locationInfo, properties);
+ }
+
+ private Log4j1XmlLayout(final boolean locationInfo, final boolean properties) {
+ super(StandardCharsets.UTF_8);
+ this.locationInfo = locationInfo;
+ this.properties = properties;
+ }
+
+ public boolean isLocationInfo() {
+ return locationInfo;
+ }
+
+ public boolean isProperties() {
+ return properties;
+ }
+
+ @Override
+ public void encode(final LogEvent event, final ByteBufferDestination destination) {
+ final StringBuilder text = getStringBuilder();
+ formatTo(event, text);
+ getStringBuilderEncoder().encode(text, destination);
+ }
+
+ @Override
+ public String toSerializable(final LogEvent event) {
+ final StringBuilder text = getStringBuilder();
+ formatTo(event, text);
+ return text.toString();
+ }
+
+ private void formatTo(final LogEvent event, final StringBuilder buf) {
+ // We yield to the \r\n heresy.
+
+ buf.append("<log4j:event logger=\"");
+ buf.append(Transform.escapeHtmlTags(event.getLoggerName()));
+ buf.append("\" timestamp=\"");
+ buf.append(event.getTimeMillis());
+ buf.append("\" level=\"");
+ buf.append(Transform.escapeHtmlTags(String.valueOf(event.getLevel())));
+ buf.append("\" thread=\"");
+ buf.append(Transform.escapeHtmlTags(event.getThreadName()));
+ buf.append("\">\r\n");
+
+ buf.append("<log4j:message><![CDATA[");
+ // Append the rendered message. Also make sure to escape any existing CDATA sections.
+ Transform.appendEscapingCData(buf, event.getMessage().getFormattedMessage());
+ buf.append("]]></log4j:message>\r\n");
+
+ final List<String> ndc = event.getContextStack().asList();
+ if (!ndc.isEmpty()) {
+ buf.append("<log4j:NDC><![CDATA[");
+ Transform.appendEscapingCData(buf, Strings.join(ndc, ' '));
+ buf.append("]]></log4j:NDC>\r\n");
+ }
+
+ @SuppressWarnings("ThrowableResultOfMethodCallIgnored")
+ final Throwable thrown = event.getThrown();
+ if (thrown != null) {
+ buf.append("<log4j:throwable><![CDATA[");
+ final StringWriter w = new StringWriter();
+ thrown.printStackTrace(new PrintWriter(w));
+ Transform.appendEscapingCData(buf, w.toString());
+ buf.append("]]></log4j:throwable>\r\n");
+ }
+
+ if (locationInfo) {
+ final StackTraceElement source = event.getSource();
+ if (source != null) {
+ buf.append("<log4j:locationInfo class=\"");
+ buf.append(Transform.escapeHtmlTags(source.getClassName()));
+ buf.append("\" method=\"");
+ buf.append(Transform.escapeHtmlTags(source.getMethodName()));
+ buf.append("\" file=\"");
+ buf.append(Transform.escapeHtmlTags(source.getFileName()));
+ buf.append("\" line=\"");
+ buf.append(source.getLineNumber());
+ buf.append("\"/>\r\n");
+ }
+ }
+
+ if (properties) {
+ final ReadOnlyStringMap contextMap = event.getContextData();
+ if (!contextMap.isEmpty()) {
+ buf.append("<log4j:properties>\r\n");
+ contextMap.forEach(new BiConsumer<String, String>() {
+ @Override
+ public void accept(final String key, final String val) {
+ if (val != null) {
+ buf.append("<log4j:data name=\"");
+ buf.append(Transform.escapeHtmlTags(key));
+ buf.append("\" value=\"");
+ buf.append(Transform.escapeHtmlTags(val));
+ buf.append("\"/>\r\n");
+ }
+ }
+ });
+ buf.append("</log4j:properties>\r\n");
+ }
+ }
+
+ buf.append("</log4j:event>\r\n\r\n");
+ }
+
+}
diff --git a/log4j-1.2-api/src/src/main/java/org/apache/log4j/legacy/core/CategoryUtil.java b/log4j-1.2-api/src/src/main/java/org/apache/log4j/legacy/core/CategoryUtil.java
new file mode 100644
index 0000000..f9e9f7c
--- /dev/null
+++ b/log4j-1.2-api/src/src/main/java/org/apache/log4j/legacy/core/CategoryUtil.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.legacy.core;
+
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.spi.LoggerContext;
+
+/**
+ * Provide access to Log4j Core Logger methods.
+ */
+public final class CategoryUtil {
+
+ private CategoryUtil() {
+ }
+
+ public static boolean isAdditive(Logger logger) {
+ if (logger instanceof org.apache.logging.log4j.core.Logger) {
+ return ((org.apache.logging.log4j.core.Logger) logger).isAdditive();
+ }
+ return false;
+ }
+
+ public static void setAdditivity(Logger logger, boolean additivity) {
+ if (logger instanceof org.apache.logging.log4j.core.Logger) {
+ ((org.apache.logging.log4j.core.Logger) logger).setAdditive(additivity);
+ }
+ }
+
+ public static Logger getParent(Logger logger) {
+ if (logger instanceof org.apache.logging.log4j.core.Logger) {
+ return ((org.apache.logging.log4j.core.Logger) logger).getParent();
+
+ }
+ return null;
+ }
+
+ public static LoggerContext getLoggerContext(Logger logger) {
+ if (logger instanceof org.apache.logging.log4j.core.Logger) {
+ return ((org.apache.logging.log4j.core.Logger) logger).getContext();
+ }
+ return null;
+ }
+
+ public static void setLevel(Logger logger, Level level) {
+ if (logger instanceof org.apache.logging.log4j.core.Logger) {
+ ((org.apache.logging.log4j.core.Logger) logger).setLevel(level);
+
+ }
+ }
+}
diff --git a/log4j-1.2-api/src/src/main/java/org/apache/log4j/legacy/core/ContextUtil.java b/log4j-1.2-api/src/src/main/java/org/apache/log4j/legacy/core/ContextUtil.java
new file mode 100644
index 0000000..d3b99fa
--- /dev/null
+++ b/log4j-1.2-api/src/src/main/java/org/apache/log4j/legacy/core/ContextUtil.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.legacy.core;
+
+import org.apache.logging.log4j.spi.LoggerContext;
+
+/**
+ * Implements LoggerContext methods specific to log4j-core.
+ */
+public final class ContextUtil {
+
+ private ContextUtil() {
+ }
+
+ public static void reconfigure(LoggerContext ctx) {
+ if (ctx instanceof org.apache.logging.log4j.core.LoggerContext) {
+ ((org.apache.logging.log4j.core.LoggerContext) ctx).reconfigure();
+ }
+ }
+}
diff --git a/log4j-1.2-api/src/src/main/java/org/apache/log4j/or/ObjectRenderer.java b/log4j-1.2-api/src/src/main/java/org/apache/log4j/or/ObjectRenderer.java
new file mode 100644
index 0000000..f3fed18
--- /dev/null
+++ b/log4j-1.2-api/src/src/main/java/org/apache/log4j/or/ObjectRenderer.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.or;
+
+/**
+ * Converts objects to Strings.
+ */
+public interface ObjectRenderer {
+ /**
+ * Render the object passed as parameter as a String.
+ */
+ String doRender(Object o);
+}
diff --git a/log4j-1.2-api/src/src/main/java/org/apache/log4j/or/RendererSupport.java b/log4j-1.2-api/src/src/main/java/org/apache/log4j/or/RendererSupport.java
new file mode 100644
index 0000000..9b8728d
--- /dev/null
+++ b/log4j-1.2-api/src/src/main/java/org/apache/log4j/or/RendererSupport.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.or;
+
+import java.util.Map;
+
+/**
+ * Interface that indicates the Renderer Map is available. This interface differs
+ */
+public interface RendererSupport {
+ Map<Class<?>, ObjectRenderer> getRendererMap();
+}
diff --git a/log4j-1.2-api/src/src/main/java/org/apache/log4j/or/ThreadGroupRenderer.java b/log4j-1.2-api/src/src/main/java/org/apache/log4j/or/ThreadGroupRenderer.java
new file mode 100644
index 0000000..08233bf
--- /dev/null
+++ b/log4j-1.2-api/src/src/main/java/org/apache/log4j/or/ThreadGroupRenderer.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.log4j.or;
+
+import org.apache.log4j.Layout;
+
+/**
+ */
+public class ThreadGroupRenderer implements ObjectRenderer {
+
+ public
+ String doRender(Object obj) {
+ if(obj instanceof ThreadGroup) {
+ StringBuilder sb = new StringBuilder();
+ ThreadGroup threadGroup = (ThreadGroup) obj;
+ sb.append("java.lang.ThreadGroup[name=");
+ sb.append(threadGroup.getName());
+ sb.append(", maxpri=");
+ sb.append(threadGroup.getMaxPriority());
+ sb.append("]");
+ Thread[] threads = new Thread[threadGroup.activeCount()];
+ threadGroup.enumerate(threads);
+ for (Thread thread : threads) {
+ sb.append(Layout.LINE_SEP);
+ sb.append(" Thread=[");
+ sb.append(thread.getName());
+ sb.append(",");
+ sb.append(thread.getPriority());
+ sb.append(",");
+ sb.append(thread.isDaemon());
+ sb.append("]");
+ }
+ return sb.toString();
+ } else {
+ 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/src/main/java/org/apache/log4j/or/jms/MessageRenderer.java b/log4j-1.2-api/src/src/main/java/org/apache/log4j/or/jms/MessageRenderer.java
new file mode 100644
index 0000000..e6f4b6b
--- /dev/null
+++ b/log4j-1.2-api/src/src/main/java/org/apache/log4j/or/jms/MessageRenderer.java
@@ -0,0 +1,87 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.log4j.or.jms;
+
+import org.apache.log4j.or.ObjectRenderer;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.status.StatusLogger;
+
+import javax.jms.Message;
+import javax.jms.JMSException;
+import javax.jms.DeliveryMode;
+
+/**
+ * Log4j 1.x JMS Message Renderer
+ */
+public class MessageRenderer implements ObjectRenderer {
+ private static final Logger LOGGER = StatusLogger.getLogger();
+
+ /**
+ Render a {@link javax.jms.Message}.
+ */
+ public
+ String doRender(Object obj) {
+ if (obj instanceof Message) {
+ StringBuilder sb = new StringBuilder();
+ Message message = (Message) obj;
+ try {
+ sb.append("DeliveryMode=");
+ switch(message.getJMSDeliveryMode()) {
+ case DeliveryMode.NON_PERSISTENT :
+ sb.append("NON_PERSISTENT");
+ break;
+ case DeliveryMode.PERSISTENT :
+ sb.append("PERSISTENT");
+ break;
+ default: sb.append("UNKNOWN");
+ }
+ sb.append(", CorrelationID=");
+ sb.append(message.getJMSCorrelationID());
+
+ sb.append(", Destination=");
+ sb.append(message.getJMSDestination());
+
+ sb.append(", Expiration=");
+ sb.append(message.getJMSExpiration());
+
+ sb.append(", MessageID=");
+ sb.append(message.getJMSMessageID());
+
+ sb.append(", Priority=");
+ sb.append(message.getJMSPriority());
+
+ sb.append(", Redelivered=");
+ sb.append(message.getJMSRedelivered());
+
+ sb.append(", ReplyTo=");
+ sb.append(message.getJMSReplyTo());
+
+ sb.append(", Timestamp=");
+ sb.append(message.getJMSTimestamp());
+
+ sb.append(", Type=");
+ sb.append(message.getJMSType());
+
+ } catch(JMSException e) {
+ LOGGER.error("Could not parse Message.", e);
+ }
+ return sb.toString();
+ } else {
+ return obj.toString();
+ }
+ }
+}
diff --git a/log4j-1.2-api/src/src/main/java/org/apache/log4j/package-info.java b/log4j-1.2-api/src/src/main/java/org/apache/log4j/package-info.java
new file mode 100644
index 0000000..714c200
--- /dev/null
+++ b/log4j-1.2-api/src/src/main/java/org/apache/log4j/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+/**
+ * Log4j 1.x compatibility layer.
+ */
+package org.apache.log4j;
diff --git a/log4j-1.2-api/src/src/main/java/org/apache/log4j/pattern/Log4j1MdcPatternConverter.java b/log4j-1.2-api/src/src/main/java/org/apache/log4j/pattern/Log4j1MdcPatternConverter.java
new file mode 100644
index 0000000..b4ae0c5
--- /dev/null
+++ b/log4j-1.2-api/src/src/main/java/org/apache/log4j/pattern/Log4j1MdcPatternConverter.java
@@ -0,0 +1,88 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.log4j.pattern;
+
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.core.pattern.ConverterKeys;
+import org.apache.logging.log4j.core.pattern.LogEventPatternConverter;
+import org.apache.logging.log4j.core.pattern.PatternConverter;
+import org.apache.logging.log4j.util.TriConsumer;
+
+/**
+ * Able to handle the contents of the LogEvent's MDC and either
+ * output the entire contents of the properties, or to output the value of a specific key
+ * within the property bundle when this pattern converter has the option set.
+ */
+@Plugin(name = "Log4j1MdcPatternConverter", category = PatternConverter.CATEGORY)
+@ConverterKeys({ "properties" })
+public final class Log4j1MdcPatternConverter extends LogEventPatternConverter {
+ /**
+ * Name of property to output.
+ */
+ private final String key;
+
+ /**
+ * Private constructor.
+ *
+ * @param options options, may be null.
+ */
+ private Log4j1MdcPatternConverter(final String[] options) {
+ super(options != null && options.length > 0 ? "Log4j1MDC{" + options[0] + '}' : "Log4j1MDC", "property");
+ if (options != null && options.length > 0) {
+ key = options[0];
+ } else {
+ key = null;
+ }
+ }
+
+ /**
+ * Obtains an instance of PropertiesPatternConverter.
+ *
+ * @param options options, may be null or first element contains name of property to format.
+ * @return instance of PropertiesPatternConverter.
+ */
+ public static Log4j1MdcPatternConverter newInstance(final String[] options) {
+ return new Log4j1MdcPatternConverter(options);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void format(final LogEvent event, final StringBuilder toAppendTo) {
+ if (key == null) {
+ // if there is no additional options, we output every single Key/Value pair for the MDC
+ toAppendTo.append('{');
+ event.getContextData().forEach(APPEND_EACH, toAppendTo);
+ toAppendTo.append('}');
+ } else {
+ // otherwise they just want a single key output
+ final Object val = event.getContextData().getValue(key);
+ if (val != null) {
+ toAppendTo.append(val);
+ }
+ }
+ }
+
+ private static TriConsumer<String, Object, StringBuilder> APPEND_EACH = new TriConsumer<String, Object, StringBuilder>() {
+ @Override
+ public void accept(final String key, final Object value, final StringBuilder toAppendTo) {
+ toAppendTo.append('{').append(key).append(',').append(value).append('}');
+ }
+ };
+}
diff --git a/log4j-1.2-api/src/src/main/java/org/apache/log4j/pattern/Log4j1NdcPatternConverter.java b/log4j-1.2-api/src/src/main/java/org/apache/log4j/pattern/Log4j1NdcPatternConverter.java
new file mode 100644
index 0000000..405db00
--- /dev/null
+++ b/log4j-1.2-api/src/src/main/java/org/apache/log4j/pattern/Log4j1NdcPatternConverter.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.pattern;
+
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.core.pattern.ConverterKeys;
+import org.apache.logging.log4j.core.pattern.LogEventPatternConverter;
+import org.apache.logging.log4j.core.pattern.PatternConverter;
+import org.apache.logging.log4j.util.Strings;
+
+import java.util.List;
+
+
+/**
+ * Returns the event's NDC in a StringBuilder.
+ */
+@Plugin(name = "Log4j1NdcPatternConverter", category = PatternConverter.CATEGORY)
+@ConverterKeys({ "ndc" })
+public final class Log4j1NdcPatternConverter extends LogEventPatternConverter {
+ /**
+ * Singleton.
+ */
+ private static final Log4j1NdcPatternConverter INSTANCE =
+ new Log4j1NdcPatternConverter();
+
+ /**
+ * Private constructor.
+ */
+ private Log4j1NdcPatternConverter() {
+ super("Log4j1NDC", "ndc");
+ }
+
+ /**
+ * Obtains an instance of NdcPatternConverter.
+ *
+ * @param options options, may be null.
+ * @return instance of NdcPatternConverter.
+ */
+ public static Log4j1NdcPatternConverter newInstance(final String[] options) {
+ return INSTANCE;
+ }
+
+ @Override
+ public void format(final LogEvent event, final StringBuilder toAppendTo) {
+ final List<String> ndc = event.getContextStack().asList();
+ toAppendTo.append(Strings.join(ndc, ' '));
+ }
+}
diff --git a/log4j-1.2-api/src/src/main/java/org/apache/log4j/spi/AppenderAttachable.java b/log4j-1.2-api/src/src/main/java/org/apache/log4j/spi/AppenderAttachable.java
new file mode 100644
index 0000000..fd464a2
--- /dev/null
+++ b/log4j-1.2-api/src/src/main/java/org/apache/log4j/spi/AppenderAttachable.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.spi;
+
+import org.apache.log4j.Appender;
+
+import java.util.Enumeration;
+
+/**
+ * Interface for attaching appenders to objects.
+ */
+public interface AppenderAttachable {
+
+ /**
+ * Add an appender.
+ */
+ void addAppender(Appender newAppender);
+
+ /**
+ * Get all previously added appenders as an Enumeration.
+ */
+ Enumeration getAllAppenders();
+
+ /**
+ * Get an appender by name.
+ */
+ Appender getAppender(String name);
+
+
+ /**
+ * Returns <code>true</code> if the specified appender is in list of
+ * attached attached, <code>false</code> otherwise.
+ *
+ * @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.
+ */
+ void removeAppender(Appender appender);
+
+
+ /**
+ * Remove the appender with the name passed as parameter from the
+ * list of appenders.
+ */
+ void removeAppender(String name);
+}
diff --git a/log4j-1.2-api/src/src/main/java/org/apache/log4j/spi/Configurator.java b/log4j-1.2-api/src/src/main/java/org/apache/log4j/spi/Configurator.java
new file mode 100644
index 0000000..b418db8
--- /dev/null
+++ b/log4j-1.2-api/src/src/main/java/org/apache/log4j/spi/Configurator.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.spi;
+
+import java.io.InputStream;
+import java.net.URL;
+
+import org.apache.logging.log4j.core.LoggerContext;
+
+/**
+ * Log4j 1.x Configurator interface.
+ */
+public interface Configurator {
+
+ public static final String INHERITED = "inherited";
+
+ 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 <code>hierarchy</code>
+ parameter.
+
+ @param inputStream The InputStream to parse
+
+ @since 1.2.17
+ */
+ void doConfigure(InputStream inputStream, final LoggerContext loggerContext);
+
+ /**
+ Interpret a resource pointed by a URL and set up log4j accordingly.
+
+ The configuration is done relative to the <code>hierarchy</code>
+ parameter.
+
+ @param url The URL to parse
+ */
+ void doConfigure(URL url, final LoggerContext loggerContext);
+}
diff --git a/log4j-1.2-api/src/src/main/java/org/apache/log4j/spi/ErrorCode.java b/log4j-1.2-api/src/src/main/java/org/apache/log4j/spi/ErrorCode.java
new file mode 100644
index 0000000..7fbbf95
--- /dev/null
+++ b/log4j-1.2-api/src/src/main/java/org/apache/log4j/spi/ErrorCode.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.spi;
+
+
+/**
+ This interface defines commonly encoutered 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/src/main/java/org/apache/log4j/spi/ErrorHandler.java b/log4j-1.2-api/src/src/main/java/org/apache/log4j/spi/ErrorHandler.java
new file mode 100644
index 0000000..2e64103
--- /dev/null
+++ b/log4j-1.2-api/src/src/main/java/org/apache/log4j/spi/ErrorHandler.java
@@ -0,0 +1,98 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.log4j.spi;
+
+import org.apache.log4j.Appender;
+import org.apache.log4j.Logger;
+
+
+/**
+ * Appenders may delegate their error handling to
+ * <code>ErrorHandlers</code>.
+ * <p>
+ * Error handling is a particularly tedious to get right because by
+ * definition errors are hard to predict and to reproduce.
+ * </p>
+ * <p>
+ * 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.
+ * </p>
+ */
+public interface ErrorHandler {
+
+ /**
+ * Add a reference to a logger to which the failing appender might
+ * be attached to. The failing appender will be searched and
+ * replaced only in the loggers you add through this method.
+ *
+ * @param logger One of the loggers that will be searched for the failing
+ * appender in view of replacement.
+ * @since 1.2
+ */
+ void setLogger(Logger logger);
+
+
+ /**
+ * Equivalent to the {@link #error(String, Exception, int,
+ * LoggingEvent)} with the the event parameter set to
+ * <code>null</code>.
+ *
+ * @param message The message associated with the error.
+ * @param e The Exception that was thrown when the error occurred.
+ * @param errorCode The error code associated with the error.
+ */
+ void error(String message, Exception e, int errorCode);
+
+ /**
+ * This method is normally used to just print the error message
+ * passed as a parameter.
+ *
+ * @param message The message associated with the error.
+ */
+ void error(String message);
+
+ /**
+ * This method is invoked to handle the error.
+ *
+ * @param message The message associated with the error.
+ * @param e The Exception that was thrown when the error occurred.
+ * @param errorCode The error code associated with the error.
+ * @param event The logging event that the failing appender is asked
+ * to log.
+ * @since 1.2
+ */
+ void error(String message, Exception e, int errorCode, LoggingEvent event);
+
+ /**
+ * Set the appender for which errors are handled. This method is
+ * usually called when the error handler is configured.
+ *
+ * @param appender The appender
+ * @since 1.2
+ */
+ void setAppender(Appender appender);
+
+ /**
+ * Set the appender to fallback upon in case of failure.
+ *
+ * @param appender The backup appender
+ * @since 1.2
+ */
+ void setBackupAppender(Appender appender);
+}
+
diff --git a/log4j-1.2-api/src/src/main/java/org/apache/log4j/spi/Filter.java b/log4j-1.2-api/src/src/main/java/org/apache/log4j/spi/Filter.java
new file mode 100644
index 0000000..997398b
--- /dev/null
+++ b/log4j-1.2-api/src/src/main/java/org/apache/log4j/spi/Filter.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.log4j.spi;
+
+import org.apache.log4j.bridge.FilterAdapter;
+
+/**
+ * @since 0.9.0
+ */
+public abstract class Filter {
+ private final FilterAdapter adapter;
+
+ public Filter() {
+ FilterAdapter filterAdapter = null;
+ try {
+ Class.forName("org.apache.logging.log4j.core.Filter");
+ filterAdapter = new FilterAdapter(this);
+ } catch(ClassNotFoundException ex) {
+ // Ignore the exception. Log4j Core is not present.
+ }
+ this.adapter = filterAdapter;
+ }
+
+ /**
+ * The log event must be dropped immediately without consulting
+ * with the remaining filters, if any, in the chain.
+ */
+ public static final int DENY = -1;
+
+ /**
+ * This filter is neutral with respect to the log event. The
+ * remaining filters, if any, should be consulted for a final decision.
+ */
+ public static final int NEUTRAL = 0;
+
+ /**
+ * The log event must be logged immediately without consulting with
+ * the remaining filters, if any, in the chain.
+ */
+ public static final int ACCEPT = 1;
+
+ /**
+ * Points to the next filter in the filter chain.
+ *
+ * @deprecated As of 1.2.12, use {@link #getNext} and {@link #setNext} instead
+ */
+ @Deprecated
+ public Filter next;
+
+ /**
+ * Usually filters options become active when set. We provide a
+ * default do-nothing implementation for convenience.
+ */
+ public void activateOptions() {
+ }
+
+
+ /**
+ * <p>If the decision is <code>DENY</code>, then the event will be
+ * dropped. If the decision is <code>NEUTRAL</code>, then the next
+ * filter, if any, will be invoked. If the decision is ACCEPT then
+ * the event will be logged without consulting with other filters in
+ * the chain.
+ *
+ * @param event The LoggingEvent to decide upon.
+ * @return decision The decision of the filter.
+ */
+ public abstract int decide(LoggingEvent event);
+
+ /**
+ * Set the next filter pointer.
+ * @param next The next Filter.
+ */
+ public void setNext(final Filter next) {
+ this.next = next;
+ }
+
+ /**
+ * Return the pointer to the next filter.
+ * @return The next Filter.
+ */
+ public Filter getNext() {
+ return next;
+ }
+
+}
diff --git a/log4j-1.2-api/src/src/main/java/org/apache/log4j/spi/HierarchyEventListener.java b/log4j-1.2-api/src/src/main/java/org/apache/log4j/spi/HierarchyEventListener.java
new file mode 100644
index 0000000..286ba54
--- /dev/null
+++ b/log4j-1.2-api/src/src/main/java/org/apache/log4j/spi/HierarchyEventListener.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.log4j.spi;
+
+import org.apache.log4j.Appender;
+import org.apache.log4j.Category;
+
+/**
+ Listen to events occurring within a Hierarchy.
+
+ @since 1.2
+
+ */
+public interface HierarchyEventListener {
+
+ void addAppenderEvent(Category cat, Appender appender);
+
+ void removeAppenderEvent(Category cat, Appender appender);
+}
diff --git a/log4j-1.2-api/src/src/main/java/org/apache/log4j/spi/LocationInfo.java b/log4j-1.2-api/src/src/main/java/org/apache/log4j/spi/LocationInfo.java
new file mode 100644
index 0000000..2102802
--- /dev/null
+++ b/log4j-1.2-api/src/src/main/java/org/apache/log4j/spi/LocationInfo.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.spi;
+
+/**
+ The internal representation of caller location information.
+
+ @since 0.8.3
+ */
+public class LocationInfo implements java.io.Serializable {
+
+ private final StackTraceElement element;
+
+ public String fullInfo;
+
+ public LocationInfo(StackTraceElement element) {
+ this.element = element;
+ }
+
+ /**
+ When location information is not available the constant
+ <code>NA</code> is returned. Current value of this string
+ constant is <b>?</b>. */
+ public final static String NA = "?";
+
+ static final long serialVersionUID = -1325822038990805636L;
+
+
+ /**
+ Return the fully qualified class name of the caller making the
+ logging request.
+ */
+ public
+ String getClassName() {
+ return element.getClassName();
+ }
+
+ /**
+ Return the file name of the caller.
+ */
+ public
+ String getFileName() {
+ return element.getFileName();
+ }
+
+ /**
+ Returns the line number of the caller.
+ */
+ public
+ String getLineNumber() {
+ return Integer.toString(element.getLineNumber());
+ }
+
+ /**
+ Returns the method name of the caller.
+ */
+ public
+ String getMethodName() {
+ return element.getMethodName();
+ }
+}
diff --git a/log4j-1.2-api/src/src/main/java/org/apache/log4j/spi/LoggerFactory.java b/log4j-1.2-api/src/src/main/java/org/apache/log4j/spi/LoggerFactory.java
new file mode 100644
index 0000000..e2f3708
--- /dev/null
+++ b/log4j-1.2-api/src/src/main/java/org/apache/log4j/spi/LoggerFactory.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.log4j.spi;
+
+import org.apache.log4j.Logger;
+
+/**
+ *
+ * Implement this interface to create new instances of Logger or a sub-class of Logger.
+ *
+ * <p>
+ * See <code>examples/subclass/MyLogger.java</code> for an example.
+ * </p>
+ */
+public interface LoggerFactory {
+
+ Logger makeNewLoggerInstance(String name);
+
+}
diff --git a/log4j-1.2-api/src/src/main/java/org/apache/log4j/spi/LoggerRepository.java b/log4j-1.2-api/src/src/main/java/org/apache/log4j/spi/LoggerRepository.java
new file mode 100644
index 0000000..812280f
--- /dev/null
+++ b/log4j-1.2-api/src/src/main/java/org/apache/log4j/spi/LoggerRepository.java
@@ -0,0 +1,109 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.log4j.spi;
+
+import java.util.Enumeration;
+
+import org.apache.log4j.Appender;
+import org.apache.log4j.Category;
+import org.apache.log4j.Level;
+import org.apache.log4j.Logger;
+
+/**
+ * A <code>LoggerRepository</code> is used to create and retrieve <code>Loggers</code>.
+ * <p>
+ * The relation between loggers in a repository depends on the repository but typically loggers are arranged in a named
+ * hierarchy.
+ * </p>
+ * <p>
+ * In addition to the creational methods, a <code>LoggerRepository</code> can be queried for existing loggers, can act
+ * as a point of registry for events related to loggers.
+ * </p>
+ *
+ * @since 1.2
+ */
+public interface LoggerRepository {
+
+ /**
+ * Add a {@link HierarchyEventListener} event to the repository.
+ *
+ * @param listener The listener
+ */
+ void addHierarchyEventListener(HierarchyEventListener listener);
+
+ /**
+ * Returns whether this repository is disabled for a given
+ * level. The answer depends on the repository threshold and the
+ * <code>level</code> parameter. See also {@link #setThreshold}
+ * method.
+ *
+ * @param level The level
+ * @return whether this repository is disabled.
+ */
+ boolean isDisabled(int level);
+
+ /**
+ * Set the repository-wide threshold. All logging requests below the
+ * threshold are immediately dropped. By default, the threshold is
+ * set to <code>Level.ALL</code> which has the lowest possible rank.
+ *
+ * @param level The level
+ */
+ void setThreshold(Level level);
+
+ /**
+ * Another form of {@link #setThreshold(Level)} accepting a string
+ * parameter instead of a <code>Level</code>.
+ *
+ * @param val The threshold value
+ */
+ void setThreshold(String val);
+
+ void emitNoAppenderWarning(Category cat);
+
+ /**
+ * Get the repository-wide threshold. See {@link #setThreshold(Level)} for an explanation.
+ *
+ * @return the level.
+ */
+ Level getThreshold();
+
+ Logger getLogger(String name);
+
+ Logger getLogger(String name, LoggerFactory factory);
+
+ Logger getRootLogger();
+
+ Logger exists(String name);
+
+ void shutdown();
+
+ @SuppressWarnings("rawtypes")
+ Enumeration getCurrentLoggers();
+
+ /**
+ * Deprecated. Please use {@link #getCurrentLoggers} instead.
+ *
+ * @return an enumeration of loggers.
+ */
+ @SuppressWarnings("rawtypes")
+ Enumeration getCurrentCategories();
+
+ void fireAddAppenderEvent(Category logger, Appender appender);
+
+ void resetConfiguration();
+}
diff --git a/log4j-1.2-api/src/src/main/java/org/apache/log4j/spi/LoggingEvent.java b/log4j-1.2-api/src/src/main/java/org/apache/log4j/spi/LoggingEvent.java
new file mode 100644
index 0000000..e9f57de
--- /dev/null
+++ b/log4j-1.2-api/src/src/main/java/org/apache/log4j/spi/LoggingEvent.java
@@ -0,0 +1,131 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.log4j.spi;
+
+import org.apache.log4j.Category;
+import org.apache.log4j.Level;
+import org.apache.log4j.bridge.LogEventAdapter;
+
+/**
+ * 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 {
+
+ /**
+ Set the location information for this logging event. The collected
+ information is cached for future use.
+ */
+ public LocationInfo getLocationInformation() {
+ return null;
+ }
+
+ /**
+ * Return the level of this event. Use this form instead of directly
+ * accessing the <code>level</code> field. */
+ public Level getLevel() {
+ return null;
+ }
+
+ /**
+ * Return the name of the logger. Use this form instead of directly
+ * accessing the <code>categoryName</code> field.
+ */
+ public String getLoggerName() {
+ return null;
+ }
+
+ /**
+ * Gets the logger of the event.
+ * Use should be restricted to cloning events.
+ * @since 1.2.15
+ */
+ public Category getLogger() {
+ return null;
+ }
+
+ /**
+ Return the message for this logging event.
+
+ <p>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.
+
+ @since 1.1 */
+ public
+ Object getMessage() {
+ return null;
+ }
+
+ public
+ String getNDC() {
+ return null;
+ }
+
+ public
+ Object getMDC(String key) {
+ return null;
+ }
+
+ /**
+ Obtain a copy of this thread's MDC prior to serialization or
+ asynchronous logging.
+ */
+ public
+ void getMDCCopy() {
+ }
+
+ public
+ String getRenderedMessage() {
+ return null;
+ }
+
+ /**
+ Returns the time when the application started, in milliseconds
+ elapsed since 01.01.1970. */
+ public static long getStartTime() {
+ return LogEventAdapter.getStartTime();
+ }
+
+ public
+ String getThreadName() {
+ return null;
+ }
+
+ /**
+ Returns the throwable information contained within this
+ event. May be <code>null</code> if there is no such information.
+
+ <p>Note that the {@link Throwable} object contained within a
+ {@link ThrowableInformation} does not survive serialization.
+
+ @since 1.1 */
+ public
+ ThrowableInformation getThrowableInformation() {
+ return null;
+ }
+
+ /**
+ Return this event's throwable's string[] representaion.
+ */
+ public
+ String[] getThrowableStrRep() {
+ return null;
+ }
+
+}
diff --git a/log4j-1.2-api/src/src/main/java/org/apache/log4j/spi/OptionHandler.java b/log4j-1.2-api/src/src/main/java/org/apache/log4j/spi/OptionHandler.java
new file mode 100644
index 0000000..1b855bc
--- /dev/null
+++ b/log4j-1.2-api/src/src/main/java/org/apache/log4j/spi/OptionHandler.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.spi;
+
+
+/**
+ * Log4j 1 Interface for dealing with configuration. Ignored in Log4j 2.
+ */
+public interface OptionHandler {
+
+ void activateOptions();
+}
diff --git a/log4j-1.2-api/src/src/main/java/org/apache/log4j/spi/RepositorySelector.java b/log4j-1.2-api/src/src/main/java/org/apache/log4j/spi/RepositorySelector.java
new file mode 100644
index 0000000..9566659
--- /dev/null
+++ b/log4j-1.2-api/src/src/main/java/org/apache/log4j/spi/RepositorySelector.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.spi;
+
+/**
+
+ The <code>LogManager</code> uses one (and only one)
+ <code>RepositorySelector</code> implementation to select the
+ {@link org.apache.log4j.spi.LoggerRepository} for a particular application context.
+
+ <p>It is the responsibility of the <code>RepositorySelector</code>
+ implementation to track the application context. Log4j makes no
+ assumptions about the application context or on its management.
+
+ <p>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)
+ * LoggerRepository is returned.
+ * @return a LoggerRepository.
+ */
+ LoggerRepository getLoggerRepository();
+}
diff --git a/log4j-1.2-api/src/src/main/java/org/apache/log4j/spi/ThrowableInformation.java b/log4j-1.2-api/src/src/main/java/org/apache/log4j/spi/ThrowableInformation.java
new file mode 100644
index 0000000..5a9ace5
--- /dev/null
+++ b/log4j-1.2-api/src/src/main/java/org/apache/log4j/spi/ThrowableInformation.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.log4j.spi;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.List;
+
+import org.apache.logging.log4j.core.util.Throwables;
+
+/**
+ * Class Description goes here.
+ */
+public class ThrowableInformation implements java.io.Serializable {
+
+ static final long serialVersionUID = -4748765566864322735L;
+
+ private transient Throwable throwable;
+ private Method toStringList;
+
+ @SuppressWarnings("unchecked")
+ public
+ ThrowableInformation(Throwable throwable) {
+ this.throwable = throwable;
+ Method method = null;
+ try {
+ 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.
+ }
+ this.toStringList = method;
+ }
+
+ public
+ Throwable getThrowable() {
+ return throwable;
+ }
+
+ public synchronized String[] getThrowableStrRep() {
+ if (toStringList != null && throwable != null) {
+ try {
+ @SuppressWarnings("unchecked")
+ List<String> elements = (List<String>) toStringList.invoke(null, throwable);
+ if (elements != null) {
+ return elements.toArray(new String[0]);
+ }
+ } catch (IllegalAccessException | InvocationTargetException ex) {
+ // Ignore the exception.
+ }
+ }
+ return null;
+ }
+}
+
diff --git a/log4j-1.2-api/src/src/main/java/org/apache/log4j/spi/package-info.java b/log4j-1.2-api/src/src/main/java/org/apache/log4j/spi/package-info.java
new file mode 100644
index 0000000..a7648dc
--- /dev/null
+++ b/log4j-1.2-api/src/src/main/java/org/apache/log4j/spi/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+/**
+ * Log4j 1.x compatibility layer.
+ */
+package org.apache.log4j.spi;
diff --git a/log4j-1.2-api/src/src/main/java/org/apache/log4j/xml/DOMConfigurator.java b/log4j-1.2-api/src/src/main/java/org/apache/log4j/xml/DOMConfigurator.java
new file mode 100644
index 0000000..04a4555
--- /dev/null
+++ b/log4j-1.2-api/src/src/main/java/org/apache/log4j/xml/DOMConfigurator.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.xml;
+
+import java.io.InputStream;
+import java.io.Reader;
+import java.net.URL;
+import java.util.Properties;
+
+import javax.xml.parsers.FactoryConfigurationError;
+
+import org.apache.log4j.config.PropertySetter;
+import org.apache.log4j.spi.LoggerRepository;
+import org.w3c.dom.Element;
+
+/**
+ *
+ */
+public class DOMConfigurator {
+
+ public void doConfigure(final String filename, final LoggerRepository repository) {
+ }
+
+ public void doConfigure(final URL url, final LoggerRepository repository) {
+ }
+
+ public void doConfigure(final InputStream inputStream, final LoggerRepository repository)
+ throws FactoryConfigurationError {
+ }
+
+ public void doConfigure(final Reader reader, final LoggerRepository repository)
+ throws FactoryConfigurationError {
+ }
+
+ public void doConfigure(final Element element, final LoggerRepository repository) {
+ }
+
+ public static void configure(final Element element) {
+ }
+
+ public static void configureAndWatch(final String configFilename) {
+ }
+
+ public static void configureAndWatch(final String configFilename, final long delay) {
+ }
+
+ public static void configure(final String filename) throws FactoryConfigurationError {
+ }
+
+ public static void configure(final URL url) throws FactoryConfigurationError {
+ }
+
+ public static String subst(final String value, final Properties props) {
+ return value;
+ }
+
+ public static void setParameter(final Element elem, final PropertySetter propSetter, final Properties props) {
+
+ }
+
+ public static Object parseElement(final Element element, final Properties props,
+ @SuppressWarnings("rawtypes") final Class expectedClass)
+ throws Exception {
+ return null;
+ }
+}
diff --git a/log4j-1.2-api/src/src/main/java/org/apache/log4j/xml/Log4jEntityResolver.java b/log4j-1.2-api/src/src/main/java/org/apache/log4j/xml/Log4jEntityResolver.java
new file mode 100644
index 0000000..edda022
--- /dev/null
+++ b/log4j-1.2-api/src/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 org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.status.StatusLogger;
+import org.xml.sax.EntityResolver;
+import org.xml.sax.InputSource;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+
+/**
+ * An {@link EntityResolver} specifically designed to return
+ * <code>log4j.dtd</code> 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";
+
+ public InputSource resolveEntity(String publicId, String systemId) {
+ if (systemId.endsWith("log4j.dtd") || PUBLIC_ID.equals(publicId)) {
+ Class clazz = getClass();
+ InputStream in = clazz.getResourceAsStream("/org/apache/log4j/xml/log4j.dtd");
+ if (in == null) {
+ LOGGER.warn("Could not find [log4j.dtd] using [{}] class loader, parsed without DTD.",
+ clazz.getClassLoader());
+ in = new ByteArrayInputStream(new byte[0]);
+ }
+ return new InputSource(in);
+ } else {
+ return null;
+ }
+ }
+}
diff --git a/log4j-1.2-api/src/src/main/java/org/apache/log4j/xml/UnrecognizedElementHandler.java b/log4j-1.2-api/src/src/main/java/org/apache/log4j/xml/UnrecognizedElementHandler.java
new file mode 100644
index 0000000..463d5d9
--- /dev/null
+++ b/log4j-1.2-api/src/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 org.w3c.dom.Element;
+import java.util.Properties;
+
+/**
+ * When implemented by an object configured by DOMConfigurator,
+ * the handle method will be called when an unrecognized child
+ * element is encountered. Unrecognized child elements of
+ * the log4j:configuration element will be dispatched to
+ * the logger repository if it supports this interface.
+ *
+ * @since 1.2.15
+ */
+public interface UnrecognizedElementHandler {
+ /**
+ * Called to inform a configured object when
+ * an unrecognized child element is encountered.
+ * @param element element, may not be null.
+ * @param props properties in force, may be null.
+ * @return true if configured object recognized the element
+ * @throws Exception throw an exception to prevent activation
+ * of the configured object.
+ */
+ boolean parseUnrecognizedElement(Element element, Properties props) throws Exception;
+}
\ No newline at end of file
diff --git a/log4j-1.2-api/src/src/main/java/org/apache/log4j/xml/XmlConfigurationFactory.java b/log4j-1.2-api/src/src/main/java/org/apache/log4j/xml/XmlConfigurationFactory.java
new file mode 100644
index 0000000..0dc66ee
--- /dev/null
+++ b/log4j-1.2-api/src/src/main/java/org/apache/log4j/xml/XmlConfigurationFactory.java
@@ -0,0 +1,926 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.Appender;
+import org.apache.log4j.Layout;
+import org.apache.log4j.Level;
+import org.apache.log4j.bridge.AppenderAdapter;
+import org.apache.log4j.bridge.AppenderWrapper;
+import org.apache.log4j.bridge.FilterAdapter;
+import org.apache.log4j.bridge.LayoutAdapter;
+import org.apache.log4j.bridge.LayoutWrapper;
+import org.apache.log4j.builders.BuilderManager;
+import org.apache.log4j.config.Log4j1Configuration;
+import org.apache.log4j.config.PropertySetter;
+import org.apache.log4j.helpers.OptionConverter;
+import org.apache.log4j.spi.AppenderAttachable;
+import org.apache.log4j.spi.Configurator;
+import org.apache.log4j.spi.ErrorHandler;
+import org.apache.log4j.spi.Filter;
+import org.apache.log4j.spi.LoggerRepository;
+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.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.ConfigurationFactory;
+import org.apache.logging.log4j.core.config.ConfigurationSource;
+import org.apache.logging.log4j.core.config.LoggerConfig;
+import org.apache.logging.log4j.core.config.Order;
+import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.core.config.status.StatusConfiguration;
+import org.apache.logging.log4j.core.layout.HtmlLayout;
+import org.apache.logging.log4j.core.layout.PatternLayout;
+import org.apache.logging.log4j.core.layout.XmlLayout;
+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;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.FactoryConfigurationError;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InterruptedIOException;
+import java.io.Reader;
+import java.lang.reflect.Method;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
+
+/**
+ * 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 implements Configurator {
+ 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";
+ private static final String APPENDER_REF_TAG = "appender-ref";
+ 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";
+ private 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 EMPTY_STR = "";
+ private static final Class[] ONE_STRING_PARAM = new Class[]{String.class};
+ private static final String dbfKey = "javax.xml.parsers.DocumentBuilderFactory";
+ private static final String THROWABLE_RENDERER_TAG = "throwableRenderer";
+ private static final String SYSTEM_OUT = "System.out";
+ private static final String SYSTEM_ERR = "System.err";
+ 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 static final String FILE_PARAM = "file";
+ public static final String APPEND_PARAM = "append";
+ public static final String BUFFERED_IO_PARAM = "bufferedio";
+ public static final String BUFFER_SIZE_PARAM = "buffersize";
+ public static final String MAX_SIZE_PARAM = "maxfileSize";
+ public static final String MAX_BACKUP_INDEX = "maxbackupindex";
+ public static final String RELATIVE = "RELATIVE";
+ 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";
+
+ private final BuilderManager manager;
+
+ // key: appenderName, value: appender
+ private Map<String, Appender> appenderBag;
+
+ private Properties props = null;
+
+ private final LoggerContext loggerContext;
+ private Log4j1Configuration configuration;
+
+ /**
+ * No argument constructor.
+ */
+ public XmlConfigurationFactory() {
+ appenderBag = new HashMap<>();
+ loggerContext = (LoggerContext) org.apache.logging.log4j.LogManager.getContext(false);
+ manager = new BuilderManager();
+ }
+
+
+ private XmlConfigurationFactory(ConfigurationSource source, int monitorIntervalSeconds) {
+ appenderBag = new HashMap<>();
+ loggerContext = (LoggerContext) org.apache.logging.log4j.LogManager.getContext(false);
+ configuration = new Log4j1Configuration(loggerContext, source, monitorIntervalSeconds);
+ manager = new BuilderManager();
+ }
+
+ @Override
+ protected String[] getSupportedTypes() {
+ return new String[] {".xml"};
+ }
+
+ @Override
+ public Configuration getConfiguration(LoggerContext loggerContext, ConfigurationSource source) {
+ configuration = new Log4j1Configuration(loggerContext, source, 0);
+ doConfigure();
+ return configuration;
+ }
+
+ public Configuration getConfiguration() {
+ return configuration;
+ }
+
+ @Override
+ protected String getTestPrefix() {
+ return TEST_PREFIX;
+ }
+
+ @Override
+ protected String getDefaultPrefix() {
+ return DEFAULT_PREFIX;
+ }
+
+ /**
+ * 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.
+ * @since 1.2.15
+ */
+ private static 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 static 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);
+ }
+ }
+
+ /**
+ * Like {@link #configureAndWatch(String, long)} except that the
+ * default delay is used.
+ *
+ * @param configFilename A log4j configuration file in XML format.
+ */
+ public static void configureAndWatch(final String configFilename) {
+ configureAndWatch(configFilename, DEFAULT_DELAY);
+ }
+
+ /**
+ * Read the configuration file <code>configFilename</code> if it
+ * exists. Moreover, a thread will be created that will periodically
+ * check if <code>configFilename</code> has been created or
+ * modified. The period is determined by the <code>delay</code>
+ * argument. If a change or file creation is detected, then
+ * <code>configFilename</code> is read to configure log4j.
+ *
+ * @param configFilename A log4j configuration file in XML format.
+ * @param delay The delay in milliseconds to wait between each check.
+ */
+ public static void configureAndWatch(final String configFilename, final long delay) {
+ try {
+ File file = new File(configFilename);
+ InputStream is = new FileInputStream(file);
+ ConfigurationSource source = new ConfigurationSource(is, file);
+ int seconds = (int) TimeUnit.MILLISECONDS.toSeconds(delay);
+ XmlConfigurationFactory factory = new XmlConfigurationFactory(source, seconds);
+ factory.doConfigure();
+ org.apache.logging.log4j.core.config.Configurator.reconfigure(factory.getConfiguration());
+
+ } catch (IOException ioe) {
+ LOGGER.error("Unable to process configuration file {} due to {}", configFilename, ioe.getMessage());
+ }
+ }
+
+ /**
+ * A static version of doConfigure(String).
+ */
+ public static void configure(final String filename) throws FactoryConfigurationError {
+ configureAndWatch(filename, 0);
+ }
+
+ /**
+ * A static version of doConfigure(URL).
+ */
+ public static void configure(final URL url) throws FactoryConfigurationError {
+ try {
+ InputStream is = url.openStream();
+ ConfigurationSource source = new ConfigurationSource(is, url);
+ XmlConfigurationFactory factory = new XmlConfigurationFactory(source, 0);
+ factory.doConfigure();
+ org.apache.logging.log4j.core.config.Configurator.reconfigure(factory.getConfiguration());
+ } catch (IOException ioe) {
+ LOGGER.error("Unable to process configuration {} due to {}", url.toString(), ioe.getMessage());
+ }
+ }
+
+ /**
+ * 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 static 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 static void setParameter(final Element elem, final PropertySetter propSetter, final Properties props) {
+ String name = subst(elem.getAttribute("name"), props);
+ String value = (elem.getAttribute("value"));
+ value = subst(OptionConverter.convertSpecialChars(value), props);
+ propSetter.setProperty(name, value);
+ }
+
+ /**
+ * Creates an object and processes any nested param elements
+ * but does not call activateOptions. If the class also supports
+ * UnrecognizedElementParser, the parseUnrecognizedElement method
+ * will be call for any child elements other than param.
+ *
+ * @param element element, may not be null.
+ * @param props properties
+ * @param expectedClass interface or class expected to be implemented
+ * by created class
+ * @return created class or null.
+ * @throws Exception thrown if the contain object should be abandoned.
+ * @since 1.2.15
+ */
+ public static Object parseElement(final Element element, final Properties props,
+ @SuppressWarnings("rawtypes") final Class expectedClass) throws Exception {
+ String clazz = subst(element.getAttribute("class"), props);
+ Object instance = OptionConverter.instantiateByClassName(clazz,
+ expectedClass, null);
+
+ if (instance != null) {
+ PropertySetter propSetter = new PropertySetter(instance);
+ NodeList children = element.getChildNodes();
+ final int length = children.getLength();
+
+ for (int loop = 0; loop < length; loop++) {
+ Node currentNode = children.item(loop);
+ if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
+ Element currentElement = (Element) currentNode;
+ String tagName = currentElement.getTagName();
+ if (tagName.equals("param")) {
+ setParameter(currentElement, propSetter, props);
+ } else {
+ parseUnrecognizedElement(instance, currentElement, props);
+ }
+ }
+ }
+ return instance;
+ }
+ return null;
+ }
+
+ /**
+ * Used internally to parse appenders by IDREF name.
+ */
+ private Appender findAppenderByName(Document doc, String appenderName) {
+ Appender appender = appenderBag.get(appenderName);
+
+ if (appender != null) {
+ return appender;
+ } else {
+ // Doesn't work on DOM Level 1 :
+ // Element element = doc.getElementById(appenderName);
+
+ // Endre's hack:
+ Element element = null;
+ NodeList list = doc.getElementsByTagName("appender");
+ for (int t = 0; t < list.getLength(); t++) {
+ Node node = list.item(t);
+ NamedNodeMap map = node.getAttributes();
+ Node attrNode = map.getNamedItem("name");
+ if (appenderName.equals(attrNode.getNodeValue())) {
+ element = (Element) node;
+ break;
+ }
+ }
+ // Hack finished.
+
+ if (element == null) {
+
+ LOGGER.error("No appender named [{}] could be found.", appenderName);
+ return null;
+ } else {
+ appender = parseAppender(element);
+ if (appender != null) {
+ appenderBag.put(appenderName, appender);
+ }
+ return appender;
+ }
+ }
+ }
+
+ /**
+ * Used internally to parse appenders by IDREF element.
+ */
+ private Appender findAppenderByReference(Element appenderRef) {
+ String appenderName = subst(appenderRef.getAttribute(REF_ATTR));
+ Document doc = appenderRef.getOwnerDocument();
+ return findAppenderByName(doc, appenderName);
+ }
+
+ /**
+ * Used internally to parse an appender element.
+ */
+ private Appender parseAppender(Element appenderElement) {
+ String className = subst(appenderElement.getAttribute(CLASS_ATTR));
+ LOGGER.debug("Class name: [" + className + ']');
+ Appender appender = manager.parseAppender(className, appenderElement, this);
+ if (appender == null) {
+ appender = buildAppender(className, appenderElement);
+ }
+ return appender;
+ }
+
+ private Appender buildAppender(String className, Element appenderElement) {
+ try {
+ Appender appender = LoaderUtil.newInstanceOf(className);
+ PropertySetter propSetter = new PropertySetter(appender);
+
+ appender.setName(subst(appenderElement.getAttribute(NAME_ATTR)));
+ 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:
+ Filter filter = parseFilters(currentElement);
+ if (filter != null) {
+ LOGGER.debug("Adding filter of type [{}] to appender named [{}]",
+ filter.getClass(), appender.getName());
+ appender.addFilter(filter);
+ }
+ break;
+ case ERROR_HANDLER_TAG:
+ parseErrorHandler(currentElement, appender);
+ break;
+ case APPENDER_REF_TAG:
+ String refName = subst(currentElement.getAttribute(REF_ATTR));
+ if (appender instanceof AppenderAttachable) {
+ AppenderAttachable aa = (AppenderAttachable) appender;
+ Appender child = findAppenderByReference(currentElement);
+ LOGGER.debug("Attaching appender named [{}] to appender named [{}].", refName,
+ appender.getName());
+ aa.addAppender(child);
+ } else {
+ LOGGER.error("Requesting attachment of appender named [{}] to appender named [{}]"
+ + "which does not implement org.apache.log4j.spi.AppenderAttachable.",
+ refName, appender.getName());
+ }
+ break;
+ default:
+ try {
+ parseUnrecognizedElement(appender, currentElement, props);
+ } catch (Exception ex) {
+ throw new ConsumerException(ex);
+ }
+ }
+ });
+ propSetter.activate();
+ return appender;
+ } catch (ConsumerException ex) {
+ Throwable t = ex.getCause();
+ if (t instanceof InterruptedException || t instanceof InterruptedIOException) {
+ Thread.currentThread().interrupt();
+ }
+ LOGGER.error("Could not create an Appender. Reported error follows.", t);
+ } catch (Exception oops) {
+ if (oops instanceof InterruptedException || oops instanceof InterruptedIOException) {
+ Thread.currentThread().interrupt();
+ }
+ LOGGER.error("Could not create an Appender. Reported error follows.", oops);
+ }
+ return null;
+ }
+
+ /**
+ * Used internally to parse an {@link ErrorHandler} element.
+ */
+ private void parseErrorHandler(Element element, Appender appender) {
+ ErrorHandler eh = (ErrorHandler) OptionConverter.instantiateByClassName(
+ subst(element.getAttribute(CLASS_ATTR)),
+ ErrorHandler.class,
+ null);
+
+ if (eh != null) {
+ eh.setAppender(appender);
+
+ PropertySetter propSetter = new PropertySetter(eh);
+ forEachElement(element.getChildNodes(), (currentElement) -> {
+ String tagName = currentElement.getTagName();
+ if (tagName.equals(PARAM_TAG)) {
+ setParameter(currentElement, propSetter);
+ }
+ });
+ propSetter.activate();
+ appender.setErrorHandler(eh);
+ }
+ }
+
+ /**
+ * Used internally to parse a filter element.
+ */
+ public Filter parseFilters(Element filterElement) {
+ String className = subst(filterElement.getAttribute(CLASS_ATTR));
+ LOGGER.debug("Class name: [" + className + ']');
+ Filter filter = manager.parseFilter(className, filterElement, this);
+ if (filter == null) {
+ PropertySetter propSetter = new PropertySetter(filter);
+ forEachElement(filterElement.getChildNodes(), (currentElement) -> {
+ String tagName = currentElement.getTagName();
+ if (tagName.equals(PARAM_TAG)) {
+ setParameter(currentElement, propSetter);
+ } else {
+ quietParseUnrecognizedElement(filter, currentElement, props);
+ }
+ });
+ propSetter.activate();
+ }
+ return filter;
+ }
+
+ /**
+ * Used internally to parse an category element.
+ */
+ private void parseCategory(Element loggerElement) {
+ // Create a new org.apache.log4j.Category object from the <category> element.
+ String catName = subst(loggerElement.getAttribute(NAME_ATTR));
+ boolean additivity = OptionConverter.toBoolean(subst(loggerElement.getAttribute(ADDITIVITY_ATTR)), true);
+ LoggerConfig loggerConfig = configuration.getLogger(catName);
+ if (loggerConfig == null) {
+ loggerConfig = new LoggerConfig(catName, org.apache.logging.log4j.Level.ERROR, additivity);
+ configuration.addLogger(catName, loggerConfig);
+ } else {
+ loggerConfig.setAdditive(additivity);
+ }
+ parseChildrenOfLoggerElement(loggerElement, loggerConfig, false);
+ }
+
+ /**
+ * Used internally to parse the roor category element.
+ */
+ private void parseRoot(Element rootElement) {
+ LoggerConfig root = configuration.getRootLogger();
+ parseChildrenOfLoggerElement(rootElement, root, true);
+ }
+
+ /**
+ * Used internally to parse the children of a LoggerConfig element.
+ */
+ private void parseChildrenOfLoggerElement(Element catElement, LoggerConfig loggerConfig, boolean isRoot) {
+
+ final PropertySetter propSetter = new PropertySetter(loggerConfig);
+ loggerConfig.getAppenderRefs().clear();
+ forEachElement(catElement.getChildNodes(), (currentElement) -> {
+ switch (currentElement.getTagName()) {
+ case APPENDER_REF_TAG: {
+ Appender appender = findAppenderByReference(currentElement);
+ String refName = subst(currentElement.getAttribute(REF_ATTR));
+ if (appender != null) {
+ LOGGER.debug("Adding appender named [{}] to loggerConfig [{}].", refName,
+ loggerConfig.getName());
+ loggerConfig.addAppender(configuration.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.
+ */
+ public Layout parseLayout(Element layoutElement) {
+ String className = subst(layoutElement.getAttribute(CLASS_ATTR));
+ LOGGER.debug("Parsing layout of class: \"{}\"", className);
+ Layout layout = manager.parseLayout(className, layoutElement, this);
+ if (layout == null) {
+ layout = buildLayout(className, layoutElement);
+ }
+ return layout;
+ }
+
+ private Layout buildLayout(String className, Element layout_element) {
+ try {
+ Layout layout = LoaderUtil.newInstanceOf(className);
+ PropertySetter propSetter = new PropertySetter(layout);
+ forEachElement(layout_element.getChildNodes(), (currentElement) -> {
+ String tagName = currentElement.getTagName();
+ if (tagName.equals(PARAM_TAG)) {
+ setParameter(currentElement, propSetter);
+ } else {
+ try {
+ parseUnrecognizedElement(layout, currentElement, props);
+ } catch (Exception ex) {
+ throw new ConsumerException(ex);
+ }
+ }
+ });
+
+ propSetter.activate();
+ return layout;
+ } catch (ConsumerException ce) {
+ Throwable cause = ce.getCause();
+ if (cause instanceof InterruptedException || cause instanceof InterruptedIOException) {
+ Thread.currentThread().interrupt();
+ }
+ LOGGER.error("Could not create the Layout. Reported error follows.", cause);
+ } catch (Exception oops) {
+ if (oops instanceof InterruptedException || oops instanceof InterruptedIOException) {
+ Thread.currentThread().interrupt();
+ }
+ LOGGER.error("Could not create the Layout. Reported error follows.", oops);
+ }
+ return null;
+ }
+
+ /**
+ * Used internally to parse a level element.
+ */
+ private void parseLevel(Element element, LoggerConfig logger, boolean isRoot) {
+ String catName = logger.getName();
+ if (isRoot) {
+ catName = "root";
+ }
+
+ String priStr = subst(element.getAttribute(VALUE_ATTR));
+ LOGGER.debug("Level value for {} is [{}].", catName, priStr);
+
+ if (INHERITED.equalsIgnoreCase(priStr) || NULL.equalsIgnoreCase(priStr)) {
+ if (isRoot) {
+ LOGGER.error("Root level cannot be inherited. Ignoring directive.");
+ } else {
+ logger.setLevel(null);
+ }
+ } else {
+ String className = subst(element.getAttribute(CLASS_ATTR));
+ if (EMPTY_STR.equals(className)) {
+ logger.setLevel(convertLevel(OptionConverter.toLevel(priStr, Level.DEBUG)));
+ } else {
+ LOGGER.debug("Desired Level sub-class: [{}]", className);
+ try {
+ Class<?> clazz = LoaderUtil.loadClass(className);
+ Method toLevelMethod = clazz.getMethod("toLevel", ONE_STRING_PARAM);
+ Level pri = (Level) toLevelMethod.invoke(null, new Object[]{priStr});
+ logger.setLevel(convertLevel(pri));
+ } catch (Exception oops) {
+ if (oops instanceof InterruptedException || oops instanceof InterruptedIOException) {
+ Thread.currentThread().interrupt();
+ }
+ LOGGER.error("Could not create level [" + priStr +
+ "]. Reported error follows.", oops);
+ return;
+ }
+ }
+ }
+ LOGGER.debug("{} level set to {}", catName, logger.getLevel());
+ }
+
+ private void setParameter(Element elem, PropertySetter propSetter) {
+ String name = subst(elem.getAttribute(NAME_ATTR));
+ String value = (elem.getAttribute(VALUE_ATTR));
+ value = subst(OptionConverter.convertSpecialChars(value));
+ propSetter.setProperty(name, value);
+ }
+
+ /**
+ * Configure log4j by reading in a log4j.dtd compliant XML
+ * configuration file.
+ */
+ private void doConfigure() throws FactoryConfigurationError {
+ ConfigurationSource source = configuration.getConfigurationSource();
+ ParseAction action = new ParseAction() {
+ public Document parse(final DocumentBuilder parser) throws SAXException, IOException {
+ InputSource inputSource = new InputSource(source.getInputStream());
+ inputSource.setSystemId("dummy://log4j.dtd");
+ return parser.parse(inputSource);
+ }
+
+ public String toString() {
+ return configuration.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 succeded.");
+ LOGGER.debug("DocumentBuilderFactory is: " + dbf.getClass().getName());
+ } catch (FactoryConfigurationError fce) {
+ Exception e = fce.getException();
+ LOGGER.debug("Could not instantiate a DocumentBuilderFactory.", e);
+ throw fce;
+ }
+
+ try {
+ dbf.setValidating(true);
+
+ DocumentBuilder docBuilder = dbf.newDocumentBuilder();
+
+ docBuilder.setErrorHandler(new SAXErrorHandler());
+ docBuilder.setEntityResolver(new Log4jEntityResolver());
+
+ Document doc = action.parse(docBuilder);
+ parse(doc.getDocumentElement());
+ } catch (Exception e) {
+ if (e instanceof InterruptedException || e instanceof InterruptedIOException) {
+ Thread.currentThread().interrupt();
+ }
+ // I know this is miserable...
+ LOGGER.error("Could not parse " + action.toString() + ".", e);
+ }
+ }
+
+ @Override
+ public void doConfigure(InputStream inputStream, LoggerContext loggerContext) {
+ try {
+ ConfigurationSource source = new ConfigurationSource(inputStream);
+ configuration = new Log4j1Configuration(loggerContext, source, 0);
+ doConfigure();
+ } catch (IOException ioe) {
+ LOGGER.error("Unable to process configuration due to {}", ioe.getMessage());
+ }
+ }
+
+ @Override
+ public void doConfigure(URL url, LoggerContext loggerContext) {
+ try {
+ ConfigurationSource source = new ConfigurationSource(url.openStream(), url);
+ configuration = new Log4j1Configuration(loggerContext, source, 0);
+ doConfigure();
+ } catch (IOException ioe) {
+ LOGGER.error("Unable to process configuration due to {}", ioe.getMessage());
+ }
+ }
+
+ /**
+ * Used internally to configure the log4j framework by parsing a DOM
+ * tree of XML elements based on <a
+ * href="doc-files/log4j.dtd">log4j.dtd</a>.
+ */
+ private void parse(Element element) {
+ String rootElementName = element.getTagName();
+
+ if (!rootElementName.equals(CONFIGURATION_TAG)) {
+ if (rootElementName.equals(OLD_CONFIGURATION_TAG)) {
+ LOGGER.warn("The <" + OLD_CONFIGURATION_TAG +
+ "> element has been deprecated.");
+ LOGGER.warn("Use the <" + CONFIGURATION_TAG + "> element instead.");
+ } else {
+ LOGGER.error("DOM element is - not a <" + CONFIGURATION_TAG + "> element.");
+ return;
+ }
+ }
+
+
+ String debugAttrib = subst(element.getAttribute(INTERNAL_DEBUG_ATTR));
+
+ LOGGER.debug("debug attribute= \"" + debugAttrib + "\".");
+ // if the log4j.dtd is not specified in the XML file, then the
+ // "debug" attribute is returned as the empty string.
+ String status = "error";
+ if (!debugAttrib.equals("") && !debugAttrib.equals("null")) {
+ status = OptionConverter.toBoolean(debugAttrib, true) ? "debug" : "error";
+
+ } else {
+ LOGGER.debug("Ignoring " + INTERNAL_DEBUG_ATTR + " attribute.");
+ }
+
+ String confDebug = subst(element.getAttribute(CONFIG_DEBUG_ATTR));
+ if (!confDebug.equals("") && !confDebug.equals("null")) {
+ LOGGER.warn("The \"" + CONFIG_DEBUG_ATTR + "\" attribute is deprecated.");
+ LOGGER.warn("Use the \"" + INTERNAL_DEBUG_ATTR + "\" attribute instead.");
+ status = OptionConverter.toBoolean(confDebug, true) ? "debug" : "error";
+ }
+
+ final StatusConfiguration statusConfig = new StatusConfiguration().withStatus(status);
+ statusConfig.initialize();
+
+ 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("Renderers are not supported by Log4j 2 and will be ignored.");
+ break;
+ case THROWABLE_RENDERER_TAG:
+ LOGGER.warn("Throwable Renderers are not supported by Log4j 2 and will be ignored.");
+ break;
+ case CATEGORY_FACTORY_TAG: case LOGGER_FACTORY_TAG:
+ LOGGER.warn("Log4j 1 Logger factories are not supported by Log4j 2 and will be ignored.");
+ break;
+ case APPENDER_TAG:
+ Appender appender = parseAppender(currentElement);
+ appenderBag.put(appender.getName(), appender);
+ if (appender instanceof AppenderWrapper) {
+ configuration.addAppender(((AppenderWrapper) appender).getAppender());
+ } else {
+ configuration.addAppender(new AppenderAdapter(appender).getAdapter());
+ }
+ break;
+ default:
+ quietParseUnrecognizedElement(null, currentElement, props);
+ }
+ });
+ }
+
+ private org.apache.logging.log4j.Level convertLevel(Level level) {
+ if (level == null) {
+ return org.apache.logging.log4j.Level.ERROR;
+ }
+ if (level.isGreaterOrEqual(Level.FATAL)) {
+ return org.apache.logging.log4j.Level.FATAL;
+ } else if (level.isGreaterOrEqual(Level.ERROR)) {
+ return org.apache.logging.log4j.Level.ERROR;
+ } else if (level.isGreaterOrEqual(Level.WARN)) {
+ return org.apache.logging.log4j.Level.WARN;
+ } else if (level.isGreaterOrEqual(Level.INFO)) {
+ return org.apache.logging.log4j.Level.INFO;
+ } else if (level.isGreaterOrEqual(Level.DEBUG)) {
+ return org.apache.logging.log4j.Level.DEBUG;
+ } else if (level.isGreaterOrEqual(Level.TRACE)) {
+ return org.apache.logging.log4j.Level.TRACE;
+ }
+ return org.apache.logging.log4j.Level.ALL;
+ }
+
+ private String subst(final String value) {
+ return configuration.getStrSubstitutor().replace(value);
+ }
+
+ public static void forEachElement(NodeList list, Consumer<Element> consumer) {
+ final int length = list.getLength();
+ for (int loop = 0; loop < length; loop++) {
+ Node currentNode = list.item(loop);
+
+ if (currentNode.getNodeType() == Node.ELEMENT_NODE) {
+ Element currentElement = (Element) currentNode;
+ consumer.accept(currentElement);
+ }
+ }
+ }
+
+ 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();
+
+ public void error(final SAXParseException ex) {
+ emitMessage("Continuable parsing error ", ex);
+ }
+
+ public void fatalError(final SAXParseException ex) {
+ emitMessage("Fatal parsing error ", ex);
+ }
+
+ public void warning(final SAXParseException ex) {
+ emitMessage("Parsing warning ", ex);
+ }
+
+ private static void emitMessage(final String msg, final SAXParseException ex) {
+ LOGGER.warn("{} {} and column {}", msg, ex.getLineNumber(), ex.getColumnNumber());
+ LOGGER.warn(ex.getMessage(), ex.getException());
+ }
+ }
+
+ private static class ConsumerException extends RuntimeException {
+
+ ConsumerException(Exception ex) {
+ super(ex);
+ }
+ }
+}
+
diff --git a/log4j-1.2-api/src/src/main/java/org/apache/log4j/xml/package-info.java b/log4j-1.2-api/src/src/main/java/org/apache/log4j/xml/package-info.java
new file mode 100644
index 0000000..e3ed0d1
--- /dev/null
+++ b/log4j-1.2-api/src/src/main/java/org/apache/log4j/xml/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+/**
+ * Log4j 1.x compatibility layer.
+ */
+package org.apache.log4j.xml;
diff --git a/log4j-1.2-api/src/src/main/resources/org/apache/log4j/xml/log4j.dtd b/log4j-1.2-api/src/src/main/resources/org/apache/log4j/xml/log4j.dtd
new file mode 100644
index 0000000..f8e433a
--- /dev/null
+++ b/log4j-1.2-api/src/src/main/resources/org/apache/log4j/xml/log4j.dtd
@@ -0,0 +1,237 @@
+<?xml version="1.0" encoding="UTF-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 "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!-- Authors: Chris Taylor, Ceki Gulcu. -->
+
+<!-- Version: 1.2 -->
+
+<!-- A configuration element consists of optional renderer
+elements,appender elements, categories and an optional root
+element. -->
+
+<!ELEMENT log4j:configuration (renderer*, throwableRenderer?,
+ appender*,plugin*, (category|logger)*,root?,
+ (categoryFactory|loggerFactory)?)>
+
+<!-- The "threshold" attribute takes a level value below which -->
+<!-- all logging statements are disabled. -->
+
+<!-- Setting the "debug" enable the printing of internal log4j logging -->
+<!-- statements. -->
+
+<!-- By default, debug attribute is "null", meaning that we not do touch -->
+<!-- internal log4j logging settings. The "null" value for the threshold -->
+<!-- attribute can be misleading. The threshold field of a repository -->
+<!-- cannot be set to null. The "null" value for the threshold attribute -->
+<!-- simply means don't touch the threshold field, the threshold field -->
+<!-- keeps its old value. -->
+
+<!ATTLIST log4j:configuration
+ xmlns:log4j CDATA #FIXED "http://jakarta.apache.org/log4j/"
+ threshold (all|trace|debug|info|warn|error|fatal|off|null) "null"
+ debug (true|false|null) "null"
+ reset (true|false) "false"
+>
+
+<!-- renderer elements allow the user to customize the conversion of -->
+<!-- message objects to String. -->
+
+<!ELEMENT renderer EMPTY>
+<!ATTLIST renderer
+ renderedClass CDATA #REQUIRED
+ renderingClass CDATA #REQUIRED
+>
+
+<!-- throwableRenderer allows the user to customize the conversion
+ of exceptions to a string representation. -->
+<!ELEMENT throwableRenderer (param*)>
+<!ATTLIST throwableRenderer
+ class CDATA #REQUIRED
+>
+
+
+<!-- Appenders must have a name and a class. -->
+<!-- Appenders may contain an error handler, a layout, optional parameters -->
+<!-- and filters. They may also reference (or include) other appenders. -->
+<!ELEMENT appender (errorHandler?, param*,
+ rollingPolicy?, triggeringPolicy?, connectionSource?,
+ layout?, filter*, appender-ref*)>
+<!ATTLIST appender
+ name CDATA #REQUIRED
+ class CDATA #REQUIRED
+>
+
+<!ELEMENT layout (param*)>
+<!ATTLIST layout
+ class CDATA #REQUIRED
+>
+
+<!ELEMENT filter (param*)>
+<!ATTLIST filter
+ class CDATA #REQUIRED
+>
+
+<!-- ErrorHandlers can be of any class. They can admit any number of -->
+<!-- parameters. -->
+
+<!ELEMENT errorHandler (param*, root-ref?, logger-ref*, appender-ref?)>
+<!ATTLIST errorHandler
+ class CDATA #REQUIRED
+>
+
+<!ELEMENT root-ref EMPTY>
+
+<!ELEMENT logger-ref EMPTY>
+<!ATTLIST logger-ref
+ ref CDATA #REQUIRED
+>
+
+<!ELEMENT param EMPTY>
+<!ATTLIST param
+ name CDATA #REQUIRED
+ value CDATA #REQUIRED
+>
+
+
+<!-- The priority class is org.apache.log4j.Level by default -->
+<!ELEMENT priority (param*)>
+<!ATTLIST priority
+ class CDATA #IMPLIED
+ value CDATA #REQUIRED
+>
+
+<!-- The level class is org.apache.log4j.Level by default -->
+<!ELEMENT level (param*)>
+<!ATTLIST level
+ class CDATA #IMPLIED
+ value CDATA #REQUIRED
+>
+
+
+<!-- If no level element is specified, then the configurator MUST not -->
+<!-- touch the level of the named category. -->
+<!ELEMENT category (param*,(priority|level)?,appender-ref*)>
+<!ATTLIST category
+ class CDATA #IMPLIED
+ name CDATA #REQUIRED
+ additivity (true|false) "true"
+>
+
+<!-- If no level element is specified, then the configurator MUST not -->
+<!-- touch the level of the named logger. -->
+<!ELEMENT logger (param*,level?,appender-ref*)>
+<!ATTLIST logger
+ class CDATA #IMPLIED
+ name CDATA #REQUIRED
+ additivity (true|false) "true"
+>
+
+
+<!ELEMENT categoryFactory (param*)>
+<!ATTLIST categoryFactory
+ class CDATA #REQUIRED>
+
+<!ELEMENT loggerFactory (param*)>
+<!ATTLIST loggerFactory
+ class CDATA #REQUIRED>
+
+<!ELEMENT appender-ref EMPTY>
+<!ATTLIST appender-ref
+ ref CDATA #REQUIRED
+>
+
+<!-- plugins must have a name and class and can have optional parameters -->
+<!ELEMENT plugin (param*, connectionSource?)>
+<!ATTLIST plugin
+ name CDATA #REQUIRED
+ class CDATA #REQUIRED
+>
+
+<!ELEMENT connectionSource (dataSource?, param*)>
+<!ATTLIST connectionSource
+ class CDATA #REQUIRED
+>
+
+<!ELEMENT dataSource (param*)>
+<!ATTLIST dataSource
+ class CDATA #REQUIRED
+>
+
+<!ELEMENT triggeringPolicy ((param|filter)*)>
+<!ATTLIST triggeringPolicy
+ name CDATA #IMPLIED
+ class CDATA #REQUIRED
+>
+
+<!ELEMENT rollingPolicy (param*)>
+<!ATTLIST rollingPolicy
+ name CDATA #IMPLIED
+ class CDATA #REQUIRED
+>
+
+
+<!-- If no priority element is specified, then the configurator MUST not -->
+<!-- touch the priority of root. -->
+<!-- The root category always exists and cannot be subclassed. -->
+<!ELEMENT root (param*, (priority|level)?, appender-ref*)>
+
+
+<!-- ==================================================================== -->
+<!-- A logging event -->
+<!-- ==================================================================== -->
+<!ELEMENT log4j:eventSet (log4j:event*)>
+<!ATTLIST log4j:eventSet
+ xmlns:log4j CDATA #FIXED "http://jakarta.apache.org/log4j/"
+ version (1.1|1.2) "1.2"
+ includesLocationInfo (true|false) "true"
+>
+
+
+
+<!ELEMENT log4j:event (log4j:message, log4j:NDC?, log4j:throwable?,
+ log4j:locationInfo?, log4j:properties?) >
+
+<!-- The timestamp format is application dependent. -->
+<!ATTLIST log4j:event
+ logger CDATA #REQUIRED
+ level CDATA #REQUIRED
+ thread CDATA #REQUIRED
+ timestamp CDATA #REQUIRED
+ time CDATA #IMPLIED
+>
+
+<!ELEMENT log4j:message (#PCDATA)>
+<!ELEMENT log4j:NDC (#PCDATA)>
+
+<!ELEMENT log4j:throwable (#PCDATA)>
+
+<!ELEMENT log4j:locationInfo EMPTY>
+<!ATTLIST log4j:locationInfo
+ class CDATA #REQUIRED
+ method CDATA #REQUIRED
+ file CDATA #REQUIRED
+ line CDATA #REQUIRED
+>
+
+<!ELEMENT log4j:properties (log4j:data*)>
+
+<!ELEMENT log4j:data EMPTY>
+<!ATTLIST log4j:data
+ name CDATA #REQUIRED
+ value CDATA #REQUIRED
+>
diff --git a/log4j-1.2-api/src/src/site/markdown/index.md b/log4j-1.2-api/src/src/site/markdown/index.md
new file mode 100644
index 0000000..696e0bb
--- /dev/null
+++ b/log4j-1.2-api/src/src/site/markdown/index.md
@@ -0,0 +1,48 @@
+<!-- vim: set syn=markdown : -->
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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 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. The following Log4j 1.x methods will behave differently when
+the Log4j 2 Core module is included then when it is not:
+
+| Method | Without log4j-core | With log4j-core |
+| ----------------------------- | ------------------ | ------------------------------------ |
+| Category.getParent() | Returns null | Returns parent logger |
+| Category.setLevel() | NoOp | Sets Logger Level |
+| Category.setPriority() | NoOp | Sets Logger Level |
+| Category.getAdditivity() | Returns false | Returns Logger's additivity setting |
+| Category.setAdditivity() | NoOp | Sets additivity of LoggerConfig |
+| Category.getResourceBundle() | NoOp | Returns the resource bundle associated with the Logger |
+| BasicConfigurator.configure() | NoOp | Reconfigures Log4j 2 |
+
+If log4j-core is not present location information will not be accurate in calls using the Log4j 1.2 API. The config
+package which attempts tp convert Log4j 1.x configurations to Log4j 2 is not supported without Log4j 2.
+
+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/src/site/site.xml b/log4j-1.2-api/src/src/site/site.xml
new file mode 100644
index 0000000..b27991c
--- /dev/null
+++ b/log4j-1.2-api/src/src/site/site.xml
@@ -0,0 +1,52 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+-->
+<project name="Log4j 1.x Adaptor"
+ xmlns="http://maven.apache.org/DECORATION/1.4.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/DECORATION/1.4.0 http://maven.apache.org/xsd/decoration-1.4.0.xsd">
+ <body>
+ <links>
+ <item name="Apache" href="http://www.apache.org/" />
+ <item name="Logging Services" href="http://logging.apache.org/"/>
+ <item name="Log4j" href="../index.html"/>
+ </links>
+
+ <!-- Component-specific reports -->
+ <menu ref="reports"/>
+
+ <!-- Overall Project Info -->
+ <menu name="Log4j Project Information" img="icon-info-sign">
+ <item name="Dependencies" href="../dependencies.html" />
+ <item name="Dependency Convergence" href="../dependency-convergence.html" />
+ <item name="Dependency Management" href="../dependency-management.html" />
+ <item name="Project Team" href="../team-list.html" />
+ <item name="Mailing Lists" href="../mail-lists.html" />
+ <item name="Issue Tracking" href="../issue-tracking.html" />
+ <item name="Project License" href="../license.html" />
+ <item name="Source Repository" href="../source-repository.html" />
+ <item name="Project Summary" href="../project-summary.html" />
+ </menu>
+
+ <menu name="Log4j Project Reports" img="icon-cog">
+ <item name="Changes Report" href="../changes-report.html" />
+ <item name="JIRA Report" href="../jira-report.html" />
+ <item name="Surefire Report" href="../surefire-report.html" />
+ <item name="RAT Report" href="../rat-report.html" />
+ </menu>
+ </body>
+</project>
diff --git a/log4j-1.2-api/src/src/test/java/org/apache/log4j/BasicConfigurationFactory.java b/log4j-1.2-api/src/src/test/java/org/apache/log4j/BasicConfigurationFactory.java
new file mode 100644
index 0000000..d231d82
--- /dev/null
+++ b/log4j-1.2-api/src/src/test/java/org/apache/log4j/BasicConfigurationFactory.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;
+
+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;
+import org.apache.logging.log4j.core.config.ConfigurationSource;
+import org.apache.logging.log4j.core.config.LoggerConfig;
+
+/**
+ *
+ */
+public class BasicConfigurationFactory extends ConfigurationFactory {
+
+ @Override
+ public String[] getSupportedTypes() {
+ return new String[] { "*" };
+ }
+
+ @Override
+ public Configuration getConfiguration(final LoggerContext loggerContext, final ConfigurationSource source) {
+ return new BasicConfiguration(loggerContext);
+ }
+
+ @Override
+ public Configuration getConfiguration(final LoggerContext loggerContext, final String name, final URI configLocation) {
+ return new BasicConfiguration(loggerContext);
+ }
+
+ public class BasicConfiguration extends AbstractConfiguration {
+
+ private static final long serialVersionUID = -2716784321395089563L;
+
+ private static final String DEFAULT_LEVEL = "org.apache.logging.log4j.level";
+
+ public BasicConfiguration(final LoggerContext loggerContext) {
+ super(loggerContext, ConfigurationSource.NULL_SOURCE);
+
+ 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;
+ root.setLevel(level);
+ }
+
+ @Override
+ protected void doConfigure() {
+ }
+ }
+}
diff --git a/log4j-1.2-api/src/src/test/java/org/apache/log4j/CallerInformationTest.java b/log4j-1.2-api/src/src/test/java/org/apache/log4j/CallerInformationTest.java
new file mode 100644
index 0000000..1aa6c31
--- /dev/null
+++ b/log4j-1.2-api/src/src/test/java/org/apache/log4j/CallerInformationTest.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 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<String> 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<String> messages = app.getMessages();
+ assertEquals("Incorrect number of messages.", 5, messages.size());
+ for (final String message : messages) {
+ assertEquals("Incorrect caller method name.", "testMethodLogger", message);
+ }
+ }
+}
diff --git a/log4j-1.2-api/src/src/test/java/org/apache/log4j/CategoryTest.java b/log4j-1.2-api/src/src/test/java/org/apache/log4j/CategoryTest.java
new file mode 100644
index 0000000..6a8af76
--- /dev/null
+++ b/log4j-1.2-api/src/src/test/java/org/apache/log4j/CategoryTest.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;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import java.lang.reflect.Method;
+import java.util.List;
+
+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.message.Message;
+import org.apache.logging.log4j.message.ObjectMessage;
+import org.apache.logging.log4j.test.appender.ListAppender;
+import org.apache.logging.log4j.util.Strings;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+
+/**
+ * Tests of Category.
+ */
+public class CategoryTest {
+
+ static ConfigurationFactory cf = new BasicConfigurationFactory();
+
+ private static ListAppender appender = new ListAppender("List");
+
+ @BeforeClass
+ public static void setupClass() {
+ appender.start();
+ ConfigurationFactory.setConfigurationFactory(cf);
+ LoggerContext.getContext().reconfigure();
+ }
+
+ @AfterClass
+ public static void cleanupClass() {
+ ConfigurationFactory.removeConfigurationFactory(cf);
+ appender.stop();
+ }
+
+ @Before
+ public void before() {
+ appender.clear();
+ }
+
+ /**
+ * Tests Category.forcedLog.
+ */
+ @Test
+ @SuppressWarnings("deprecation")
+ public void testForcedLog() {
+ final MockCategory category = new MockCategory("org.example.foo");
+ category.setAdditivity(false);
+ ((org.apache.logging.log4j.core.Logger) category.getLogger()).addAppender(appender);
+ category.info("Hello, World");
+ final List<LogEvent> list = appender.getEvents();
+ int events = list.size();
+ assertTrue("Number of events should be 1, was " + events, events == 1);
+ LogEvent event = list.get(0);
+ Message msg = event.getMessage();
+ assertNotNull("No message", msg);
+ assertTrue("Incorrect Message type", msg instanceof ObjectMessage);
+ Object[] objects = msg.getParameters();
+ assertTrue("Incorrect Object type", objects[0] instanceof String);
+ appender.clear();
+ category.log(Priority.INFO, "Hello, World");
+ events = list.size();
+ assertTrue("Number of events should be 1, was " + events, events == 1);
+ event = list.get(0);
+ msg = event.getMessage();
+ assertNotNull("No message", msg);
+ assertTrue("Incorrect Message type", msg instanceof ObjectMessage);
+ objects = msg.getParameters();
+ assertTrue("Incorrect Object type", objects[0] instanceof String);
+ appender.clear();
+ }
+
+ /**
+ * Tests that the return type of getChainedPriority is Priority.
+ *
+ * @throws Exception thrown if Category.getChainedPriority can not be found.
+ */
+ @Test
+ public void testGetChainedPriorityReturnType() throws Exception {
+ final Method method = Category.class.getMethod("getChainedPriority", (Class[]) null);
+ assertTrue(method.getReturnType() == Priority.class);
+ }
+
+ /**
+ * Tests l7dlog(Priority, String, Throwable).
+ */
+ @Test
+ public 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);
+ }
+
+ /**
+ * Tests l7dlog(Priority, String, Object[], Throwable).
+ */
+ @Test
+ public void testL7dlog4Param() {
+ final Logger logger = Logger.getLogger("org.example.foo");
+ logger.setLevel(Level.ERROR);
+ final Priority debug = Level.DEBUG;
+ logger.l7dlog(debug, "Hello, World", new Object[0], null);
+ assertTrue(appender.getEvents().size() == 0);
+ }
+
+ /**
+ * Test using a pre-existing Log4j 2 logger
+ */
+ @Test
+ public void testExistingLog4j2Logger() {
+ // create the logger using LogManager
+ org.apache.logging.log4j.LogManager.getLogger("existingLogger");
+ // Logger will be the one created above
+ final Logger logger = Logger.getLogger("existingLogger");
+ final Logger l2 = LogManager.getLogger("existingLogger");
+ assertEquals(logger, l2);
+ logger.setLevel(Level.ERROR);
+ 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);
+ }
+
+ /**
+ * Tests setPriority(Priority).
+ *
+ * @deprecated
+ */
+ @Deprecated
+ @Test
+ public void testSetPriority() {
+ final Logger logger = Logger.getLogger("org.example.foo");
+ final Priority debug = Level.DEBUG;
+ logger.setPriority(debug);
+ }
+
+ @Test
+ public void testClassName() {
+ final Category category = Category.getInstance("TestCategory");
+ final Layout<String> layout = PatternLayout.newBuilder().withPattern("%d %p %C{1.} [%t] %m%n").build();
+ final ListAppender appender = new ListAppender("List2", null, layout, false, false);
+ appender.start();
+ category.setAdditivity(false);
+ ((org.apache.logging.log4j.core.Logger) category.getLogger()).addAppender(appender);
+ category.error("Test Message");
+ final List<String> msgs = appender.getMessages();
+ assertTrue("Incorrect number of messages. Expected 1 got " + msgs.size(), msgs.size() == 1);
+ 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));
+ }
+
+ /**
+ * Derived category to check method signature of forcedLog.
+ */
+ private static class MockCategory extends Logger {
+ /**
+ * Create new instance of MockCategory.
+ *
+ * @param name category name
+ */
+ public MockCategory(final String name) {
+ super(name);
+ }
+
+ /**
+ * Request an info level message.
+ *
+ * @param msg message
+ */
+ public void info(final String msg) {
+ final Priority info = Level.INFO;
+ forcedLog(MockCategory.class.toString(), info, msg, null);
+ }
+ }
+}
diff --git a/log4j-1.2-api/src/src/test/java/org/apache/log4j/LevelTest.java b/log4j-1.2-api/src/src/test/java/org/apache/log4j/LevelTest.java
new file mode 100644
index 0000000..bb991ad
--- /dev/null
+++ b/log4j-1.2-api/src/src/test/java/org/apache/log4j/LevelTest.java
@@ -0,0 +1,284 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.Locale;
+
+import org.apache.log4j.util.SerializationTestHelper;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+
+/**
+ * Tests of Level.
+ *
+ * @since 1.2.12
+ */
+public class LevelTest {
+
+ /**
+ * Serialize Level.INFO and check against witness.
+ *
+ * @throws Exception if exception during test.
+ */
+ @Test
+ public void testSerializeINFO() throws Exception {
+ final int[] skip = new int[]{};
+ SerializationTestHelper.assertSerializationEquals(
+ "target/test-classes/witness/serialization/info.bin",
+ Level.INFO, skip, Integer.MAX_VALUE);
+ }
+
+ /**
+ * Deserialize witness and see if resolved to Level.INFO.
+ *
+ * @throws Exception if exception during test.
+ */
+ @Test
+ public void testDeserializeINFO() throws Exception {
+ final Object obj =
+ SerializationTestHelper.deserializeStream(
+ "target/test-classes/witness/serialization/info.bin");
+ assertTrue(obj instanceof Level);
+ 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);
+ }
+ }
+
+ /**
+ * Tests that a custom level can be serialized and deserialized
+ * and is not resolved to a stock level.
+ *
+ * @throws Exception if exception during test.
+ */
+ @Test
+ public void testCustomLevelSerialization() throws Exception {
+ final CustomLevel custom = new CustomLevel();
+ final Object obj = SerializationTestHelper.serializeClone(custom);
+ assertTrue(obj instanceof CustomLevel);
+
+ final CustomLevel clone = (CustomLevel) obj;
+ assertEquals(Level.INFO.level, clone.level);
+ assertEquals(Level.INFO.levelStr, clone.levelStr);
+ assertEquals(Level.INFO.syslogEquivalent, clone.syslogEquivalent);
+ }
+
+ /**
+ * Custom level to check that custom levels are
+ * serializable, but not resolved to a plain Level.
+ */
+ private static class CustomLevel extends Level {
+ /**
+ * Generated serial version ID.
+ */
+ private static final long serialVersionUID = -6931920872225831135L;
+
+ /**
+ * Create an instance of CustomLevel.
+ */
+ public CustomLevel() {
+ super(
+ Level.INFO.level, Level.INFO.levelStr, Level.INFO.syslogEquivalent);
+ }
+ }
+
+ /**
+ * Tests Level.TRACE_INT.
+ */
+ @Test
+ public void testTraceInt() {
+ assertEquals(5000, Level.TRACE_INT);
+ }
+
+ /**
+ * Tests Level.TRACE.
+ */
+ @Test
+ public void testTrace() {
+ assertEquals("TRACE", Level.TRACE.toString());
+ assertEquals(5000, Level.TRACE.toInt());
+ assertEquals(7, Level.TRACE.getSyslogEquivalent());
+ }
+
+ /**
+ * Tests Level.toLevel(Level.TRACE_INT).
+ */
+ @Test
+ public void testIntToTrace() {
+ final Level trace = Level.toLevel(5000);
+ assertEquals("TRACE", trace.toString());
+ }
+
+ /**
+ * Tests Level.toLevel("TRACE");
+ */
+ @Test
+ public void testStringToTrace() {
+ final Level trace = Level.toLevel("TRACE");
+ assertEquals("TRACE", trace.toString());
+ }
+
+ /**
+ * Tests that Level extends Priority.
+ */
+ @Test
+ public void testLevelExtendsPriority() {
+ assertTrue(Priority.class.isAssignableFrom(Level.class));
+ }
+
+ /**
+ * Tests Level.OFF.
+ */
+ @Test
+ public void testOFF() {
+ assertTrue(Level.OFF instanceof Level);
+ }
+
+ /**
+ * Tests Level.FATAL.
+ */
+ @Test
+ public void testFATAL() {
+ assertTrue(Level.FATAL instanceof Level);
+ }
+
+ /**
+ * Tests Level.ERROR.
+ */
+ @Test
+ public void testERROR() {
+ assertTrue(Level.ERROR instanceof Level);
+ }
+
+ /**
+ * Tests Level.WARN.
+ */
+ @Test
+ public void testWARN() {
+ assertTrue(Level.WARN instanceof Level);
+ }
+
+ /**
+ * Tests Level.INFO.
+ */
+ @Test
+ public void testINFO() {
+ assertTrue(Level.INFO instanceof Level);
+ }
+
+ /**
+ * Tests Level.DEBUG.
+ */
+ @Test
+ public void testDEBUG() {
+ assertTrue(Level.DEBUG instanceof Level);
+ }
+
+ /**
+ * Tests Level.TRACE.
+ */
+ @Test
+ public void testTRACE() {
+ assertTrue(Level.TRACE instanceof Level);
+ }
+
+ /**
+ * Tests Level.ALL.
+ */
+ @Test
+ public void testALL() {
+ assertTrue(Level.ALL instanceof Level);
+ }
+
+ /**
+ * Tests Level.toLevel(Level.All_INT).
+ */
+ @Test
+ public void testIntToAll() {
+ final Level level = Level.toLevel(Priority.ALL_INT);
+ assertEquals("ALL", level.toString());
+ }
+
+ /**
+ * Tests Level.toLevel(Level.FATAL_INT).
+ */
+ @Test
+ public void testIntToFatal() {
+ final Level level = Level.toLevel(Priority.FATAL_INT);
+ assertEquals("FATAL", level.toString());
+ }
+
+
+ /**
+ * Tests Level.toLevel(Level.OFF_INT).
+ */
+ @Test
+ public void testIntToOff() {
+ final Level level = Level.toLevel(Priority.OFF_INT);
+ assertEquals("OFF", level.toString());
+ }
+
+ /**
+ * Tests Level.toLevel(17, Level.FATAL).
+ */
+ @Test
+ public void testToLevelUnrecognizedInt() {
+ final Level level = Level.toLevel(17, Level.FATAL);
+ assertEquals("FATAL", level.toString());
+ }
+
+ /**
+ * Tests Level.toLevel(null, Level.FATAL).
+ */
+ @Test
+ public void testToLevelNull() {
+ final Level level = Level.toLevel(null, Level.FATAL);
+ assertEquals("FATAL", level.toString());
+ }
+
+ /**
+ * Test that dotless lower I + "nfo" is recognized as INFO.
+ */
+ @Test
+ public void testDotlessLowerI() {
+ final Level level = Level.toLevel("\u0131nfo");
+ assertEquals("INFO", level.toString());
+ }
+
+ /**
+ * Test that dotted lower I + "nfo" is recognized as INFO
+ * even in Turkish locale.
+ */
+ @Test
+ public void testDottedLowerI() {
+ final Locale defaultLocale = Locale.getDefault();
+ final Locale turkey = new Locale("tr", "TR");
+ Locale.setDefault(turkey);
+ final Level level = Level.toLevel("info");
+ Locale.setDefault(defaultLocale);
+ assertEquals("INFO", level.toString());
+ }
+
+
+}
+
diff --git a/log4j-1.2-api/src/src/test/java/org/apache/log4j/ListAppender.java b/log4j-1.2-api/src/src/test/java/org/apache/log4j/ListAppender.java
new file mode 100644
index 0000000..83c2758
--- /dev/null
+++ b/log4j-1.2-api/src/src/test/java/org/apache/log4j/ListAppender.java
@@ -0,0 +1,83 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.log4j;
+
+import org.apache.log4j.spi.LoggingEvent;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 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<LoggingEvent> events = Collections.synchronizedList(new ArrayList<>());
+
+ private final List<String> messages = Collections.synchronizedList(new ArrayList<>());
+
+
+ private static final String WINDOWS_LINE_SEP = "\r\n";
+
+ @Override
+ protected void append(LoggingEvent event) {
+ Layout layout = getLayout();
+ if (layout != null) {
+ String result = layout.format(event);
+ if (result != null) {
+ messages.add(result);
+ }
+ } else {
+ events.add(event);
+ }
+ }
+
+ @Override
+ public void close() {
+
+ }
+
+ @Override
+ public boolean requiresLayout() {
+ return false;
+ }
+
+ /** Returns an immutable snapshot of captured log events */
+ public List<LoggingEvent> getEvents() {
+ return Collections.unmodifiableList(new ArrayList<>(events));
+ }
+
+ /** Returns an immutable snapshot of captured messages */
+ public List<String> 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<String> getMessages(final int minSize, final long timeout, final TimeUnit timeUnit) throws InterruptedException {
+ final long endMillis = System.currentTimeMillis() + timeUnit.toMillis(timeout);
+ while (messages.size() < minSize && System.currentTimeMillis() < endMillis) {
+ Thread.sleep(100);
+ }
+ return getMessages();
+ }
+}
diff --git a/log4j-1.2-api/src/src/test/java/org/apache/log4j/LogWithMDCTest.java b/log4j-1.2-api/src/src/test/java/org/apache/log4j/LogWithMDCTest.java
new file mode 100644
index 0000000..997d745
--- /dev/null
+++ b/log4j-1.2-api/src/src/test/java/org/apache/log4j/LogWithMDCTest.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.logging.log4j.junit.LoggerContextRule;
+import org.apache.logging.log4j.test.appender.ListAppender;
+import org.junit.ClassRule;
+import org.junit.Test;
+
+import java.util.List;
+
+import static org.junit.Assert.*;
+
+/**
+ * Test logging with MDC values.
+ */
+public class LogWithMDCTest {
+
+ private static final String CONFIG = "logWithMDC.xml";
+
+ @ClassRule
+ public static final LoggerContextRule CTX = new LoggerContextRule(CONFIG);
+
+ @Test
+ public void testMDC() throws Exception {
+ MDC.put("Key1", "John");
+ MDC.put("Key2", "Smith");
+ try {
+ final Logger logger = Logger.getLogger("org.apache.test.logging");
+ logger.debug("This is a test");
+ final ListAppender listApp = (ListAppender) CTX.getAppender("List");
+ assertNotNull(listApp);
+ final List<String> msgs = listApp.getMessages();
+ assertNotNull("No messages received", msgs);
+ assertTrue(msgs.size() == 1);
+ assertTrue("Key1 is missing", msgs.get(0).contains("Key1=John"));
+ assertTrue("Key2 is missing", msgs.get(0).contains("Key2=Smith"));
+ } finally {
+ MDC.remove("Key1");
+ MDC.remove("Key2");
+ }
+ }
+}
diff --git a/log4j-1.2-api/src/src/test/java/org/apache/log4j/LogWithRouteTest.java b/log4j-1.2-api/src/src/test/java/org/apache/log4j/LogWithRouteTest.java
new file mode 100644
index 0000000..606e87b
--- /dev/null
+++ b/log4j-1.2-api/src/src/test/java/org/apache/log4j/LogWithRouteTest.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;
+
+import org.apache.logging.log4j.junit.LoggerContextRule;
+import org.apache.logging.log4j.test.appender.ListAppender;
+import org.junit.ClassRule;
+import org.junit.Test;
+
+import java.util.List;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * 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);
+
+ @Test
+ public void testMDC() throws Exception {
+ MDC.put("Type", "Service");
+ MDC.put("Name", "John Smith");
+ try {
+ final Logger logger = Logger.getLogger("org.apache.test.logging");
+ logger.debug("This is a test");
+ final ListAppender listApp = (ListAppender) CTX.getAppender("List");
+ assertNotNull(listApp);
+ final List<String> 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"));
+ } finally {
+ MDC.remove("Type");
+ MDC.remove("Name");
+ }
+ }
+}
diff --git a/log4j-1.2-api/src/src/test/java/org/apache/log4j/LoggerTest.java b/log4j-1.2-api/src/src/test/java/org/apache/log4j/LoggerTest.java
new file mode 100644
index 0000000..0c4308f
--- /dev/null
+++ b/log4j-1.2-api/src/src/test/java/org/apache/log4j/LoggerTest.java
@@ -0,0 +1,525 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF 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.List;
+import java.util.Locale;
+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.*;
+
+/**
+ * Used for internal unit testing the Logger class.
+ */
+public class LoggerTest {
+
+ Appender a1;
+ Appender a2;
+
+ static ResourceBundle rbUS;
+ static ResourceBundle rbFR;
+ static ResourceBundle rbCH;
+
+ // A short message.
+ static String MSG = "M";
+
+ static ConfigurationFactory configurationFactory = new BasicConfigurationFactory();
+
+ @BeforeClass
+ public static void setUpClass() {
+ rbUS = ResourceBundle.getBundle("L7D", new Locale("en", "US"));
+ assertNotNull(rbUS);
+
+ rbFR = ResourceBundle.getBundle("L7D", new Locale("fr", "FR"));
+ assertNotNull("Got a null resource bundle.", rbFR);
+
+ rbCH = ResourceBundle.getBundle("L7D", new Locale("fr", "CH"));
+ assertNotNull("Got a null resource bundle.", rbCH);
+
+ ConfigurationFactory.setConfigurationFactory(configurationFactory);
+ }
+
+ @AfterClass
+ public static void tearDownClass() {
+ ConfigurationFactory.removeConfigurationFactory(configurationFactory);
+ }
+
+ @After
+ public void tearDown() {
+ LoggerContext.getContext().reconfigure();
+ a1 = null;
+ a2 = null;
+ }
+
+ /**
+ * 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);
+ } */
+
+ /**
+ * 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());
+ } */
+
+ /**
+ * Test if logger a.b inherits its appender from a.
+ */
+ @Test
+ public void testAdditivity1() {
+ final Logger loggerA = Logger.getLogger("a");
+ final Logger loggerAB = Logger.getLogger("a.b");
+ final CountingAppender coutingAppender = new CountingAppender();
+ coutingAppender.start();
+ try {
+ ((org.apache.logging.log4j.core.Logger) loggerA.getLogger()).addAppender(coutingAppender);
+
+ assertEquals(0, coutingAppender.counter);
+ loggerAB.debug(MSG);
+ assertEquals(1, coutingAppender.counter);
+ loggerAB.info(MSG);
+ assertEquals(2, coutingAppender.counter);
+ loggerAB.warn(MSG);
+ assertEquals(3, coutingAppender.counter);
+ loggerAB.error(MSG);
+ assertEquals(4, coutingAppender.counter);
+ coutingAppender.stop();
+ } finally {
+ ((org.apache.logging.log4j.core.Logger) loggerA.getLogger()).removeAppender(coutingAppender);
+ }
+ }
+
+ /**
+ * Test multiple additivity.
+ */
+ @Test
+ public void testAdditivity2() {
+ final Logger a = Logger.getLogger("a");
+ final Logger ab = Logger.getLogger("a.b");
+ final Logger abc = Logger.getLogger("a.b.c");
+ final Logger x = Logger.getLogger("x");
+
+ final CountingAppender ca1 = new CountingAppender();
+ ca1.start();
+ final CountingAppender ca2 = new CountingAppender();
+ ca2.start();
+
+ try {
+ ((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);
+
+ ab.debug(MSG);
+ assertEquals(ca1.counter, 1);
+ assertEquals(ca2.counter, 0);
+
+ abc.debug(MSG);
+ assertEquals(ca1.counter, 2);
+ assertEquals(ca2.counter, 1);
+
+ x.debug(MSG);
+ assertEquals(ca1.counter, 2);
+ assertEquals(ca2.counter, 1);
+ ca1.stop();
+ ca2.stop();
+ } finally {
+ ((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() {
+ final Logger root = Logger.getRootLogger();
+ final Logger a = Logger.getLogger("a");
+ final Logger ab = Logger.getLogger("a.b");
+ final Logger abc = Logger.getLogger("a.b.c");
+ Logger.getLogger("x");
+
+ final CountingAppender caRoot = new CountingAppender();
+ caRoot.start();
+ final CountingAppender caA = new CountingAppender();
+ caA.start();
+ final CountingAppender caABC = new CountingAppender();
+ caABC.start();
+ try {
+ ((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);
+
+ ab.setAdditivity(false);
+
+ a.debug(MSG);
+ assertEquals(caRoot.counter, 1);
+ assertEquals(caA.counter, 1);
+ assertEquals(caABC.counter, 0);
+
+ ab.debug(MSG);
+ assertEquals(caRoot.counter, 1);
+ assertEquals(caA.counter, 1);
+ assertEquals(caABC.counter, 0);
+
+ abc.debug(MSG);
+ assertEquals(caRoot.counter, 1);
+ assertEquals(caA.counter, 1);
+ assertEquals(caABC.counter, 1);
+ caRoot.stop();
+ caA.stop();
+ caABC.stop();
+ } finally {
+ ((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() {
+ CountingAppender caRoot = new CountingAppender();
+ Logger root = Logger.getRootLogger();
+ root.getLogger().addAppender(caRoot);
+
+ LoggerRepository h = LogManager.getLoggerRepository();
+ //h.disableDebug();
+ h.setThreshold((Level) Level.INFO);
+ assertEquals(caRoot.counter, 0);
+
+ root.debug(MSG);
+ assertEquals(caRoot.counter, 0);
+ root.info(MSG);
+ assertEquals(caRoot.counter, 1);
+ root.log(Level.WARN, MSG);
+ assertEquals(caRoot.counter, 2);
+ root.warn(MSG);
+ assertEquals(caRoot.counter, 3);
+
+ //h.disableInfo();
+ h.setThreshold((Level) Level.WARN);
+ root.debug(MSG);
+ assertEquals(caRoot.counter, 3);
+ root.info(MSG);
+ assertEquals(caRoot.counter, 3);
+ root.log(Level.WARN, MSG);
+ assertEquals(caRoot.counter, 4);
+ root.error(MSG);
+ assertEquals(caRoot.counter, 5);
+ root.log(Level.ERROR, MSG);
+ assertEquals(caRoot.counter, 6);
+
+ //h.disableAll();
+ h.setThreshold(Level.OFF);
+ root.debug(MSG);
+ assertEquals(caRoot.counter, 6);
+ root.info(MSG);
+ assertEquals(caRoot.counter, 6);
+ root.log(Level.WARN, MSG);
+ assertEquals(caRoot.counter, 6);
+ root.error(MSG);
+ assertEquals(caRoot.counter, 6);
+ root.log(Level.FATAL, MSG);
+ assertEquals(caRoot.counter, 6);
+ root.log(Level.FATAL, MSG);
+ assertEquals(caRoot.counter, 6);
+
+ //h.disable(Level.FATAL);
+ h.setThreshold(Level.OFF);
+ root.debug(MSG);
+ assertEquals(caRoot.counter, 6);
+ root.info(MSG);
+ assertEquals(caRoot.counter, 6);
+ root.log(Level.WARN, MSG);
+ assertEquals(caRoot.counter, 6);
+ root.error(MSG);
+ assertEquals(caRoot.counter, 6);
+ root.log(Level.ERROR, MSG);
+ assertEquals(caRoot.counter, 6);
+ root.log(Level.FATAL, MSG);
+ assertEquals(caRoot.counter, 6);
+ } */
+
+ @Test
+ public void testRB1() {
+ final Logger root = Logger.getRootLogger();
+ root.setResourceBundle(rbUS);
+ ResourceBundle t = root.getResourceBundle();
+ assertSame(t, rbUS);
+
+ final Logger x = Logger.getLogger("x");
+ final Logger x_y = Logger.getLogger("x.y");
+ final Logger x_y_z = Logger.getLogger("x.y.z");
+
+ t = x.getResourceBundle();
+ assertSame(t, rbUS);
+ t = x_y.getResourceBundle();
+ assertSame(t, rbUS);
+ t = x_y_z.getResourceBundle();
+ assertSame(t, rbUS);
+ }
+
+ @Test
+ public void testRB2() {
+ final Logger root = Logger.getRootLogger();
+ root.setResourceBundle(rbUS);
+ ResourceBundle t = root.getResourceBundle();
+ assertTrue(t == rbUS);
+
+ final Logger x = Logger.getLogger("x");
+ final Logger x_y = Logger.getLogger("x.y");
+ final Logger x_y_z = Logger.getLogger("x.y.z");
+
+ x_y.setResourceBundle(rbFR);
+ t = x.getResourceBundle();
+ assertSame(t, rbUS);
+ t = x_y.getResourceBundle();
+ assertSame(t, rbFR);
+ t = x_y_z.getResourceBundle();
+ assertSame(t, rbFR);
+ }
+
+ @Test
+ public void testRB3() {
+ final Logger root = Logger.getRootLogger();
+ root.setResourceBundle(rbUS);
+ ResourceBundle t = root.getResourceBundle();
+ assertTrue(t == rbUS);
+
+ final Logger x = Logger.getLogger("x");
+ final Logger x_y = Logger.getLogger("x.y");
+ final Logger x_y_z = Logger.getLogger("x.y.z");
+
+ x_y.setResourceBundle(rbFR);
+ x_y_z.setResourceBundle(rbCH);
+ t = x.getResourceBundle();
+ assertSame(t, rbUS);
+ t = x_y.getResourceBundle();
+ assertSame(t, rbFR);
+ t = x_y_z.getResourceBundle();
+ assertSame(t, rbCH);
+ }
+
+ @Test
+ public 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");
+
+ Logger t;
+ t = LogManager.exists("xx");
+ assertNull(t);
+ t = LogManager.exists("a");
+ assertSame(a, t);
+ t = LogManager.exists("a.b");
+ assertSame(a_b, t);
+ 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));
+ Logger a0 = h.getLogger("a");
+ assertEquals("a", a0.getName());
+ assertNull(a0.getLevel());
+ assertSame(Level.ERROR, a0.getEffectiveLevel());
+
+ Logger a1 = h.getLogger("a");
+ assertSame(a0, a1);
+ } */
+
+ /**
+ * Tests logger.trace(Object).
+ */
+ @Test
+ public void testTrace() {
+ final ListAppender appender = new ListAppender("List");
+ appender.start();
+ final Logger root = Logger.getRootLogger();
+ ((org.apache.logging.log4j.core.Logger) root.getLogger()).addAppender(appender);
+ root.setLevel(Level.INFO);
+
+ final Logger tracer = Logger.getLogger("com.example.Tracer");
+ tracer.setLevel(Level.TRACE);
+
+ tracer.trace("Message 1");
+ root.trace("Discarded Message");
+ root.trace("Discarded Message");
+
+ final List<LogEvent> msgs = appender.getEvents();
+ assertEquals(1, msgs.size());
+ final LogEvent event = msgs.get(0);
+ assertEquals(org.apache.logging.log4j.Level.TRACE, event.getLevel());
+ assertEquals("Message 1", event.getMessage().getFormat());
+ appender.stop();
+ ((org.apache.logging.log4j.core.Logger) root.getLogger()).removeAppender(appender);
+ }
+
+ /**
+ * Tests logger.trace(Object, Exception).
+ */
+ @Test
+ public void testTraceWithException() {
+ final ListAppender appender = new ListAppender("List");
+ appender.start();
+ final Logger root = Logger.getRootLogger();
+ try {
+ ((org.apache.logging.log4j.core.Logger) root.getLogger()).addAppender(appender);
+ root.setLevel(Level.INFO);
+
+ final Logger tracer = Logger.getLogger("com.example.Tracer");
+ tracer.setLevel(Level.TRACE);
+ final NullPointerException ex = new NullPointerException();
+
+ tracer.trace("Message 1", ex);
+ root.trace("Discarded Message", ex);
+ root.trace("Discarded Message", ex);
+
+ final List<LogEvent> msgs = appender.getEvents();
+ assertEquals(1, msgs.size());
+ final LogEvent event = msgs.get(0);
+ assertEquals(org.apache.logging.log4j.Level.TRACE, event.getLevel());
+ assertEquals("Message 1", event.getMessage().getFormattedMessage());
+ appender.stop();
+ } finally {
+ ((org.apache.logging.log4j.core.Logger) root.getLogger()).removeAppender(appender);
+ }
+ }
+
+ /**
+ * Tests isTraceEnabled.
+ */
+ @Test
+ public void testIsTraceEnabled() {
+ final ListAppender appender = new ListAppender("List");
+ appender.start();
+ final Logger root = Logger.getRootLogger();
+ try {
+ ((org.apache.logging.log4j.core.Logger) root.getLogger()).addAppender(appender);
+ root.setLevel(Level.INFO);
+
+ final Logger tracer = Logger.getLogger("com.example.Tracer");
+ tracer.setLevel(Level.TRACE);
+
+ assertTrue(tracer.isTraceEnabled());
+ assertFalse(root.isTraceEnabled());
+ appender.stop();
+ } finally {
+ ((org.apache.logging.log4j.core.Logger) root.getLogger()).removeAppender(appender);
+ }
+ }
+
+ @Test
+ @SuppressWarnings("deprecation")
+ public void testLog() {
+ final PatternLayout layout = PatternLayout.newBuilder().withPattern("%d %C %L %m").build();
+ final ListAppender appender = new ListAppender("List", null, layout, false, false);
+ appender.start();
+ final Logger root = Logger.getRootLogger();
+ try {
+ ((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<String> msgs = appender.getMessages();
+ assertTrue("Incorrect number of messages", msgs.size() == 3);
+ final String msg = msgs.get(0);
+ assertTrue("Message contains incorrect class name: " + msg, msg.contains(LoggerTest.class.getName()));
+ appender.stop();
+ } finally {
+ ((org.apache.logging.log4j.core.Logger) root.getLogger()).removeAppender(appender);
+ }
+ }
+
+ private static class MyLogger {
+
+ private final Logger logger;
+
+ public MyLogger(final Logger logger) {
+ this.logger = logger;
+ }
+
+ @SuppressWarnings("deprecation")
+ public void logInfo(final String msg, final Throwable t) {
+ logger.log(MyLogger.class.getName(), Priority.INFO, msg, t);
+ }
+ }
+
+ private static class CountingAppender extends AbstractAppender {
+
+ private static final long serialVersionUID = 1L;
+
+ int counter;
+
+ CountingAppender() {
+ super("Counter", null, null, true, Property.EMPTY_ARRAY);
+ counter = 0;
+ }
+
+ @Override
+ public void append(final LogEvent event) {
+ counter++;
+ }
+
+ public boolean requiresLayout() {
+ return true;
+ }
+ }
+}
+
diff --git a/log4j-1.2-api/src/src/test/java/org/apache/log4j/LoggingTest.java b/log4j-1.2-api/src/src/test/java/org/apache/log4j/LoggingTest.java
new file mode 100644
index 0000000..4ec4fb8
--- /dev/null
+++ b/log4j-1.2-api/src/src/test/java/org/apache/log4j/LoggingTest.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.log4j;
+
+import org.apache.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());
+ }
+
+}
diff --git a/log4j-1.2-api/src/src/test/java/org/apache/log4j/MDCTestCase.java b/log4j-1.2-api/src/src/test/java/org/apache/log4j/MDCTestCase.java
new file mode 100644
index 0000000..c0e5ba5
--- /dev/null
+++ b/log4j-1.2-api/src/src/test/java/org/apache/log4j/MDCTestCase.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;
+
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+public class MDCTestCase {
+
+ @Before
+ public void setUp() {
+ MDC.clear();
+ }
+
+ @After
+ public void tearDown() {
+ MDC.clear();
+ }
+
+ @Test
+ public void testPut() throws Exception {
+ MDC.put("key", "some value");
+ Assert.assertEquals("some value", MDC.get("key"));
+ Assert.assertEquals(1, MDC.getContext().size());
+ }
+
+ @Test
+ public void testRemoveLastKey() throws Exception {
+ MDC.put("key", "some value");
+ MDC.remove("key");
+ }
+
+}
diff --git a/log4j-1.2-api/src/src/test/java/org/apache/log4j/NDCTest.java b/log4j-1.2-api/src/src/test/java/org/apache/log4j/NDCTest.java
new file mode 100644
index 0000000..c8baf0f
--- /dev/null
+++ b/log4j-1.2-api/src/src/test/java/org/apache/log4j/NDCTest.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;
+
+import org.apache.logging.log4j.util.Strings;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class NDCTest {
+
+ @Test
+ public void testPopEmpty() {
+ NDC.clear();
+ Assert.assertEquals(Strings.EMPTY, NDC.pop());
+ }
+
+ @Test
+ public void testPeekEmpty() {
+ NDC.clear();
+ Assert.assertEquals(Strings.EMPTY, NDC.peek());
+ }
+}
diff --git a/log4j-1.2-api/src/src/test/java/org/apache/log4j/PriorityTest.java b/log4j-1.2-api/src/src/test/java/org/apache/log4j/PriorityTest.java
new file mode 100644
index 0000000..63321a7
--- /dev/null
+++ b/log4j-1.2-api/src/src/test/java/org/apache/log4j/PriorityTest.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.log4j;
+
+import java.util.Locale;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * Tests of Priority.
+ *
+ */
+public class PriorityTest {
+
+ /**
+ * Tests Priority.OFF_INT.
+ */
+ @Test
+ public void testOffInt() {
+ assertEquals(Integer.MAX_VALUE, Priority.OFF_INT);
+ }
+
+ /**
+ * Tests Priority.FATAL_INT.
+ */
+ @Test
+ public void testFatalInt() {
+ assertEquals(50000, Priority.FATAL_INT);
+ }
+
+ /**
+ * Tests Priority.ERROR_INT.
+ */
+ @Test
+ public void testErrorInt() {
+ assertEquals(40000, Priority.ERROR_INT);
+ }
+
+ /**
+ * Tests Priority.WARN_INT.
+ */
+ @Test
+ public void testWarnInt() {
+ assertEquals(30000, Priority.WARN_INT);
+ }
+
+ /**
+ * Tests Priority.INFO_INT.
+ */
+ @Test
+ public void testInfoInt() {
+ assertEquals(20000, Priority.INFO_INT);
+ }
+
+ /**
+ * Tests Priority.DEBUG_INT.
+ */
+ @Test
+ public void testDebugInt() {
+ assertEquals(10000, Priority.DEBUG_INT);
+ }
+
+ /**
+ * Tests Priority.ALL_INT.
+ */
+ @Test
+ public void testAllInt() {
+ assertEquals(Integer.MIN_VALUE, Priority.ALL_INT);
+ }
+
+ /**
+ * Tests Priority.FATAL.
+ */
+ @Test
+ @SuppressWarnings("deprecation")
+ public void testFatal() {
+ assertTrue(Priority.FATAL instanceof Level);
+ }
+
+ /**
+ * Tests Priority.ERROR.
+ */
+ @Test
+ @SuppressWarnings("deprecation")
+ public void testERROR() {
+ assertTrue(Priority.ERROR instanceof Level);
+ }
+
+ /**
+ * Tests Priority.WARN.
+ */
+ @Test
+ @SuppressWarnings("deprecation")
+ public void testWARN() {
+ assertTrue(Priority.WARN instanceof Level);
+ }
+
+ /**
+ * Tests Priority.INFO.
+ */
+ @Test
+ @SuppressWarnings("deprecation")
+ public void testINFO() {
+ assertTrue(Priority.INFO instanceof Level);
+ }
+
+ /**
+ * Tests Priority.DEBUG.
+ */
+ @Test
+ @SuppressWarnings("deprecation")
+ public void testDEBUG() {
+ assertTrue(Priority.DEBUG instanceof Level);
+ }
+
+ /**
+ * Tests Priority.equals(null).
+ */
+ @Test
+ @SuppressWarnings("deprecation")
+ public void testEqualsNull() {
+ assertFalse(Priority.DEBUG.equals(null));
+ }
+
+ /**
+ * Tests Priority.equals(Level.DEBUG).
+ */
+ @Test
+ @SuppressWarnings("deprecation")
+ public void testEqualsLevel() {
+ //
+ // this behavior violates the equals contract.
+ //
+ assertTrue(Priority.DEBUG.equals(Level.DEBUG));
+ }
+
+ /**
+ * Tests getAllPossiblePriorities().
+ */
+ @Test
+ @SuppressWarnings("deprecation")
+ public void testGetAllPossiblePriorities() {
+ final Priority[] priorities = Priority.getAllPossiblePriorities();
+ assertEquals(5, priorities.length);
+ }
+
+ /**
+ * Tests toPriority(String).
+ */
+ @Test
+ @SuppressWarnings("deprecation")
+ public void testToPriorityString() {
+ assertTrue(Priority.toPriority("DEBUG") == Level.DEBUG);
+ }
+
+ /**
+ * Tests toPriority(int).
+ */
+ @Test
+ @SuppressWarnings("deprecation")
+ public void testToPriorityInt() {
+ assertTrue(Priority.toPriority(Priority.DEBUG_INT) == Level.DEBUG);
+ }
+
+ /**
+ * Tests toPriority(String, Priority).
+ */
+ @Test
+ @SuppressWarnings("deprecation")
+ public void testToPriorityStringPriority() {
+ assertTrue(Priority.toPriority("foo", Priority.DEBUG) == Priority.DEBUG);
+ }
+
+ /**
+ * Tests toPriority(int, Priority).
+ */
+ @Test
+ @SuppressWarnings("deprecation")
+ public void testToPriorityIntPriority() {
+ assertTrue(Priority.toPriority(17, Priority.DEBUG) == Priority.DEBUG);
+ }
+
+ /**
+ * Test that dotless lower I + "nfo" is recognized as INFO.
+ */
+ @Test
+ @SuppressWarnings("deprecation")
+ public void testDotlessLowerI() {
+ final Priority level = Priority.toPriority("\u0131nfo");
+ assertEquals("INFO", level.toString());
+ }
+
+ /**
+ * Test that dotted lower I + "nfo" is recognized as INFO
+ * even in Turkish locale.
+ */
+ @Test
+ @SuppressWarnings("deprecation")
+ public 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/src/test/java/org/apache/log4j/VelocityTest.java b/log4j-1.2-api/src/src/test/java/org/apache/log4j/VelocityTest.java
new file mode 100644
index 0000000..99bd5a2
--- /dev/null
+++ b/log4j-1.2-api/src/src/test/java/org/apache/log4j/VelocityTest.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.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/src/test/java/org/apache/log4j/config/AbstractLog4j1ConfigurationConverterTest.java b/log4j-1.2-api/src/src/test/java/org/apache/log4j/config/AbstractLog4j1ConfigurationConverterTest.java
new file mode 100644
index 0000000..9c973ee
--- /dev/null
+++ b/log4j-1.2-api/src/src/test/java/org/apache/log4j/config/AbstractLog4j1ConfigurationConverterTest.java
@@ -0,0 +1,68 @@
+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
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+
+@RunWith(Parameterized.class)
+public abstract class AbstractLog4j1ConfigurationConverterTest {
+
+ protected static List<Path> getPaths(final String root) throws IOException {
+ final List<Path> paths = new ArrayList<>();
+ Files.walkFileTree(Paths.get(root), new SimpleFileVisitor<Path>() {
+ @Override
+ public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) throws IOException {
+ paths.add(file.toAbsolutePath());
+ return FileVisitResult.CONTINUE;
+ }
+ });
+ return paths;
+ }
+
+ private final Path pathIn;
+
+ public AbstractLog4j1ConfigurationConverterTest(final Path path) {
+ super();
+ this.pathIn = path;
+ }
+
+ @Test
+ public void test() throws IOException {
+ final Path tempFile = Files.createTempFile("log4j2", ".xml");
+ try {
+ final Log4j1ConfigurationConverter.CommandLineArguments cla = new Log4j1ConfigurationConverter.CommandLineArguments();
+ cla.setPathIn(pathIn);
+ cla.setPathOut(tempFile);
+ Log4j1ConfigurationConverter.run(cla);
+ } finally {
+ Files.deleteIfExists(tempFile);
+ }
+ }
+}
diff --git a/log4j-1.2-api/src/src/test/java/org/apache/log4j/config/AutoConfigTest.java b/log4j-1.2-api/src/src/test/java/org/apache/log4j/config/AutoConfigTest.java
new file mode 100644
index 0000000..764f612
--- /dev/null
+++ b/log4j-1.2-api/src/src/test/java/org/apache/log4j/config/AutoConfigTest.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 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.config.Configuration;
+import org.apache.logging.log4j.spi.LoggerContext;
+import org.junit.Test;
+
+import java.io.File;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Test configuration from XML.
+ */
+public class AutoConfigTest {
+
+ @Test
+ public void testListAppender() {
+ Logger logger = LogManager.getLogger("test");
+ logger.debug("This is a test of the root logger");
+ LoggerContext loggerContext = org.apache.logging.log4j.LogManager.getContext(false);
+ Configuration configuration = ((org.apache.logging.log4j.core.LoggerContext) loggerContext).getConfiguration();
+ Map<String, Appender> appenders = configuration.getAppenders();
+ ListAppender eventAppender = null;
+ ListAppender messageAppender = null;
+ for (Map.Entry<String, Appender> 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("No Event Appender", eventAppender);
+ assertNotNull("No Message Appender", messageAppender);
+ List<LoggingEvent> events = eventAppender.getEvents();
+ assertTrue("No events", events != null && events.size() > 0);
+ List<String> messages = messageAppender.getMessages();
+ assertTrue("No messages", messages != null && messages.size() > 0);
+ }
+
+}
diff --git a/log4j-1.2-api/src/src/test/java/org/apache/log4j/config/Log4j1ConfigurationConverterHadoopTest.java b/log4j-1.2-api/src/src/test/java/org/apache/log4j/config/Log4j1ConfigurationConverterHadoopTest.java
new file mode 100644
index 0000000..152f5dd
--- /dev/null
+++ b/log4j-1.2-api/src/src/test/java/org/apache/log4j/config/Log4j1ConfigurationConverterHadoopTest.java
@@ -0,0 +1,39 @@
+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
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+
+@RunWith(Parameterized.class)
+public class Log4j1ConfigurationConverterHadoopTest extends AbstractLog4j1ConfigurationConverterTest {
+
+ @Parameterized.Parameters(name = "{0}")
+ public static List<Path> data() throws IOException {
+ return getPaths("src/test/resources/config-1.2/hadoop");
+ }
+
+ public Log4j1ConfigurationConverterHadoopTest(final Path path) {
+ super(path);
+ }
+
+}
diff --git a/log4j-1.2-api/src/src/test/java/org/apache/log4j/config/Log4j1ConfigurationConverterSparkTest.java b/log4j-1.2-api/src/src/test/java/org/apache/log4j/config/Log4j1ConfigurationConverterSparkTest.java
new file mode 100644
index 0000000..2b39d4f
--- /dev/null
+++ b/log4j-1.2-api/src/src/test/java/org/apache/log4j/config/Log4j1ConfigurationConverterSparkTest.java
@@ -0,0 +1,39 @@
+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
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+
+@RunWith(Parameterized.class)
+public class Log4j1ConfigurationConverterSparkTest extends AbstractLog4j1ConfigurationConverterTest {
+
+ @Parameterized.Parameters(name = "{0}")
+ public static List<Path> data() throws IOException {
+ return getPaths("src/test/resources/config-1.2/spark");
+ }
+
+ public Log4j1ConfigurationConverterSparkTest(final Path path) {
+ super(path);
+ }
+
+}
diff --git a/log4j-1.2-api/src/src/test/java/org/apache/log4j/config/Log4j1ConfigurationFactoryTest.java b/log4j-1.2-api/src/src/test/java/org/apache/log4j/config/Log4j1ConfigurationFactoryTest.java
new file mode 100644
index 0000000..ebe3e54
--- /dev/null
+++ b/log4j-1.2-api/src/src/test/java/org/apache/log4j/config/Log4j1ConfigurationFactoryTest.java
@@ -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.
+ */
+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 java.io.File;
+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.Layout;
+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.layout.PatternLayout;
+import org.junit.Test;
+
+public class Log4j1ConfigurationFactoryTest {
+
+ private Layout<?> testConsole(final String configResource) throws Exception {
+ final Configuration configuration = getConfiguration(configResource);
+ final String name = "Console";
+ final ConsoleAppender appender = configuration.getAppender(name);
+ assertNotNull("Missing appender '" + name + "' in configuration " + configResource + " → " + configuration,
+ appender);
+ assertEquals(Target.SYSTEM_ERR, appender.getTarget());
+ //
+ final LoggerConfig loggerConfig = configuration.getLoggerConfig("com.example.foo");
+ assertNotNull(loggerConfig);
+ assertEquals(Level.DEBUG, loggerConfig.getLevel());
+ configuration.start();
+ configuration.stop();
+ 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);
+ 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 (final FileSystemException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ @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();
+ }
+
+}
diff --git a/log4j-1.2-api/src/src/test/java/org/apache/log4j/config/XmlConfigurationFactoryTest.java b/log4j-1.2-api/src/src/test/java/org/apache/log4j/config/XmlConfigurationFactoryTest.java
new file mode 100644
index 0000000..d522a18
--- /dev/null
+++ b/log4j-1.2-api/src/src/test/java/org/apache/log4j/config/XmlConfigurationFactoryTest.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.config;
+
+import org.apache.log4j.ListAppender;
+import org.apache.log4j.LogManager;
+import org.apache.log4j.Logger;
+import org.apache.log4j.bridge.AppenderAdapter;
+import org.apache.log4j.spi.LoggingEvent;
+import org.apache.log4j.xml.DOMConfigurator;
+import org.apache.log4j.xml.XmlConfigurationFactory;
+import org.apache.logging.log4j.core.Appender;
+import org.apache.logging.log4j.core.config.Configuration;
+import org.apache.logging.log4j.spi.LoggerContext;
+import org.junit.Test;
+
+import java.io.File;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Test configuration from XML.
+ */
+public class XmlConfigurationFactoryTest {
+
+ @Test
+ public void testXML() throws Exception {
+ XmlConfigurationFactory.configure("target/test-classes/log4j1-file.xml");
+ Logger logger = LogManager.getLogger("test");
+ logger.debug("This is a test of the root logger");
+ File file = new File("target/temp.A1");
+ assertTrue("File A1 was not created", file.exists());
+ assertTrue("File A1 is empty", file.length() > 0);
+ file = new File("target/temp.A2");
+ assertTrue("File A2 was not created", file.exists());
+ assertTrue("File A2 is empty", file.length() > 0);
+ }
+
+ @Test
+ public void testListAppender() {
+ XmlConfigurationFactory.configure("target/test-classes/log4j1-list.xml");
+ Logger logger = LogManager.getLogger("test");
+ logger.debug("This is a test of the root logger");
+ LoggerContext loggerContext = org.apache.logging.log4j.LogManager.getContext(false);
+ Configuration configuration = ((org.apache.logging.log4j.core.LoggerContext) loggerContext).getConfiguration();
+ Map<String, Appender> appenders = configuration.getAppenders();
+ ListAppender eventAppender = null;
+ ListAppender messageAppender = null;
+ for (Map.Entry<String, Appender> 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("No Event Appender", eventAppender);
+ assertNotNull("No Message Appender", messageAppender);
+ List<LoggingEvent> events = eventAppender.getEvents();
+ assertTrue("No events", events != null && events.size() > 0);
+ List<String> messages = messageAppender.getMessages();
+ assertTrue("No messages", messages != null && messages.size() > 0);
+ }
+
+}
diff --git a/log4j-1.2-api/src/src/test/java/org/apache/log4j/layout/Log4j1XmlLayoutTest.java b/log4j-1.2-api/src/src/test/java/org/apache/log4j/layout/Log4j1XmlLayoutTest.java
new file mode 100644
index 0000000..28579fe
--- /dev/null
+++ b/log4j-1.2-api/src/src/test/java/org/apache/log4j/layout/Log4j1XmlLayoutTest.java
@@ -0,0 +1,88 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.log4j.layout;
+
+import static org.junit.Assert.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.junit.ThreadContextRule;
+import org.apache.logging.log4j.message.SimpleMessage;
+import org.apache.logging.log4j.util.StringMap;
+import org.junit.Rule;
+import org.junit.Test;
+
+public class Log4j1XmlLayoutTest {
+
+ @Rule
+ public ThreadContextRule threadContextRule = new ThreadContextRule();
+
+ @Test
+ public void testWithoutThrown() {
+ final Log4j1XmlLayout layout = Log4j1XmlLayout.createLayout(false, true);
+
+ final Log4jLogEvent event = Log4jLogEvent.newBuilder()
+ .setLoggerName("a.B")
+ .setLevel(Level.INFO)
+ .setMessage(new SimpleMessage("Hello, World"))
+ .setTimeMillis(System.currentTimeMillis() + 17)
+ .build();
+
+ final String result = layout.toSerializable(event);
+
+ final String expected =
+ "<log4j:event logger=\"a.B\" timestamp=\"" + event.getTimeMillis() + "\" level=\"INFO\" thread=\"main\">\r\n" +
+ "<log4j:message><![CDATA[Hello, World]]></log4j:message>\r\n" +
+ "</log4j:event>\r\n\r\n";
+
+ assertEquals(expected, result);
+ }
+
+ @Test
+ public void testWithPropertiesAndLocationInfo() {
+ final Log4j1XmlLayout layout = Log4j1XmlLayout.createLayout(true, true);
+
+ final StringMap contextMap = ContextDataFactory.createContextData(2);
+ contextMap.putValue("key1", "value1");
+ contextMap.putValue("key2", "value2");
+ final Log4jLogEvent event = Log4jLogEvent.newBuilder()
+ .setLoggerName("a.B")
+ .setLevel(Level.INFO)
+ .setMessage(new SimpleMessage("Hello, World"))
+ .setTimeMillis(System.currentTimeMillis() + 17)
+ .setIncludeLocation(true)
+ .setSource(new StackTraceElement("pack.MyClass", "myMethod", "MyClass.java", 17))
+ .setContextData(contextMap)
+ .build();
+
+ final String result = layout.toSerializable(event);
+
+ final String expected =
+ "<log4j:event logger=\"a.B\" timestamp=\"" + event.getTimeMillis() + "\" level=\"INFO\" thread=\"main\">\r\n" +
+ "<log4j:message><![CDATA[Hello, World]]></log4j:message>\r\n" +
+ "<log4j:locationInfo class=\"pack.MyClass\" method=\"myMethod\" file=\"MyClass.java\" line=\"17\"/>\r\n" +
+ "<log4j:properties>\r\n" +
+ "<log4j:data name=\"key1\" value=\"value1\"/>\r\n" +
+ "<log4j:data name=\"key2\" value=\"value2\"/>\r\n" +
+ "</log4j:properties>\r\n"+
+ "</log4j:event>\r\n\r\n";
+
+ assertEquals(expected, result);
+ }
+
+}
diff --git a/log4j-1.2-api/src/src/test/java/org/apache/log4j/pattern/Log4j1MdcPatternConverterTest.java b/log4j-1.2-api/src/src/test/java/org/apache/log4j/pattern/Log4j1MdcPatternConverterTest.java
new file mode 100644
index 0000000..c1d5b83
--- /dev/null
+++ b/log4j-1.2-api/src/src/test/java/org/apache/log4j/pattern/Log4j1MdcPatternConverterTest.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.pattern;
+
+import static org.junit.Assert.assertEquals;
+
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.impl.ContextDataFactory;
+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;
+
+public class Log4j1MdcPatternConverterTest {
+
+ @Test
+ public void testConverter0() {
+ final StringMap contextMap = ContextDataFactory.createContextData(0);
+ final String expected = "{}";
+ test(contextMap, expected, null);
+ }
+
+ @Test
+ public void testConverter1() {
+ final StringMap contextMap = ContextDataFactory.createContextData(1);
+ contextMap.putValue("key1", "value1");
+ final String expected = "{{key1,value1}}";
+ test(contextMap, expected, null);
+ }
+
+ @Test
+ public void testConverter2() {
+ final StringMap contextMap = ContextDataFactory.createContextData(2);
+ contextMap.putValue("key1", "value1");
+ contextMap.putValue("key2", "value2");
+ final String expected = "{{key1,value1}{key2,value2}}";
+ test(contextMap, expected, null);
+ }
+
+ @Test
+ public void testConverterWithKey() {
+ final StringMap contextMap = ContextDataFactory.createContextData(2);
+ contextMap.putValue("key1", "value1");
+ contextMap.putValue("key2", "value2");
+ final String expected = "value1";
+ test(contextMap, expected, new String[] {"key1"});
+ }
+
+ private void test(final StringMap contextMap, final String expected, final String[] options) {
+ final LogEvent event = Log4jLogEvent.newBuilder()
+ .setLoggerName("MyLogger")
+ .setLevel(Level.DEBUG)
+ .setMessage(new SimpleMessage("Hello"))
+ .setContextData(contextMap)
+ .build();
+ final StringBuilder sb = new StringBuilder();
+ final Log4j1MdcPatternConverter converter = Log4j1MdcPatternConverter.newInstance(options);
+ converter.format(event, sb);
+ assertEquals(expected, sb.toString());
+ }
+
+}
+
diff --git a/log4j-1.2-api/src/src/test/java/org/apache/log4j/pattern/Log4j1NdcPatternConverterTest.java b/log4j-1.2-api/src/src/test/java/org/apache/log4j/pattern/Log4j1NdcPatternConverterTest.java
new file mode 100644
index 0000000..2f0b80f
--- /dev/null
+++ b/log4j-1.2-api/src/src/test/java/org/apache/log4j/pattern/Log4j1NdcPatternConverterTest.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.pattern;
+
+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 {
+
+ @Rule
+ public final ThreadContextStackRule threadContextRule = new ThreadContextStackRule();
+
+ @Test
+ public void testEmpty() {
+ testConverter("");
+ }
+
+ @Test
+ public void test1() {
+ ThreadContext.push("foo");
+ testConverter("foo");
+ }
+
+ @Test
+ public void test2() {
+ ThreadContext.push("foo");
+ ThreadContext.push("bar");
+ testConverter("foo bar");
+ }
+
+ @Test
+ public void test3() {
+ ThreadContext.push("foo");
+ ThreadContext.push("bar");
+ ThreadContext.push("baz");
+ testConverter("foo bar baz");
+ }
+
+ private void testConverter(final String expected) {
+ final Log4j1NdcPatternConverter converter = Log4j1NdcPatternConverter.newInstance(null);
+ final LogEvent event = Log4jLogEvent.newBuilder()
+ .setLoggerName("MyLogger")
+ .setLevel(Level.DEBUG)
+ .setMessage(new SimpleMessage("Hello"))
+ .build();
+ final StringBuilder sb = new StringBuilder();
+ converter.format(event, sb);
+ assertEquals(expected, sb.toString());
+ }
+
+}
+
diff --git a/log4j-1.2-api/src/src/test/java/org/apache/log4j/util/SerializationTestHelper.java b/log4j-1.2-api/src/src/test/java/org/apache/log4j/util/SerializationTestHelper.java
new file mode 100644
index 0000000..0576463
--- /dev/null
+++ b/log4j-1.2-api/src/src/test/java/org/apache/log4j/util/SerializationTestHelper.java
@@ -0,0 +1,148 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.log4j.util;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+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() {
+ }
+
+ /**
+ * Creates a clone by serializing object and
+ * deserializing byte stream.
+ *
+ * @param obj object to serialize and deserialize.
+ * @return clone
+ * @throws IOException on IO error.
+ * @throws ClassNotFoundException if class not found.
+ */
+ public static Object serializeClone(final Object obj)
+ throws IOException, ClassNotFoundException {
+ final ByteArrayOutputStream memOut = new ByteArrayOutputStream();
+ try (final ObjectOutputStream objOut = new ObjectOutputStream(memOut)) {
+ objOut.writeObject(obj);
+ }
+
+ final ByteArrayInputStream src = new ByteArrayInputStream(memOut.toByteArray());
+ final ObjectInputStream objIs = new ObjectInputStream(src);
+
+ return objIs.readObject();
+ }
+
+ /**
+ * Deserializes a specified file.
+ *
+ * @param witness serialization file, may not be null.
+ * @return deserialized object.
+ * @throws Exception thrown on IO or deserialization exception.
+ */
+ public static Object deserializeStream(final String witness) throws Exception {
+ try (final ObjectInputStream objIs = new ObjectInputStream(new FileInputStream(witness))) {
+ return objIs.readObject();
+ }
+ }
+
+ /**
+ * Checks the serialization of an object against an file
+ * containing the expected serialization.
+ *
+ * @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 ByteArrayOutputStream memOut = new ByteArrayOutputStream();
+ try (final ObjectOutputStream objOut = new ObjectOutputStream(memOut)) {
+ objOut.writeObject(obj);
+ }
+
+ assertStreamEquals(witness, memOut.toByteArray(), skip, endCompare);
+ }
+
+ /**
+ * 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 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 File witnessFile = new File(witness);
+
+ if (witnessFile.exists()) {
+ int skipIndex = 0;
+ final byte[] expected = FileUtils.readFileToByteArray(witnessFile);
+ final int bytesRead = expected.length;
+
+ if (bytesRead < endCompare) {
+ assertEquals(bytesRead, actual.length);
+ }
+
+ int endScan = actual.length;
+
+ if (endScan > endCompare) {
+ endScan = endCompare;
+ }
+
+ 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 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);
+ }
+ }
+}
+
diff --git a/log4j-1.2-api/src/src/test/resources/L7D_en_US.properties b/log4j-1.2-api/src/src/test/resources/L7D_en_US.properties
new file mode 100644
index 0000000..c3c2802
--- /dev/null
+++ b/log4j-1.2-api/src/src/test/resources/L7D_en_US.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.
+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/src/test/resources/L7D_fr.properties b/log4j-1.2-api/src/src/test/resources/L7D_fr.properties
new file mode 100644
index 0000000..25b878a
--- /dev/null
+++ b/log4j-1.2-api/src/src/test/resources/L7D_fr.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.
+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/src/test/resources/L7D_fr_CH.properties b/log4j-1.2-api/src/src/test/resources/L7D_fr_CH.properties
new file mode 100644
index 0000000..ba9b1ff
--- /dev/null
+++ b/log4j-1.2-api/src/src/test/resources/L7D_fr_CH.properties
@@ -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.
+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/src/test/resources/config-1.2/hadoop/hadoop-common-project/hadoop-auth-examples/src/main/resources/log4j.properties b/log4j-1.2-api/src/src/test/resources/config-1.2/hadoop/hadoop-common-project/hadoop-auth-examples/src/main/resources/log4j.properties
new file mode 100644
index 0000000..5fa4020
--- /dev/null
+++ b/log4j-1.2-api/src/src/test/resources/config-1.2/hadoop/hadoop-common-project/hadoop-auth-examples/src/main/resources/log4j.properties
@@ -0,0 +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
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES 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.
+#
+log4j.appender.test=org.apache.log4j.ConsoleAppender
+log4j.appender.test.Target=System.out
+log4j.appender.test.layout=org.apache.log4j.PatternLayout
+log4j.appender.test.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n
+
+log4j.logger.org.apache.hadoop.security.authentication=DEBUG, test
diff --git a/log4j-1.2-api/src/src/test/resources/config-1.2/hadoop/hadoop-common-project/hadoop-common/src/main/conf/log4j.properties b/log4j-1.2-api/src/src/test/resources/config-1.2/hadoop/hadoop-common-project/hadoop-common/src/main/conf/log4j.properties
new file mode 100644
index 0000000..b08514c
--- /dev/null
+++ b/log4j-1.2-api/src/src/test/resources/config-1.2/hadoop/hadoop-common-project/hadoop-common/src/main/conf/log4j.properties
@@ -0,0 +1,323 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Define some default values that can be overridden by system properties
+hadoop.root.logger=INFO,console
+hadoop.log.dir=target
+hadoop.log.file=hadoop.log
+
+# Define the root logger to the system property "hadoop.root.logger".
+log4j.rootLogger=${hadoop.root.logger}, EventCounter
+
+# Logging Threshold
+log4j.threshold=ALL
+
+# Null Appender
+log4j.appender.NullAppender=org.apache.log4j.varia.NullAppender
+
+#
+# Rolling File Appender - cap space usage at 5gb.
+#
+hadoop.log.maxfilesize=256MB
+hadoop.log.maxbackupindex=20
+log4j.appender.RFA=org.apache.log4j.RollingFileAppender
+log4j.appender.RFA.File=${hadoop.log.dir}/${hadoop.log.file}
+
+log4j.appender.RFA.MaxFileSize=${hadoop.log.maxfilesize}
+log4j.appender.RFA.MaxBackupIndex=${hadoop.log.maxbackupindex}
+
+log4j.appender.RFA.layout=org.apache.log4j.PatternLayout
+
+# Pattern format: Date LogLevel LoggerName LogMessage
+log4j.appender.RFA.layout.ConversionPattern=%d{ISO8601} %p %c: %m%n
+# Debugging Pattern format
+#log4j.appender.RFA.layout.ConversionPattern=%d{ISO8601} %-5p %c{2} (%F:%M(%L)) - %m%n
+
+
+#
+# Daily Rolling File Appender
+#
+
+log4j.appender.DRFA=org.apache.log4j.DailyRollingFileAppender
+log4j.appender.DRFA.File=${hadoop.log.dir}/${hadoop.log.file}
+
+# Rollover at midnight
+log4j.appender.DRFA.DatePattern=.yyyy-MM-dd
+
+log4j.appender.DRFA.layout=org.apache.log4j.PatternLayout
+
+# Pattern format: Date LogLevel LoggerName LogMessage
+log4j.appender.DRFA.layout.ConversionPattern=%d{ISO8601} %p %c: %m%n
+# Debugging Pattern format
+#log4j.appender.DRFA.layout.ConversionPattern=%d{ISO8601} %-5p %c{2} (%F:%M(%L)) - %m%n
+
+
+#
+# console
+# Add "console" to rootlogger above if you want to use this
+#
+
+log4j.appender.console=org.apache.log4j.ConsoleAppender
+log4j.appender.console.target=System.err
+log4j.appender.console.layout=org.apache.log4j.PatternLayout
+log4j.appender.console.layout.ConversionPattern=%d{ISO8601} %p %c{2}: %m%n
+
+#
+# TaskLog Appender
+#
+
+#Default values
+hadoop.tasklog.taskid=null
+hadoop.tasklog.iscleanup=false
+hadoop.tasklog.noKeepSplits=4
+hadoop.tasklog.totalLogFileSize=100
+hadoop.tasklog.purgeLogSplits=true
+hadoop.tasklog.logsRetainHours=12
+
+log4j.appender.TLA=org.apache.hadoop.mapred.TaskLogAppender
+log4j.appender.TLA.taskId=${hadoop.tasklog.taskid}
+log4j.appender.TLA.isCleanup=${hadoop.tasklog.iscleanup}
+log4j.appender.TLA.totalLogFileSize=${hadoop.tasklog.totalLogFileSize}
+
+log4j.appender.TLA.layout=org.apache.log4j.PatternLayout
+log4j.appender.TLA.layout.ConversionPattern=%d{ISO8601} %p %c: %m%n
+
+#
+# HDFS block state change log from block manager
+#
+# Uncomment the following to log normal block state change
+# messages from BlockManager in NameNode.
+#log4j.logger.BlockStateChange=DEBUG
+
+#
+#Security appender
+#
+hadoop.security.logger=INFO,NullAppender
+hadoop.security.log.maxfilesize=256MB
+hadoop.security.log.maxbackupindex=20
+log4j.category.SecurityLogger=${hadoop.security.logger}
+hadoop.security.log.file=SecurityAuth-${user.name}.audit
+log4j.appender.RFAS=org.apache.log4j.RollingFileAppender
+log4j.appender.RFAS.File=${hadoop.log.dir}/${hadoop.security.log.file}
+log4j.appender.RFAS.layout=org.apache.log4j.PatternLayout
+log4j.appender.RFAS.layout.ConversionPattern=%d{ISO8601} %p %c: %m%n
+log4j.appender.RFAS.MaxFileSize=${hadoop.security.log.maxfilesize}
+log4j.appender.RFAS.MaxBackupIndex=${hadoop.security.log.maxbackupindex}
+
+#
+# Daily Rolling Security appender
+#
+log4j.appender.DRFAS=org.apache.log4j.DailyRollingFileAppender
+log4j.appender.DRFAS.File=${hadoop.log.dir}/${hadoop.security.log.file}
+log4j.appender.DRFAS.layout=org.apache.log4j.PatternLayout
+log4j.appender.DRFAS.layout.ConversionPattern=%d{ISO8601} %p %c: %m%n
+log4j.appender.DRFAS.DatePattern=.yyyy-MM-dd
+
+#
+# hadoop configuration logging
+#
+
+# Uncomment the following line to turn off configuration deprecation warnings.
+# log4j.logger.org.apache.hadoop.conf.Configuration.deprecation=WARN
+
+#
+# hdfs audit logging
+#
+hdfs.audit.logger=INFO,NullAppender
+hdfs.audit.log.maxfilesize=256MB
+hdfs.audit.log.maxbackupindex=20
+log4j.logger.org.apache.hadoop.hdfs.server.namenode.FSNamesystem.audit=${hdfs.audit.logger}
+log4j.additivity.org.apache.hadoop.hdfs.server.namenode.FSNamesystem.audit=false
+log4j.appender.RFAAUDIT=org.apache.log4j.RollingFileAppender
+log4j.appender.RFAAUDIT.File=${hadoop.log.dir}/hdfs-audit.log
+log4j.appender.RFAAUDIT.layout=org.apache.log4j.PatternLayout
+log4j.appender.RFAAUDIT.layout.ConversionPattern=%d{ISO8601} %p %c{2}: %m%n
+log4j.appender.RFAAUDIT.MaxFileSize=${hdfs.audit.log.maxfilesize}
+log4j.appender.RFAAUDIT.MaxBackupIndex=${hdfs.audit.log.maxbackupindex}
+
+#
+# NameNode metrics logging.
+# The default is to retain two namenode-metrics.log files up to 64MB each.
+#
+namenode.metrics.logger=INFO,NullAppender
+log4j.logger.NameNodeMetricsLog=${namenode.metrics.logger}
+log4j.additivity.NameNodeMetricsLog=false
+log4j.appender.NNMETRICSRFA=org.apache.log4j.RollingFileAppender
+log4j.appender.NNMETRICSRFA.File=${hadoop.log.dir}/namenode-metrics.log
+log4j.appender.NNMETRICSRFA.layout=org.apache.log4j.PatternLayout
+log4j.appender.NNMETRICSRFA.layout.ConversionPattern=%d{ISO8601} %m%n
+log4j.appender.NNMETRICSRFA.MaxBackupIndex=1
+log4j.appender.NNMETRICSRFA.MaxFileSize=64MB
+
+#
+# DataNode metrics logging.
+# The default is to retain two datanode-metrics.log files up to 64MB each.
+#
+datanode.metrics.logger=INFO,NullAppender
+log4j.logger.DataNodeMetricsLog=${datanode.metrics.logger}
+log4j.additivity.DataNodeMetricsLog=false
+log4j.appender.DNMETRICSRFA=org.apache.log4j.RollingFileAppender
+log4j.appender.DNMETRICSRFA.File=${hadoop.log.dir}/datanode-metrics.log
+log4j.appender.DNMETRICSRFA.layout=org.apache.log4j.PatternLayout
+log4j.appender.DNMETRICSRFA.layout.ConversionPattern=%d{ISO8601} %m%n
+log4j.appender.DNMETRICSRFA.MaxBackupIndex=1
+log4j.appender.DNMETRICSRFA.MaxFileSize=64MB
+
+#
+# mapred audit logging
+#
+mapred.audit.logger=INFO,NullAppender
+mapred.audit.log.maxfilesize=256MB
+mapred.audit.log.maxbackupindex=20
+log4j.logger.org.apache.hadoop.mapred.AuditLogger=${mapred.audit.logger}
+log4j.additivity.org.apache.hadoop.mapred.AuditLogger=false
+log4j.appender.MRAUDIT=org.apache.log4j.RollingFileAppender
+log4j.appender.MRAUDIT.File=${hadoop.log.dir}/mapred-audit.log
+log4j.appender.MRAUDIT.layout=org.apache.log4j.PatternLayout
+log4j.appender.MRAUDIT.layout.ConversionPattern=%d{ISO8601} %p %c{2}: %m%n
+log4j.appender.MRAUDIT.MaxFileSize=${mapred.audit.log.maxfilesize}
+log4j.appender.MRAUDIT.MaxBackupIndex=${mapred.audit.log.maxbackupindex}
+
+# Custom Logging levels
+
+#log4j.logger.org.apache.hadoop.mapred.JobTracker=DEBUG
+#log4j.logger.org.apache.hadoop.mapred.TaskTracker=DEBUG
+#log4j.logger.org.apache.hadoop.hdfs.server.namenode.FSNamesystem.audit=DEBUG
+
+# Jets3t library
+log4j.logger.org.jets3t.service.impl.rest.httpclient.RestS3Service=ERROR
+
+# AWS SDK & S3A FileSystem
+log4j.logger.com.amazonaws=ERROR
+log4j.logger.com.amazonaws.http.AmazonHttpClient=ERROR
+log4j.logger.org.apache.hadoop.fs.s3a.S3AFileSystem=WARN
+
+#
+# Event Counter Appender
+# Sends counts of logging messages at different severity levels to Hadoop Metrics.
+#
+log4j.appender.EventCounter=org.apache.hadoop.log.metrics.EventCounter
+
+#
+# Job Summary Appender
+#
+# Use following logger to send summary to separate file defined by
+# hadoop.mapreduce.jobsummary.log.file :
+# hadoop.mapreduce.jobsummary.logger=INFO,JSA
+#
+hadoop.mapreduce.jobsummary.logger=${hadoop.root.logger}
+hadoop.mapreduce.jobsummary.log.file=hadoop-mapreduce.jobsummary.log
+hadoop.mapreduce.jobsummary.log.maxfilesize=256MB
+hadoop.mapreduce.jobsummary.log.maxbackupindex=20
+log4j.appender.JSA=org.apache.log4j.RollingFileAppender
+log4j.appender.JSA.File=${hadoop.log.dir}/${hadoop.mapreduce.jobsummary.log.file}
+log4j.appender.JSA.MaxFileSize=${hadoop.mapreduce.jobsummary.log.maxfilesize}
+log4j.appender.JSA.MaxBackupIndex=${hadoop.mapreduce.jobsummary.log.maxbackupindex}
+log4j.appender.JSA.layout=org.apache.log4j.PatternLayout
+log4j.appender.JSA.layout.ConversionPattern=%d{ISO8601} %p %c{2}: %m%n
+log4j.logger.org.apache.hadoop.mapred.JobInProgress$JobSummary=${hadoop.mapreduce.jobsummary.logger}
+log4j.additivity.org.apache.hadoop.mapred.JobInProgress$JobSummary=false
+
+#
+# shuffle connection log from shuffleHandler
+# Uncomment the following line to enable logging of shuffle connections
+# log4j.logger.org.apache.hadoop.mapred.ShuffleHandler.audit=DEBUG
+
+#
+# Yarn ResourceManager Application Summary Log
+#
+# Set the ResourceManager summary log filename
+yarn.server.resourcemanager.appsummary.log.file=rm-appsummary.log
+# Set the ResourceManager summary log level and appender
+yarn.server.resourcemanager.appsummary.logger=${hadoop.root.logger}
+#yarn.server.resourcemanager.appsummary.logger=INFO,RMSUMMARY
+
+# To enable AppSummaryLogging for the RM,
+# set yarn.server.resourcemanager.appsummary.logger to
+# <LEVEL>,RMSUMMARY in hadoop-env.sh
+
+# Appender for ResourceManager Application Summary Log
+# Requires the following properties to be set
+# - hadoop.log.dir (Hadoop Log directory)
+# - yarn.server.resourcemanager.appsummary.log.file (resource manager app summary log filename)
+# - yarn.server.resourcemanager.appsummary.logger (resource manager app summary log level and appender)
+
+log4j.logger.org.apache.hadoop.yarn.server.resourcemanager.RMAppManager$ApplicationSummary=${yarn.server.resourcemanager.appsummary.logger}
+log4j.additivity.org.apache.hadoop.yarn.server.resourcemanager.RMAppManager$ApplicationSummary=false
+log4j.appender.RMSUMMARY=org.apache.log4j.RollingFileAppender
+log4j.appender.RMSUMMARY.File=${hadoop.log.dir}/${yarn.server.resourcemanager.appsummary.log.file}
+log4j.appender.RMSUMMARY.MaxFileSize=256MB
+log4j.appender.RMSUMMARY.MaxBackupIndex=20
+log4j.appender.RMSUMMARY.layout=org.apache.log4j.PatternLayout
+log4j.appender.RMSUMMARY.layout.ConversionPattern=%d{ISO8601} %p %c{2}: %m%n
+
+# HS audit log configs
+#mapreduce.hs.audit.logger=INFO,HSAUDIT
+#log4j.logger.org.apache.hadoop.mapreduce.v2.hs.HSAuditLogger=${mapreduce.hs.audit.logger}
+#log4j.additivity.org.apache.hadoop.mapreduce.v2.hs.HSAuditLogger=false
+#log4j.appender.HSAUDIT=org.apache.log4j.DailyRollingFileAppender
+#log4j.appender.HSAUDIT.File=${hadoop.log.dir}/hs-audit.log
+#log4j.appender.HSAUDIT.layout=org.apache.log4j.PatternLayout
+#log4j.appender.HSAUDIT.layout.ConversionPattern=%d{ISO8601} %p %c{2}: %m%n
+#log4j.appender.HSAUDIT.DatePattern=.yyyy-MM-dd
+
+# Http Server Request Logs
+#log4j.logger.http.requests.namenode=INFO,namenoderequestlog
+#log4j.appender.namenoderequestlog=org.apache.hadoop.http.HttpRequestLogAppender
+#log4j.appender.namenoderequestlog.Filename=${hadoop.log.dir}/jetty-namenode-yyyy_mm_dd.log
+#log4j.appender.namenoderequestlog.RetainDays=3
+
+#log4j.logger.http.requests.datanode=INFO,datanoderequestlog
+#log4j.appender.datanoderequestlog=org.apache.hadoop.http.HttpRequestLogAppender
+#log4j.appender.datanoderequestlog.Filename=${hadoop.log.dir}/jetty-datanode-yyyy_mm_dd.log
+#log4j.appender.datanoderequestlog.RetainDays=3
+
+#log4j.logger.http.requests.resourcemanager=INFO,resourcemanagerrequestlog
+#log4j.appender.resourcemanagerrequestlog=org.apache.hadoop.http.HttpRequestLogAppender
+#log4j.appender.resourcemanagerrequestlog.Filename=${hadoop.log.dir}/jetty-resourcemanager-yyyy_mm_dd.log
+#log4j.appender.resourcemanagerrequestlog.RetainDays=3
+
+#log4j.logger.http.requests.jobhistory=INFO,jobhistoryrequestlog
+#log4j.appender.jobhistoryrequestlog=org.apache.hadoop.http.HttpRequestLogAppender
+#log4j.appender.jobhistoryrequestlog.Filename=${hadoop.log.dir}/jetty-jobhistory-yyyy_mm_dd.log
+#log4j.appender.jobhistoryrequestlog.RetainDays=3
+
+#log4j.logger.http.requests.nodemanager=INFO,nodemanagerrequestlog
+#log4j.appender.nodemanagerrequestlog=org.apache.hadoop.http.HttpRequestLogAppender
+#log4j.appender.nodemanagerrequestlog.Filename=${hadoop.log.dir}/jetty-nodemanager-yyyy_mm_dd.log
+#log4j.appender.nodemanagerrequestlog.RetainDays=3
+
+
+# WebHdfs request log on datanodes
+# Specify -Ddatanode.webhdfs.logger=INFO,HTTPDRFA on datanode startup to
+# direct the log to a separate file.
+#datanode.webhdfs.logger=INFO,console
+#log4j.logger.datanode.webhdfs=${datanode.webhdfs.logger}
+#log4j.appender.HTTPDRFA=org.apache.log4j.DailyRollingFileAppender
+#log4j.appender.HTTPDRFA.File=${hadoop.log.dir}/hadoop-datanode-webhdfs.log
+#log4j.appender.HTTPDRFA.layout=org.apache.log4j.PatternLayout
+#log4j.appender.HTTPDRFA.layout.ConversionPattern=%d{ISO8601} %m%n
+#log4j.appender.HTTPDRFA.DatePattern=.yyyy-MM-dd
+
+
+# Appender for viewing information for errors and warnings
+yarn.ewma.cleanupInterval=300
+yarn.ewma.messageAgeLimitSeconds=86400
+yarn.ewma.maxUniqueMessages=250
+log4j.appender.EWMA=org.apache.hadoop.yarn.util.Log4jWarningErrorMetricsAppender
+log4j.appender.EWMA.cleanupInterval=${yarn.ewma.cleanupInterval}
+log4j.appender.EWMA.messageAgeLimitSeconds=${yarn.ewma.messageAgeLimitSeconds}
+log4j.appender.EWMA.maxUniqueMessages=${yarn.ewma.maxUniqueMessages}
diff --git a/log4j-1.2-api/src/src/test/resources/config-1.2/hadoop/hadoop-common-project/hadoop-common/src/test/resources/log4j.properties b/log4j-1.2-api/src/src/test/resources/config-1.2/hadoop/hadoop-common-project/hadoop-common/src/test/resources/log4j.properties
new file mode 100644
index 0000000..ced0687
--- /dev/null
+++ b/log4j-1.2-api/src/src/test/resources/config-1.2/hadoop/hadoop-common-project/hadoop-common/src/test/resources/log4j.properties
@@ -0,0 +1,18 @@
+# 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
+log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
+log4j.appender.stdout.layout.ConversionPattern=%d{ISO8601} %-5p %c{2} (%F:%M(%L)) - %m%n
diff --git a/log4j-1.2-api/src/src/test/resources/config-1.2/hadoop/hadoop-common-project/hadoop-kms/src/test/resources/log4j.properties b/log4j-1.2-api/src/src/test/resources/config-1.2/hadoop/hadoop-common-project/hadoop-kms/src/test/resources/log4j.properties
new file mode 100644
index 0000000..b347d27
--- /dev/null
+++ b/log4j-1.2-api/src/src/test/resources/config-1.2/hadoop/hadoop-common-project/hadoop-kms/src/test/resources/log4j.properties
@@ -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.
+#
+
+# STDOUT Appender
+log4j.appender.stdout=org.apache.log4j.ConsoleAppender
+log4j.appender.stdout.Target=System.out
+log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
+log4j.appender.stdout.layout.ConversionPattern=%d{ISO8601} %-5p %c{1} - %m%n
+
+log4j.rootLogger=INFO, stdout
+log4j.logger.org.apache.hadoop.conf=ERROR
+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
diff --git a/log4j-1.2-api/src/src/test/resources/config-1.2/hadoop/hadoop-common-project/hadoop-minikdc/src/main/resources/log4j.properties b/log4j-1.2-api/src/src/test/resources/config-1.2/hadoop/hadoop-common-project/hadoop-minikdc/src/main/resources/log4j.properties
new file mode 100644
index 0000000..9efd671
--- /dev/null
+++ b/log4j-1.2-api/src/src/test/resources/config-1.2/hadoop/hadoop-common-project/hadoop-minikdc/src/main/resources/log4j.properties
@@ -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.
+#
+
+# STDOUT Appender
+log4j.appender.stdout=org.apache.log4j.ConsoleAppender
+log4j.appender.stdout.Target=System.err
+log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
+log4j.appender.stdout.layout.ConversionPattern=%d{ISO8601} %-5p %c{1} - %m%n
+
+log4j.rootLogger=INFO, stdout
+
+# Switching off most of Apache DS logqing which is QUITE verbose
+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
diff --git a/log4j-1.2-api/src/src/test/resources/config-1.2/hadoop/hadoop-common-project/hadoop-nfs/src/test/resources/log4j.properties b/log4j-1.2-api/src/src/test/resources/config-1.2/hadoop/hadoop-common-project/hadoop-nfs/src/test/resources/log4j.properties
new file mode 100644
index 0000000..ced0687
--- /dev/null
+++ b/log4j-1.2-api/src/src/test/resources/config-1.2/hadoop/hadoop-common-project/hadoop-nfs/src/test/resources/log4j.properties
@@ -0,0 +1,18 @@
+# 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
+log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
+log4j.appender.stdout.layout.ConversionPattern=%d{ISO8601} %-5p %c{2} (%F:%M(%L)) - %m%n
diff --git a/log4j-1.2-api/src/src/test/resources/config-1.2/hadoop/hadoop-hdfs-project/hadoop-hdfs-client/src/test/resources/log4j.properties b/log4j-1.2-api/src/src/test/resources/config-1.2/hadoop/hadoop-hdfs-project/hadoop-hdfs-client/src/test/resources/log4j.properties
new file mode 100644
index 0000000..7378846
--- /dev/null
+++ b/log4j-1.2-api/src/src/test/resources/config-1.2/hadoop/hadoop-hdfs-project/hadoop-hdfs-client/src/test/resources/log4j.properties
@@ -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.
+#
+# log4j configuration used during build and unit tests
+
+log4j.rootLogger=info,stdout
+log4j.threshold=ALL
+log4j.appender.stdout=org.apache.log4j.ConsoleAppender
+log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
+log4j.appender.stdout.layout.ConversionPattern=%d{ISO8601} [%t] %-5p %c{2} (%F:%M(%L)) - %m%n
+
+#
+# NameNode metrics logging.
+# The default is to retain two namenode-metrics.log files up to 64MB each.
+#
+log4j.logger.NameNodeMetricsLog=INFO,NNMETRICSRFA
+log4j.additivity.NameNodeMetricsLog=false
+log4j.appender.NNMETRICSRFA=org.apache.log4j.RollingFileAppender
+log4j.appender.NNMETRICSRFA.File=${hadoop.log.dir}/namenode-metrics.log
+log4j.appender.NNMETRICSRFA.layout=org.apache.log4j.PatternLayout
+log4j.appender.NNMETRICSRFA.layout.ConversionPattern=%d{ISO8601} %m%n
+log4j.appender.NNMETRICSRFA.MaxBackupIndex=1
+log4j.appender.NNMETRICSRFA.MaxFileSize=64MB
+
+#
+# DataNode metrics logging.
+# The default is to retain two datanode-metrics.log files up to 64MB each.
+#
+log4j.logger.DataNodeMetricsLog=INFO,DNMETRICSRFA
+log4j.additivity.DataNodeMetricsLog=false
+log4j.appender.DNMETRICSRFA=org.apache.log4j.RollingFileAppender
+log4j.appender.DNMETRICSRFA.File=${hadoop.log.dir}/datanode-metrics.log
+log4j.appender.DNMETRICSRFA.layout=org.apache.log4j.PatternLayout
+log4j.appender.DNMETRICSRFA.layout.ConversionPattern=%d{ISO8601} %m%n
+log4j.appender.DNMETRICSRFA.MaxBackupIndex=1
+log4j.appender.DNMETRICSRFA.MaxFileSize=64MB
diff --git a/log4j-1.2-api/src/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/src/test/resources/config-1.2/hadoop/hadoop-hdfs-project/hadoop-hdfs/src/contrib/bkjournal/src/test/resources/log4j.properties
new file mode 100644
index 0000000..52aac43
--- /dev/null
+++ b/log4j-1.2-api/src/src/test/resources/config-1.2/hadoop/hadoop-hdfs-project/hadoop-hdfs/src/contrib/bkjournal/src/test/resources/log4j.properties
@@ -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.
+#
+#
+
+#
+# Bookkeeper Journal Logging Configuration
+#
+
+# Format is "<default threshold> (, <appender>)+
+
+# DEFAULT: console appender only
+log4j.rootLogger=DEBUG, CONSOLE
+
+# Example with rolling log file
+#log4j.rootLogger=DEBUG, CONSOLE, ROLLINGFILE
+
+# Example with rolling log file and tracing
+#log4j.rootLogger=TRACE, CONSOLE, ROLLINGFILE, TRACEFILE
+
+#
+# Log INFO level and above messages to the console
+#
+log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
+log4j.appender.CONSOLE.Threshold=INFO
+log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
+log4j.appender.CONSOLE.layout.ConversionPattern=%d{ISO8601} - %-5p - [%t:%C{1}@%L] - %m%n
+
+#
+# Add ROLLINGFILE to rootLogger to get log file output
+# Log DEBUG level and above messages to a log file
+log4j.appender.ROLLINGFILE=org.apache.log4j.DailyRollingFileAppender
+log4j.appender.ROLLINGFILE.Threshold=DEBUG
+log4j.appender.ROLLINGFILE.File=hdfs-namenode.log
+log4j.appender.ROLLINGFILE.layout=org.apache.log4j.PatternLayout
+log4j.appender.ROLLINGFILE.layout.ConversionPattern=%d{ISO8601} - %-5p - [%t:%C{1}@%L] - %m%n
+
+# Max log file size of 10MB
+log4j.appender.ROLLINGFILE.MaxFileSize=10MB
diff --git a/log4j-1.2-api/src/src/test/resources/config-1.2/hadoop/hadoop-hdfs-project/hadoop-hdfs/src/test/resources/log4j.properties b/log4j-1.2-api/src/src/test/resources/config-1.2/hadoop/hadoop-hdfs-project/hadoop-hdfs/src/test/resources/log4j.properties
new file mode 100644
index 0000000..7378846
--- /dev/null
+++ b/log4j-1.2-api/src/src/test/resources/config-1.2/hadoop/hadoop-hdfs-project/hadoop-hdfs/src/test/resources/log4j.properties
@@ -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.
+#
+# log4j configuration used during build and unit tests
+
+log4j.rootLogger=info,stdout
+log4j.threshold=ALL
+log4j.appender.stdout=org.apache.log4j.ConsoleAppender
+log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
+log4j.appender.stdout.layout.ConversionPattern=%d{ISO8601} [%t] %-5p %c{2} (%F:%M(%L)) - %m%n
+
+#
+# NameNode metrics logging.
+# The default is to retain two namenode-metrics.log files up to 64MB each.
+#
+log4j.logger.NameNodeMetricsLog=INFO,NNMETRICSRFA
+log4j.additivity.NameNodeMetricsLog=false
+log4j.appender.NNMETRICSRFA=org.apache.log4j.RollingFileAppender
+log4j.appender.NNMETRICSRFA.File=${hadoop.log.dir}/namenode-metrics.log
+log4j.appender.NNMETRICSRFA.layout=org.apache.log4j.PatternLayout
+log4j.appender.NNMETRICSRFA.layout.ConversionPattern=%d{ISO8601} %m%n
+log4j.appender.NNMETRICSRFA.MaxBackupIndex=1
+log4j.appender.NNMETRICSRFA.MaxFileSize=64MB
+
+#
+# DataNode metrics logging.
+# The default is to retain two datanode-metrics.log files up to 64MB each.
+#
+log4j.logger.DataNodeMetricsLog=INFO,DNMETRICSRFA
+log4j.additivity.DataNodeMetricsLog=false
+log4j.appender.DNMETRICSRFA=org.apache.log4j.RollingFileAppender
+log4j.appender.DNMETRICSRFA.File=${hadoop.log.dir}/datanode-metrics.log
+log4j.appender.DNMETRICSRFA.layout=org.apache.log4j.PatternLayout
+log4j.appender.DNMETRICSRFA.layout.ConversionPattern=%d{ISO8601} %m%n
+log4j.appender.DNMETRICSRFA.MaxBackupIndex=1
+log4j.appender.DNMETRICSRFA.MaxFileSize=64MB
diff --git a/log4j-1.2-api/src/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/src/test/resources/config-1.2/hadoop/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-app/src/test/resources/log4j.properties
new file mode 100644
index 0000000..81a3f6a
--- /dev/null
+++ b/log4j-1.2-api/src/src/test/resources/config-1.2/hadoop/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-app/src/test/resources/log4j.properties
@@ -0,0 +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
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT 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
+log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
+log4j.appender.stdout.layout.ConversionPattern=%d{ISO8601} %-5p [%t] %c{2} (%F:%M(%L)) - %m%n
diff --git a/log4j-1.2-api/src/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/src/test/resources/config-1.2/hadoop/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-common/src/test/resources/log4j.properties
new file mode 100644
index 0000000..81a3f6a
--- /dev/null
+++ b/log4j-1.2-api/src/src/test/resources/config-1.2/hadoop/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-common/src/test/resources/log4j.properties
@@ -0,0 +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
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT 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
+log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
+log4j.appender.stdout.layout.ConversionPattern=%d{ISO8601} %-5p [%t] %c{2} (%F:%M(%L)) - %m%n
diff --git a/log4j-1.2-api/src/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/src/test/resources/config-1.2/hadoop/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/test/resources/log4j.properties
new file mode 100644
index 0000000..81a3f6a
--- /dev/null
+++ b/log4j-1.2-api/src/src/test/resources/config-1.2/hadoop/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-core/src/test/resources/log4j.properties
@@ -0,0 +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
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT 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
+log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
+log4j.appender.stdout.layout.ConversionPattern=%d{ISO8601} %-5p [%t] %c{2} (%F:%M(%L)) - %m%n
diff --git a/log4j-1.2-api/src/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/src/test/resources/config-1.2/hadoop/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-hs/src/test/resources/log4j.properties
new file mode 100644
index 0000000..81a3f6a
--- /dev/null
+++ b/log4j-1.2-api/src/src/test/resources/config-1.2/hadoop/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-hs/src/test/resources/log4j.properties
@@ -0,0 +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
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT 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
+log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
+log4j.appender.stdout.layout.ConversionPattern=%d{ISO8601} %-5p [%t] %c{2} (%F:%M(%L)) - %m%n
diff --git a/log4j-1.2-api/src/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/src/test/resources/config-1.2/hadoop/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-jobclient/src/test/resources/log4j.properties
new file mode 100644
index 0000000..81a3f6a
--- /dev/null
+++ b/log4j-1.2-api/src/src/test/resources/config-1.2/hadoop/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-jobclient/src/test/resources/log4j.properties
@@ -0,0 +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
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT 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
+log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
+log4j.appender.stdout.layout.ConversionPattern=%d{ISO8601} %-5p [%t] %c{2} (%F:%M(%L)) - %m%n
diff --git a/log4j-1.2-api/src/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/src/test/resources/config-1.2/hadoop/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-nativetask/src/test/resources/log4j.properties
new file mode 100644
index 0000000..81a3f6a
--- /dev/null
+++ b/log4j-1.2-api/src/src/test/resources/config-1.2/hadoop/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-nativetask/src/test/resources/log4j.properties
@@ -0,0 +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
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT 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
+log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
+log4j.appender.stdout.layout.ConversionPattern=%d{ISO8601} %-5p [%t] %c{2} (%F:%M(%L)) - %m%n
diff --git a/log4j-1.2-api/src/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/src/test/resources/config-1.2/hadoop/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-shuffle/src/test/resources/log4j.properties
new file mode 100644
index 0000000..81a3f6a
--- /dev/null
+++ b/log4j-1.2-api/src/src/test/resources/config-1.2/hadoop/hadoop-mapreduce-project/hadoop-mapreduce-client/hadoop-mapreduce-client-shuffle/src/test/resources/log4j.properties
@@ -0,0 +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
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT 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
+log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
+log4j.appender.stdout.layout.ConversionPattern=%d{ISO8601} %-5p [%t] %c{2} (%F:%M(%L)) - %m%n
diff --git a/log4j-1.2-api/src/src/test/resources/config-1.2/hadoop/hadoop-tools/hadoop-aws/src/test/resources/log4j.properties b/log4j-1.2-api/src/src/test/resources/config-1.2/hadoop/hadoop-tools/hadoop-aws/src/test/resources/log4j.properties
new file mode 100644
index 0000000..1330ed1
--- /dev/null
+++ b/log4j-1.2-api/src/src/test/resources/config-1.2/hadoop/hadoop-tools/hadoop-aws/src/test/resources/log4j.properties
@@ -0,0 +1,23 @@
+# 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
+log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
+log4j.appender.stdout.layout.ConversionPattern=%d{ISO8601} [%t] %-5p %c{2} (%F:%M(%L)) - %m%n
+
+log4j.logger.org.apache.hadoop.util.NativeCodeLoader=ERROR
+
+# for debugging low level S3a operations, uncomment this line
+# log4j.logger.org.apache.hadoop.fs.s3a=DEBUG
diff --git a/log4j-1.2-api/src/src/test/resources/config-1.2/hadoop/hadoop-tools/hadoop-azure/src/test/resources/log4j.properties b/log4j-1.2-api/src/src/test/resources/config-1.2/hadoop/hadoop-tools/hadoop-azure/src/test/resources/log4j.properties
new file mode 100644
index 0000000..73ee3f9
--- /dev/null
+++ b/log4j-1.2-api/src/src/test/resources/config-1.2/hadoop/hadoop-tools/hadoop-azure/src/test/resources/log4j.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 configuration used during build and unit tests
+
+log4j.rootLogger=INFO,stdout
+log4j.threshold=ALL
+log4j.appender.stdout=org.apache.log4j.ConsoleAppender
+log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
+log4j.appender.stdout.layout.ConversionPattern=%d{ISO8601} %-5p [%t]: %c{2} (%F:%M(%L)) - %m%n
+
+log4j.logger.org.apache.hadoop.fs.azure.AzureFileSystemThreadPoolExecutor=DEBUG
diff --git a/log4j-1.2-api/src/src/test/resources/config-1.2/hadoop/hadoop-tools/hadoop-openstack/src/test/resources/log4j.properties b/log4j-1.2-api/src/src/test/resources/config-1.2/hadoop/hadoop-tools/hadoop-openstack/src/test/resources/log4j.properties
new file mode 100644
index 0000000..6aeb41d
--- /dev/null
+++ b/log4j-1.2-api/src/src/test/resources/config-1.2/hadoop/hadoop-tools/hadoop-openstack/src/test/resources/log4j.properties
@@ -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.
+#
+
+# 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
+log4j.appender.stdout.target=System.out
+log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
+log4j.appender.stdout.layout.ConversionPattern=%d{ISO8601} [%t] %-5p %c{2} (%F:%M(%L)) - %m%n
+#log4j.appender.stdout.layout.ConversionPattern=%d{ISO8601} [%t] %-5p %c %x - %m%n"
+#log4j.logger.org.apache.hadoop.fs.swift=DEBUG
+
+#crank back on warnings about -1 content length GETs
+log4j.logger.org.apache.commons.httpclient.HttpMethodBase=ERROR
diff --git a/log4j-1.2-api/src/src/test/resources/config-1.2/hadoop/hadoop-tools/hadoop-sls/src/main/sample-conf/log4j.properties b/log4j-1.2-api/src/src/test/resources/config-1.2/hadoop/hadoop-tools/hadoop-sls/src/main/sample-conf/log4j.properties
new file mode 100644
index 0000000..cfd405b
--- /dev/null
+++ b/log4j-1.2-api/src/src/test/resources/config-1.2/hadoop/hadoop-tools/hadoop-sls/src/main/sample-conf/log4j.properties
@@ -0,0 +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
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES 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.
+#
+log4j.appender.test=org.apache.log4j.ConsoleAppender
+log4j.appender.test.Target=System.out
+log4j.appender.test.layout=org.apache.log4j.PatternLayout
+log4j.appender.test.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n
+
+log4j.logger=NONE, test
diff --git a/log4j-1.2-api/src/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/src/test/resources/config-1.2/hadoop/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-distributedshell/src/test/resources/log4j.properties
new file mode 100644
index 0000000..81a3f6a
--- /dev/null
+++ b/log4j-1.2-api/src/src/test/resources/config-1.2/hadoop/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-applications/hadoop-yarn-applications-distributedshell/src/test/resources/log4j.properties
@@ -0,0 +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
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT 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
+log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
+log4j.appender.stdout.layout.ConversionPattern=%d{ISO8601} %-5p [%t] %c{2} (%F:%M(%L)) - %m%n
diff --git a/log4j-1.2-api/src/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/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
new file mode 100644
index 0000000..e46856e
--- /dev/null
+++ b/log4j-1.2-api/src/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
@@ -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.
+#
+
+# 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
+log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
+log4j.appender.stdout.layout.ConversionPattern=%d{ISO8601} %-5p [%t] %c{2} (%F:%M(%L)) - %m%n
diff --git a/log4j-1.2-api/src/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/src/test/resources/config-1.2/hadoop/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/test/resources/log4j.properties
new file mode 100644
index 0000000..81a3f6a
--- /dev/null
+++ b/log4j-1.2-api/src/src/test/resources/config-1.2/hadoop/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-common/src/test/resources/log4j.properties
@@ -0,0 +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
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT 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
+log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
+log4j.appender.stdout.layout.ConversionPattern=%d{ISO8601} %-5p [%t] %c{2} (%F:%M(%L)) - %m%n
diff --git a/log4j-1.2-api/src/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/src/test/resources/config-1.2/hadoop/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-registry/src/test/resources/log4j.properties
new file mode 100644
index 0000000..bed1abc
--- /dev/null
+++ b/log4j-1.2-api/src/src/test/resources/config-1.2/hadoop/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-registry/src/test/resources/log4j.properties
@@ -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.
+
+# log4j configuration used during build and unit tests
+
+log4j.rootLogger=INFO,stdout
+log4j.threshold=ALL
+log4j.appender.stdout=org.apache.log4j.ConsoleAppender
+log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
+log4j.appender.stdout.layout.ConversionPattern=%d{ISO8601} [%t] %-5p %c{2} (%F:%M(%L)) - %m%n
+
+log4j.appender.subprocess=org.apache.log4j.ConsoleAppender
+log4j.appender.subprocess.layout=org.apache.log4j.PatternLayout
+log4j.appender.subprocess.layout.ConversionPattern=[%c{1}]: %m%n
+
+# packages under test
+log4j.logger.org.apache.hadoop.yarn.registry=DEBUG
+log4j.logger.org.apache.hadoop.service=DEBUG
+
+log4j.logger.org.apache.hadoop.security.UserGroupInformation=DEBUG
+
+
+#crank back on some noise
+log4j.logger.org.apache.hadoop.util.NativeCodeLoader=ERROR
+log4j.logger.org.apache.hadoop.hdfs.server.datanode.BlockPoolSliceScanner=WARN
+log4j.logger.org.apache.hadoop.hdfs.server.blockmanagement=WARN
+log4j.logger.org.apache.hadoop.hdfs.server.namenode.FSNamesystem.audit=WARN
+log4j.logger.org.apache.hadoop.hdfs=WARN
+
+
+log4j.logger.org.apache.hadoop.yarn.server.nodemanager.containermanager.monitor=WARN
+log4j.logger.org.apache.hadoop.yarn.server.nodemanager.NodeStatusUpdaterImpl=WARN
+log4j.logger.org.apache.zookeeper=INFO
+log4j.logger.org.apache.zookeeper.ClientCnxn=DEBUG
+
+log4j.logger.org.apache.hadoop.yarn.server.resourcemanager.security=WARN
+log4j.logger.org.apache.hadoop.metrics2=ERROR
+log4j.logger.org.apache.hadoop.util.HostsFileReader=WARN
+log4j.logger.org.apache.hadoop.yarn.event.AsyncDispatcher=WARN
+log4j.logger.org.apache.hadoop.security.token.delegation=WARN
+log4j.logger.org.apache.hadoop.yarn.util.AbstractLivelinessMonitor=WARN
+log4j.logger.org.apache.hadoop.yarn.server.nodemanager.security=WARN
+log4j.logger.org.apache.hadoop.yarn.server.resourcemanager.RMNMInfo=WARN
+
+# curator noise
+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
diff --git a/log4j-1.2-api/src/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/src/test/resources/config-1.2/hadoop/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-common/src/test/resources/log4j.properties
new file mode 100644
index 0000000..81a3f6a
--- /dev/null
+++ b/log4j-1.2-api/src/src/test/resources/config-1.2/hadoop/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-common/src/test/resources/log4j.properties
@@ -0,0 +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
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT 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
+log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
+log4j.appender.stdout.layout.ConversionPattern=%d{ISO8601} %-5p [%t] %c{2} (%F:%M(%L)) - %m%n
diff --git a/log4j-1.2-api/src/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/src/test/resources/config-1.2/hadoop/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/test/resources/log4j.properties
new file mode 100644
index 0000000..81a3f6a
--- /dev/null
+++ b/log4j-1.2-api/src/src/test/resources/config-1.2/hadoop/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/test/resources/log4j.properties
@@ -0,0 +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
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT 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
+log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
+log4j.appender.stdout.layout.ConversionPattern=%d{ISO8601} %-5p [%t] %c{2} (%F:%M(%L)) - %m%n
diff --git a/log4j-1.2-api/src/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/src/test/resources/config-1.2/hadoop/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/resources/log4j.properties
new file mode 100644
index 0000000..81a3f6a
--- /dev/null
+++ b/log4j-1.2-api/src/src/test/resources/config-1.2/hadoop/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-resourcemanager/src/test/resources/log4j.properties
@@ -0,0 +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
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT 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
+log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
+log4j.appender.stdout.layout.ConversionPattern=%d{ISO8601} %-5p [%t] %c{2} (%F:%M(%L)) - %m%n
diff --git a/log4j-1.2-api/src/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/src/test/resources/config-1.2/hadoop/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-tests/src/test/resources/log4j.properties
new file mode 100644
index 0000000..c088bb7
--- /dev/null
+++ b/log4j-1.2-api/src/src/test/resources/config-1.2/hadoop/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-tests/src/test/resources/log4j.properties
@@ -0,0 +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
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT 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
+log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
+log4j.appender.stdout.layout.ConversionPattern=%d{ISO8601} %-5p [%t] %c{2} (%F:%M(%L)) - %m%n
diff --git a/log4j-1.2-api/src/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/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
new file mode 100644
index 0000000..81a3f6a
--- /dev/null
+++ b/log4j-1.2-api/src/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
@@ -0,0 +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
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT 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
+log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
+log4j.appender.stdout.layout.ConversionPattern=%d{ISO8601} %-5p [%t] %c{2} (%F:%M(%L)) - %m%n
diff --git a/log4j-1.2-api/src/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/src/test/resources/config-1.2/hadoop/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-timelineservice/src/test/resources/log4j.properties
new file mode 100644
index 0000000..81a3f6a
--- /dev/null
+++ b/log4j-1.2-api/src/src/test/resources/config-1.2/hadoop/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-timelineservice/src/test/resources/log4j.properties
@@ -0,0 +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
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT 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
+log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
+log4j.appender.stdout.layout.ConversionPattern=%d{ISO8601} %-5p [%t] %c{2} (%F:%M(%L)) - %m%n
diff --git a/log4j-1.2-api/src/src/test/resources/config-1.2/log4j-DailyRollingFileAppender.properties b/log4j-1.2-api/src/src/test/resources/config-1.2/log4j-DailyRollingFileAppender.properties
new file mode 100644
index 0000000..123a51d
--- /dev/null
+++ b/log4j-1.2-api/src/src/test/resources/config-1.2/log4j-DailyRollingFileAppender.properties
@@ -0,0 +1,26 @@
+###############################################################################
+#
+# Log4J 1.2 Configuration.
+#
+
+hadoop.log.dir=target
+hadoop.log.file=hadoop.log
+
+log4j.rootLogger=TRACE, DRFA
+
+#
+# Daily Rolling File Appender
+#
+
+log4j.appender.DRFA=org.apache.log4j.DailyRollingFileAppender
+log4j.appender.DRFA.File=${hadoop.log.dir}/${hadoop.log.file}
+
+# Rollover at midnight
+log4j.appender.DRFA.DatePattern=.yyyy-MM-dd
+
+log4j.appender.DRFA.layout=org.apache.log4j.PatternLayout
+
+# Pattern format: Date LogLevel LoggerName LogMessage
+log4j.appender.DRFA.layout.ConversionPattern=%d{ISO8601} %p %c: %m%n
+# Debugging Pattern format
+#log4j.appender.DRFA.layout.ConversionPattern=%d{ISO8601} %-5p %c{2} (%F:%M(%L)) - %m%n
diff --git a/log4j-1.2-api/src/src/test/resources/config-1.2/log4j-NullAppender.properties b/log4j-1.2-api/src/src/test/resources/config-1.2/log4j-NullAppender.properties
new file mode 100644
index 0000000..d89a4f4
--- /dev/null
+++ b/log4j-1.2-api/src/src/test/resources/config-1.2/log4j-NullAppender.properties
@@ -0,0 +1,9 @@
+###############################################################################
+#
+# Log4J 1.2 Configuration.
+#
+
+log4j.rootLogger=TRACE, NullAppender
+
+# Null Appender
+log4j.appender.NullAppender=org.apache.log4j.varia.NullAppender
diff --git a/log4j-1.2-api/src/src/test/resources/config-1.2/log4j-RollingFileAppender-with-props.properties b/log4j-1.2-api/src/src/test/resources/config-1.2/log4j-RollingFileAppender-with-props.properties
new file mode 100644
index 0000000..b664bb8
--- /dev/null
+++ b/log4j-1.2-api/src/src/test/resources/config-1.2/log4j-RollingFileAppender-with-props.properties
@@ -0,0 +1,27 @@
+###############################################################################
+#
+# Log4J 1.2 Configuration.
+#
+
+hadoop.log.dir=target
+hadoop.log.file=hadoop.log
+
+log4j.rootLogger=TRACE, RFA
+
+#
+# Rolling File Appender - cap space usage at 5gb.
+#
+hadoop.log.maxfilesize=256MB
+hadoop.log.maxbackupindex=20
+log4j.appender.RFA=org.apache.log4j.RollingFileAppender
+log4j.appender.RFA.File=${hadoop.log.dir}/${hadoop.log.file}
+
+log4j.appender.RFA.MaxFileSize=${hadoop.log.maxfilesize}
+log4j.appender.RFA.MaxBackupIndex=${hadoop.log.maxbackupindex}
+
+log4j.appender.RFA.layout=org.apache.log4j.PatternLayout
+
+# Pattern format: Date LogLevel LoggerName LogMessage
+log4j.appender.RFA.layout.ConversionPattern=%d{ISO8601} %p %c: %m%n
+# Debugging Pattern format
+#log4j.appender.RFA.layout.ConversionPattern=%d{ISO8601} %-5p %c{2} (%F:%M(%L)) - %m%n
diff --git a/log4j-1.2-api/src/src/test/resources/config-1.2/log4j-RollingFileAppender.properties b/log4j-1.2-api/src/src/test/resources/config-1.2/log4j-RollingFileAppender.properties
new file mode 100644
index 0000000..55234ba
--- /dev/null
+++ b/log4j-1.2-api/src/src/test/resources/config-1.2/log4j-RollingFileAppender.properties
@@ -0,0 +1,22 @@
+###############################################################################
+#
+# Log4J 1.2 Configuration.
+#
+
+log4j.rootLogger=TRACE, RFA
+
+#
+# Rolling File Appender - cap space usage at 5gb.
+#
+log4j.appender.RFA=org.apache.log4j.RollingFileAppender
+log4j.appender.RFA.File=target/hadoop.log
+
+log4j.appender.RFA.MaxFileSize=256MB
+log4j.appender.RFA.MaxBackupIndex=20
+
+log4j.appender.RFA.layout=org.apache.log4j.PatternLayout
+
+# Pattern format: Date LogLevel LoggerName LogMessage
+log4j.appender.RFA.layout.ConversionPattern=%d{ISO8601} %p %c: %m%n
+# Debugging Pattern format
+#log4j.appender.RFA.layout.ConversionPattern=%d{ISO8601} %-5p %c{2} (%F:%M(%L)) - %m%n
diff --git a/log4j-1.2-api/src/src/test/resources/config-1.2/log4j-console-EnhancedPatternLayout.properties b/log4j-1.2-api/src/src/test/resources/config-1.2/log4j-console-EnhancedPatternLayout.properties
new file mode 100644
index 0000000..6793eb2
--- /dev/null
+++ b/log4j-1.2-api/src/src/test/resources/config-1.2/log4j-console-EnhancedPatternLayout.properties
@@ -0,0 +1,18 @@
+###############################################################################
+#
+# Log4J 1.2 Configuration.
+#
+
+log4j.rootLogger=TRACE, Console
+
+##############################################################################
+#
+# The Console log
+#
+
+log4j.appender.Console=org.apache.log4j.ConsoleAppender
+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
+
+log4j.logger.com.example.foo = DEBUG
diff --git a/log4j-1.2-api/src/src/test/resources/config-1.2/log4j-console-HtmlLayout.properties b/log4j-1.2-api/src/src/test/resources/config-1.2/log4j-console-HtmlLayout.properties
new file mode 100644
index 0000000..216a12e
--- /dev/null
+++ b/log4j-1.2-api/src/src/test/resources/config-1.2/log4j-console-HtmlLayout.properties
@@ -0,0 +1,19 @@
+###############################################################################
+#
+# Log4J 1.2 Configuration.
+#
+
+log4j.rootLogger=TRACE, Console
+
+##############################################################################
+#
+# The Console log
+#
+
+log4j.appender.Console=org.apache.log4j.ConsoleAppender
+log4j.appender.Console.Target=System.err
+log4j.appender.Console.layout=org.apache.log4j.HTMLLayout
+log4j.appender.Console.layout.Title=Headline
+log4j.appender.Console.layout.LocationInfo=true
+
+log4j.logger.com.example.foo = DEBUG
diff --git a/log4j-1.2-api/src/src/test/resources/config-1.2/log4j-console-PatternLayout.properties b/log4j-1.2-api/src/src/test/resources/config-1.2/log4j-console-PatternLayout.properties
new file mode 100644
index 0000000..810a494
--- /dev/null
+++ b/log4j-1.2-api/src/src/test/resources/config-1.2/log4j-console-PatternLayout.properties
@@ -0,0 +1,18 @@
+###############################################################################
+#
+# Log4J 1.2 Configuration.
+#
+
+log4j.rootLogger=TRACE, Console
+
+##############################################################################
+#
+# The Console log
+#
+
+log4j.appender.Console=org.apache.log4j.ConsoleAppender
+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
+
+log4j.logger.com.example.foo = DEBUG
diff --git a/log4j-1.2-api/src/src/test/resources/config-1.2/log4j-console-SimpleLayout.properties b/log4j-1.2-api/src/src/test/resources/config-1.2/log4j-console-SimpleLayout.properties
new file mode 100644
index 0000000..5a8ac4e
--- /dev/null
+++ b/log4j-1.2-api/src/src/test/resources/config-1.2/log4j-console-SimpleLayout.properties
@@ -0,0 +1,17 @@
+###############################################################################
+#
+# Log4J 1.2 Configuration.
+#
+
+log4j.rootLogger=TRACE, Console
+
+##############################################################################
+#
+# The Console log
+#
+
+log4j.appender.Console=org.apache.log4j.ConsoleAppender
+log4j.appender.Console.Target=System.err
+log4j.appender.Console.layout=org.apache.log4j.SimpleLayout
+
+log4j.logger.com.example.foo = DEBUG
diff --git a/log4j-1.2-api/src/src/test/resources/config-1.2/log4j-console-TTCCLayout.properties b/log4j-1.2-api/src/src/test/resources/config-1.2/log4j-console-TTCCLayout.properties
new file mode 100644
index 0000000..80d38c2
--- /dev/null
+++ b/log4j-1.2-api/src/src/test/resources/config-1.2/log4j-console-TTCCLayout.properties
@@ -0,0 +1,19 @@
+###############################################################################
+#
+# Log4J 1.2 Configuration.
+#
+
+log4j.rootLogger=TRACE, Console
+
+##############################################################################
+#
+# The Console log
+#
+
+log4j.appender.Console=org.apache.log4j.ConsoleAppender
+log4j.appender.Console.Target=System.err
+log4j.appender.Console.layout=org.apache.log4j.TTCCLayout
+log4j.appender.Console.layout.ThreadPrinting=true
+log4j.appender.Console.layout.CategoryPrefixing=false
+
+log4j.logger.com.example.foo = DEBUG
diff --git a/log4j-1.2-api/src/src/test/resources/config-1.2/log4j-console-XmlLayout.properties b/log4j-1.2-api/src/src/test/resources/config-1.2/log4j-console-XmlLayout.properties
new file mode 100644
index 0000000..c8190ec
--- /dev/null
+++ b/log4j-1.2-api/src/src/test/resources/config-1.2/log4j-console-XmlLayout.properties
@@ -0,0 +1,19 @@
+###############################################################################
+#
+# Log4J 1.2 Configuration.
+#
+
+log4j.rootLogger=TRACE, Console
+
+##############################################################################
+#
+# The Console log
+#
+
+log4j.appender.Console=org.apache.log4j.ConsoleAppender
+log4j.appender.Console.Target=System.err
+log4j.appender.Console.layout=org.apache.log4j.xml.XMLLayout
+log4j.appender.Console.layout.LocationInfo=true
+log4j.appender.Console.layout.Properties=false
+
+log4j.logger.com.example.foo = DEBUG
diff --git a/log4j-1.2-api/src/src/test/resources/config-1.2/log4j-file-SimpleLayout.properties b/log4j-1.2-api/src/src/test/resources/config-1.2/log4j-file-SimpleLayout.properties
new file mode 100644
index 0000000..4d3ec0d
--- /dev/null
+++ b/log4j-1.2-api/src/src/test/resources/config-1.2/log4j-file-SimpleLayout.properties
@@ -0,0 +1,17 @@
+###############################################################################
+#
+# Log4J 1.2 Configuration.
+#
+
+log4j.rootLogger=TRACE, File
+
+##############################################################################
+#
+# The Console log
+#
+
+log4j.appender.File=org.apache.log4j.FileAppender
+log4j.appender.File.File=target/mylog.txt
+log4j.appender.File.layout=org.apache.log4j.SimpleLayout
+
+log4j.logger.com.example.foo = DEBUG
diff --git a/log4j-1.2-api/src/src/test/resources/config-1.2/log4j-system-properties-1.properties b/log4j-1.2-api/src/src/test/resources/config-1.2/log4j-system-properties-1.properties
new file mode 100644
index 0000000..a82c4c3
--- /dev/null
+++ b/log4j-1.2-api/src/src/test/resources/config-1.2/log4j-system-properties-1.properties
@@ -0,0 +1,14 @@
+###############################################################################
+#
+# Log4J 1.2 Configuration.
+#
+
+hadoop.log.file=hadoop.log
+
+log4j.rootLogger=TRACE, RFA
+
+#
+# Rolling File Appender
+#
+log4j.appender.RFA=org.apache.log4j.RollingFileAppender
+log4j.appender.RFA.File=${java.io.tmpdir}/${hadoop.log.file}
diff --git a/log4j-1.2-api/src/src/test/resources/config-1.2/log4j-system-properties-2.properties b/log4j-1.2-api/src/src/test/resources/config-1.2/log4j-system-properties-2.properties
new file mode 100644
index 0000000..9228434
--- /dev/null
+++ b/log4j-1.2-api/src/src/test/resources/config-1.2/log4j-system-properties-2.properties
@@ -0,0 +1,15 @@
+###############################################################################
+#
+# Log4J 1.2 Configuration.
+#
+
+hadoop.log.dir=${java.io.tmpdir}
+hadoop.log.file=hadoop.log
+
+log4j.rootLogger=TRACE, RFA
+
+#
+# Rolling File Appender
+#
+log4j.appender.RFA=org.apache.log4j.RollingFileAppender
+log4j.appender.RFA.File=${hadoop.log.dir}/${hadoop.log.file}
diff --git a/log4j-1.2-api/src/src/test/resources/config-1.2/spark/R/log4j.properties b/log4j-1.2-api/src/src/test/resources/config-1.2/spark/R/log4j.properties
new file mode 100644
index 0000000..cce8d91
--- /dev/null
+++ b/log4j-1.2-api/src/src/test/resources/config-1.2/spark/R/log4j.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.
+#
+
+# Set everything to be logged to the file target/unit-tests.log
+log4j.rootCategory=INFO, file
+log4j.appender.file=org.apache.log4j.FileAppender
+log4j.appender.file.append=true
+log4j.appender.file.file=R/target/unit-tests.log
+log4j.appender.file.layout=org.apache.log4j.PatternLayout
+log4j.appender.file.layout.ConversionPattern=%d{yy/MM/dd HH:mm:ss.SSS} %t %p %c{1}: %m%n
+
+# Ignore messages below warning level from Jetty, because it's a bit verbose
+log4j.logger.org.eclipse.jetty=WARN
+org.eclipse.jetty.LEVEL=WARN
diff --git a/log4j-1.2-api/src/src/test/resources/config-1.2/spark/common/network-common/src/test/resources/log4j.properties b/log4j-1.2-api/src/src/test/resources/config-1.2/spark/common/network-common/src/test/resources/log4j.properties
new file mode 100644
index 0000000..e8da774
--- /dev/null
+++ b/log4j-1.2-api/src/src/test/resources/config-1.2/spark/common/network-common/src/test/resources/log4j.properties
@@ -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.
+#
+
+# Set everything to be logged to the file target/unit-tests.log
+log4j.rootCategory=DEBUG, file
+log4j.appender.file=org.apache.log4j.FileAppender
+log4j.appender.file.append=true
+log4j.appender.file.file=target/unit-tests.log
+log4j.appender.file.layout=org.apache.log4j.PatternLayout
+log4j.appender.file.layout.ConversionPattern=%d{yy/MM/dd HH:mm:ss.SSS} %t %p %c{1}: %m%n
+
+# Silence verbose logs from 3rd-party libraries.
+log4j.logger.io.netty=INFO
diff --git a/log4j-1.2-api/src/src/test/resources/config-1.2/spark/common/network-shuffle/src/test/resources/log4j.properties b/log4j-1.2-api/src/src/test/resources/config-1.2/spark/common/network-shuffle/src/test/resources/log4j.properties
new file mode 100644
index 0000000..e739789
--- /dev/null
+++ b/log4j-1.2-api/src/src/test/resources/config-1.2/spark/common/network-shuffle/src/test/resources/log4j.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.
+#
+
+# Set everything to be logged to the file target/unit-tests.log
+log4j.rootCategory=DEBUG, file
+log4j.appender.file=org.apache.log4j.FileAppender
+log4j.appender.file.append=true
+log4j.appender.file.file=target/unit-tests.log
+log4j.appender.file.layout=org.apache.log4j.PatternLayout
+log4j.appender.file.layout.ConversionPattern=%d{yy/MM/dd HH:mm:ss.SSS} %t %p %c{1}: %m%n
diff --git a/log4j-1.2-api/src/src/test/resources/config-1.2/spark/core/src/test/resources/log4j.properties b/log4j-1.2-api/src/src/test/resources/config-1.2/spark/core/src/test/resources/log4j.properties
new file mode 100644
index 0000000..fb9d985
--- /dev/null
+++ b/log4j-1.2-api/src/src/test/resources/config-1.2/spark/core/src/test/resources/log4j.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.
+#
+
+# Set everything to be logged to the file target/unit-tests.log
+test.appender=file
+log4j.rootCategory=INFO, ${test.appender}
+log4j.appender.file=org.apache.log4j.FileAppender
+log4j.appender.file.append=true
+log4j.appender.file.file=target/unit-tests.log
+log4j.appender.file.layout=org.apache.log4j.PatternLayout
+log4j.appender.file.layout.ConversionPattern=%d{yy/MM/dd HH:mm:ss.SSS} %t %p %c{1}: %m%n
+
+# Tests that launch java subprocesses can set the "test.appender" system property to
+# "console" to avoid having the child process's logs overwrite the unit test's
+# log file.
+log4j.appender.console=org.apache.log4j.ConsoleAppender
+log4j.appender.console.target=System.err
+log4j.appender.console.layout=org.apache.log4j.PatternLayout
+log4j.appender.console.layout.ConversionPattern=%t: %m%n
+
+# 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/src/test/resources/config-1.2/spark/external/flume-sink/src/test/resources/log4j.properties b/log4j-1.2-api/src/src/test/resources/config-1.2/spark/external/flume-sink/src/test/resources/log4j.properties
new file mode 100644
index 0000000..1e3f163
--- /dev/null
+++ b/log4j-1.2-api/src/src/test/resources/config-1.2/spark/external/flume-sink/src/test/resources/log4j.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.
+#
+
+# Set everything to be logged to the file streaming/target/unit-tests.log
+log4j.rootCategory=INFO, file
+log4j.appender.file=org.apache.log4j.FileAppender
+log4j.appender.file.append=true
+log4j.appender.file.file=target/unit-tests.log
+log4j.appender.file.layout=org.apache.log4j.PatternLayout
+log4j.appender.file.layout.ConversionPattern=%d{yy/MM/dd HH:mm:ss.SSS} %t %p %c{1}: %m%n
+
+# 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/src/test/resources/config-1.2/spark/external/flume/src/test/resources/log4j.properties b/log4j-1.2-api/src/src/test/resources/config-1.2/spark/external/flume/src/test/resources/log4j.properties
new file mode 100644
index 0000000..fd51f8f
--- /dev/null
+++ b/log4j-1.2-api/src/src/test/resources/config-1.2/spark/external/flume/src/test/resources/log4j.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.
+#
+
+# Set everything to be logged to the file target/unit-tests.log
+log4j.rootCategory=INFO, file
+log4j.appender.file=org.apache.log4j.FileAppender
+log4j.appender.file.append=true
+log4j.appender.file.file=target/unit-tests.log
+log4j.appender.file.layout=org.apache.log4j.PatternLayout
+log4j.appender.file.layout.ConversionPattern=%d{yy/MM/dd HH:mm:ss.SSS} %t %p %c{1}: %m%n
+
+# 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/src/test/resources/config-1.2/spark/external/java8-tests/src/test/resources/log4j.properties b/log4j-1.2-api/src/src/test/resources/config-1.2/spark/external/java8-tests/src/test/resources/log4j.properties
new file mode 100644
index 0000000..3706a6e
--- /dev/null
+++ b/log4j-1.2-api/src/src/test/resources/config-1.2/spark/external/java8-tests/src/test/resources/log4j.properties
@@ -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.
+#
+
+# Set everything to be logged to the file target/unit-tests.log
+log4j.rootCategory=INFO, file
+log4j.appender.file=org.apache.log4j.FileAppender
+log4j.appender.file.append=true
+log4j.appender.file.file=target/unit-tests.log
+log4j.appender.file.layout=org.apache.log4j.PatternLayout
+log4j.appender.file.layout.ConversionPattern=%d{yy/MM/dd HH:mm:ss.SSS} %t %p %c{1}: %m%n
+
+# 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/src/test/resources/config-1.2/spark/external/kafka-0-10/src/test/resources/log4j.properties b/log4j-1.2-api/src/src/test/resources/config-1.2/spark/external/kafka-0-10/src/test/resources/log4j.properties
new file mode 100644
index 0000000..75e3b53
--- /dev/null
+++ b/log4j-1.2-api/src/src/test/resources/config-1.2/spark/external/kafka-0-10/src/test/resources/log4j.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.
+#
+
+# Set everything to be logged to the file target/unit-tests.log
+log4j.rootCategory=INFO, file
+log4j.appender.file=org.apache.log4j.FileAppender
+log4j.appender.file.append=true
+log4j.appender.file.file=target/unit-tests.log
+log4j.appender.file.layout=org.apache.log4j.PatternLayout
+log4j.appender.file.layout.ConversionPattern=%d{yy/MM/dd HH:mm:ss.SSS} %t %p %c{1}: %m%n
+
+# 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/src/test/resources/config-1.2/spark/external/kafka-0-8/src/test/resources/log4j.properties b/log4j-1.2-api/src/src/test/resources/config-1.2/spark/external/kafka-0-8/src/test/resources/log4j.properties
new file mode 100644
index 0000000..fd51f8f
--- /dev/null
+++ b/log4j-1.2-api/src/src/test/resources/config-1.2/spark/external/kafka-0-8/src/test/resources/log4j.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.
+#
+
+# Set everything to be logged to the file target/unit-tests.log
+log4j.rootCategory=INFO, file
+log4j.appender.file=org.apache.log4j.FileAppender
+log4j.appender.file.append=true
+log4j.appender.file.file=target/unit-tests.log
+log4j.appender.file.layout=org.apache.log4j.PatternLayout
+log4j.appender.file.layout.ConversionPattern=%d{yy/MM/dd HH:mm:ss.SSS} %t %p %c{1}: %m%n
+
+# 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/src/test/resources/config-1.2/spark/external/kinesis-asl/src/main/resources/log4j.properties b/log4j-1.2-api/src/src/test/resources/config-1.2/spark/external/kinesis-asl/src/main/resources/log4j.properties
new file mode 100644
index 0000000..4f5ea7b
--- /dev/null
+++ b/log4j-1.2-api/src/src/test/resources/config-1.2/spark/external/kinesis-asl/src/main/resources/log4j.properties
@@ -0,0 +1,37 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+log4j.rootCategory=WARN, console
+
+# File appender
+log4j.appender.file=org.apache.log4j.FileAppender
+log4j.appender.file.append=false
+log4j.appender.file.file=target/unit-tests.log
+log4j.appender.file.layout=org.apache.log4j.PatternLayout
+log4j.appender.file.layout.ConversionPattern=%d{yy/MM/dd HH:mm:ss.SSS} %p %c{1}: %m%n
+
+# Console appender
+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{yy/MM/dd HH:mm:ss} %p %c{1}: %m%n
+
+# Settings to quiet third party logs that are too verbose
+log4j.logger.org.spark_project.jetty=WARN
+log4j.logger.org.spark_project.jetty.util.component.AbstractLifeCycle=ERROR
+log4j.logger.org.apache.spark.repl.SparkIMain$exprTyper=INFO
+log4j.logger.org.apache.spark.repl.SparkILoop$SparkILoopInterpreter=INFO
diff --git a/log4j-1.2-api/src/src/test/resources/config-1.2/spark/external/kinesis-asl/src/test/resources/log4j.properties b/log4j-1.2-api/src/src/test/resources/config-1.2/spark/external/kinesis-asl/src/test/resources/log4j.properties
new file mode 100644
index 0000000..3706a6e
--- /dev/null
+++ b/log4j-1.2-api/src/src/test/resources/config-1.2/spark/external/kinesis-asl/src/test/resources/log4j.properties
@@ -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.
+#
+
+# Set everything to be logged to the file target/unit-tests.log
+log4j.rootCategory=INFO, file
+log4j.appender.file=org.apache.log4j.FileAppender
+log4j.appender.file.append=true
+log4j.appender.file.file=target/unit-tests.log
+log4j.appender.file.layout=org.apache.log4j.PatternLayout
+log4j.appender.file.layout.ConversionPattern=%d{yy/MM/dd HH:mm:ss.SSS} %t %p %c{1}: %m%n
+
+# 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/src/test/resources/config-1.2/spark/graphx/src/test/resources/log4j.properties b/log4j-1.2-api/src/src/test/resources/config-1.2/spark/graphx/src/test/resources/log4j.properties
new file mode 100644
index 0000000..3706a6e
--- /dev/null
+++ b/log4j-1.2-api/src/src/test/resources/config-1.2/spark/graphx/src/test/resources/log4j.properties
@@ -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.
+#
+
+# Set everything to be logged to the file target/unit-tests.log
+log4j.rootCategory=INFO, file
+log4j.appender.file=org.apache.log4j.FileAppender
+log4j.appender.file.append=true
+log4j.appender.file.file=target/unit-tests.log
+log4j.appender.file.layout=org.apache.log4j.PatternLayout
+log4j.appender.file.layout.ConversionPattern=%d{yy/MM/dd HH:mm:ss.SSS} %t %p %c{1}: %m%n
+
+# 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/src/test/resources/config-1.2/spark/launcher/src/test/resources/log4j.properties b/log4j-1.2-api/src/src/test/resources/config-1.2/spark/launcher/src/test/resources/log4j.properties
new file mode 100644
index 0000000..744c456
--- /dev/null
+++ b/log4j-1.2-api/src/src/test/resources/config-1.2/spark/launcher/src/test/resources/log4j.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.
+#
+
+# Set everything to be logged to the file core/target/unit-tests.log
+test.appender=file
+log4j.rootCategory=INFO, ${test.appender}
+log4j.appender.file=org.apache.log4j.FileAppender
+log4j.appender.file.append=false
+log4j.appender.file.file=target/unit-tests.log
+log4j.appender.file.layout=org.apache.log4j.PatternLayout
+log4j.appender.file.layout.ConversionPattern=%d{yy/MM/dd HH:mm:ss.SSS} %t %p %c{1}: %m%n
+
+log4j.appender.childproc=org.apache.log4j.ConsoleAppender
+log4j.appender.childproc.target=System.err
+log4j.appender.childproc.layout=org.apache.log4j.PatternLayout
+log4j.appender.childproc.layout.ConversionPattern=%t: %m%n
+
+# 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/src/test/resources/config-1.2/spark/mllib/src/test/resources/log4j.properties b/log4j-1.2-api/src/src/test/resources/config-1.2/spark/mllib/src/test/resources/log4j.properties
new file mode 100644
index 0000000..fd51f8f
--- /dev/null
+++ b/log4j-1.2-api/src/src/test/resources/config-1.2/spark/mllib/src/test/resources/log4j.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.
+#
+
+# Set everything to be logged to the file target/unit-tests.log
+log4j.rootCategory=INFO, file
+log4j.appender.file=org.apache.log4j.FileAppender
+log4j.appender.file.append=true
+log4j.appender.file.file=target/unit-tests.log
+log4j.appender.file.layout=org.apache.log4j.PatternLayout
+log4j.appender.file.layout.ConversionPattern=%d{yy/MM/dd HH:mm:ss.SSS} %t %p %c{1}: %m%n
+
+# 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/src/test/resources/config-1.2/spark/repl/src/test/resources/log4j.properties b/log4j-1.2-api/src/src/test/resources/config-1.2/spark/repl/src/test/resources/log4j.properties
new file mode 100644
index 0000000..7665bd5
--- /dev/null
+++ b/log4j-1.2-api/src/src/test/resources/config-1.2/spark/repl/src/test/resources/log4j.properties
@@ -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.
+#
+
+# Set everything to be logged to the target/unit-tests.log
+log4j.rootCategory=INFO, file
+log4j.appender.file=org.apache.log4j.FileAppender
+log4j.appender.file.append=true
+log4j.appender.file.file=target/unit-tests.log
+log4j.appender.file.layout=org.apache.log4j.PatternLayout
+log4j.appender.file.layout.ConversionPattern=%d{yy/MM/dd HH:mm:ss.SSS} %t %p %c{1}: %m%n
+
+# 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/src/test/resources/config-1.2/spark/sql/catalyst/src/test/resources/log4j.properties b/log4j-1.2-api/src/src/test/resources/config-1.2/spark/sql/catalyst/src/test/resources/log4j.properties
new file mode 100644
index 0000000..3706a6e
--- /dev/null
+++ b/log4j-1.2-api/src/src/test/resources/config-1.2/spark/sql/catalyst/src/test/resources/log4j.properties
@@ -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.
+#
+
+# Set everything to be logged to the file target/unit-tests.log
+log4j.rootCategory=INFO, file
+log4j.appender.file=org.apache.log4j.FileAppender
+log4j.appender.file.append=true
+log4j.appender.file.file=target/unit-tests.log
+log4j.appender.file.layout=org.apache.log4j.PatternLayout
+log4j.appender.file.layout.ConversionPattern=%d{yy/MM/dd HH:mm:ss.SSS} %t %p %c{1}: %m%n
+
+# 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/src/test/resources/config-1.2/spark/sql/core/src/test/resources/log4j.properties b/log4j-1.2-api/src/src/test/resources/config-1.2/spark/sql/core/src/test/resources/log4j.properties
new file mode 100644
index 0000000..33b9ecf
--- /dev/null
+++ b/log4j-1.2-api/src/src/test/resources/config-1.2/spark/sql/core/src/test/resources/log4j.properties
@@ -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.
+#
+
+# Set everything to be logged to the file core/target/unit-tests.log
+log4j.rootLogger=INFO, CA, FA
+
+#Console Appender
+log4j.appender.CA=org.apache.log4j.ConsoleAppender
+log4j.appender.CA.layout=org.apache.log4j.PatternLayout
+log4j.appender.CA.layout.ConversionPattern=%d{HH:mm:ss.SSS} %p %c: %m%n
+log4j.appender.CA.Threshold = WARN
+log4j.appender.CA.follow = true
+
+
+#File Appender
+log4j.appender.FA=org.apache.log4j.FileAppender
+log4j.appender.FA.append=false
+log4j.appender.FA.file=target/unit-tests.log
+log4j.appender.FA.layout=org.apache.log4j.PatternLayout
+log4j.appender.FA.layout.ConversionPattern=%d{HH:mm:ss.SSS} %t %p %c{1}: %m%n
+
+# Set the logger level of File Appender to WARN
+log4j.appender.FA.Threshold = INFO
+
+# Some packages are noisy for no good reason.
+log4j.additivity.org.apache.parquet.hadoop.ParquetRecordReader=false
+log4j.logger.org.apache.parquet.hadoop.ParquetRecordReader=OFF
+
+log4j.additivity.org.apache.parquet.hadoop.ParquetOutputCommitter=false
+log4j.logger.org.apache.parquet.hadoop.ParquetOutputCommitter=OFF
+
+log4j.additivity.org.apache.hadoop.hive.serde2.lazy.LazyStruct=false
+log4j.logger.org.apache.hadoop.hive.serde2.lazy.LazyStruct=OFF
+
+log4j.additivity.org.apache.hadoop.hive.metastore.RetryingHMSHandler=false
+log4j.logger.org.apache.hadoop.hive.metastore.RetryingHMSHandler=OFF
+
+log4j.additivity.hive.ql.metadata.Hive=false
+log4j.logger.hive.ql.metadata.Hive=OFF
+
+# Parquet related logging
+log4j.logger.org.apache.parquet.hadoop=WARN
+log4j.logger.org.apache.spark.sql.parquet=INFO
diff --git a/log4j-1.2-api/src/src/test/resources/config-1.2/spark/sql/hive/src/test/resources/log4j.properties b/log4j-1.2-api/src/src/test/resources/config-1.2/spark/sql/hive/src/test/resources/log4j.properties
new file mode 100644
index 0000000..fea3404
--- /dev/null
+++ b/log4j-1.2-api/src/src/test/resources/config-1.2/spark/sql/hive/src/test/resources/log4j.properties
@@ -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.
+#
+
+# Set everything to be logged to the file core/target/unit-tests.log
+log4j.rootLogger=DEBUG, CA, FA
+
+#Console Appender
+log4j.appender.CA=org.apache.log4j.ConsoleAppender
+log4j.appender.CA.layout=org.apache.log4j.PatternLayout
+log4j.appender.CA.layout.ConversionPattern=%d{HH:mm:ss.SSS} %p %c: %m%n
+log4j.appender.CA.Threshold = WARN
+
+
+#File Appender
+log4j.appender.FA=org.apache.log4j.FileAppender
+log4j.appender.FA.append=false
+log4j.appender.FA.file=target/unit-tests.log
+log4j.appender.FA.layout=org.apache.log4j.PatternLayout
+log4j.appender.FA.layout.ConversionPattern=%d{HH:mm:ss.SSS} %t %p %c{1}: %m%n
+
+# Set the logger level of File Appender to WARN
+log4j.appender.FA.Threshold = DEBUG
+
+# Some packages are noisy for no good reason.
+log4j.additivity.org.apache.hadoop.hive.serde2.lazy.LazyStruct=false
+log4j.logger.org.apache.hadoop.hive.serde2.lazy.LazyStruct=OFF
+
+log4j.additivity.org.apache.hadoop.hive.metastore.RetryingHMSHandler=false
+log4j.logger.org.apache.hadoop.hive.metastore.RetryingHMSHandler=OFF
+
+log4j.additivity.hive.log=false
+log4j.logger.hive.log=OFF
+
+log4j.additivity.parquet.hadoop.ParquetRecordReader=false
+log4j.logger.parquet.hadoop.ParquetRecordReader=OFF
+
+log4j.additivity.org.apache.parquet.hadoop.ParquetRecordReader=false
+log4j.logger.org.apache.parquet.hadoop.ParquetRecordReader=OFF
+
+log4j.additivity.org.apache.parquet.hadoop.ParquetOutputCommitter=false
+log4j.logger.org.apache.parquet.hadoop.ParquetOutputCommitter=OFF
+
+log4j.additivity.hive.ql.metadata.Hive=false
+log4j.logger.hive.ql.metadata.Hive=OFF
+
+log4j.additivity.org.apache.hadoop.hive.ql.io.RCFile=false
+log4j.logger.org.apache.hadoop.hive.ql.io.RCFile=ERROR
diff --git a/log4j-1.2-api/src/src/test/resources/config-1.2/spark/streaming/src/test/resources/log4j.properties b/log4j-1.2-api/src/src/test/resources/config-1.2/spark/streaming/src/test/resources/log4j.properties
new file mode 100644
index 0000000..fd51f8f
--- /dev/null
+++ b/log4j-1.2-api/src/src/test/resources/config-1.2/spark/streaming/src/test/resources/log4j.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.
+#
+
+# Set everything to be logged to the file target/unit-tests.log
+log4j.rootCategory=INFO, file
+log4j.appender.file=org.apache.log4j.FileAppender
+log4j.appender.file.append=true
+log4j.appender.file.file=target/unit-tests.log
+log4j.appender.file.layout=org.apache.log4j.PatternLayout
+log4j.appender.file.layout.ConversionPattern=%d{yy/MM/dd HH:mm:ss.SSS} %t %p %c{1}: %m%n
+
+# 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/src/test/resources/config-1.2/spark/yarn/src/test/resources/log4j.properties b/log4j-1.2-api/src/src/test/resources/config-1.2/spark/yarn/src/test/resources/log4j.properties
new file mode 100644
index 0000000..d13454d
--- /dev/null
+++ b/log4j-1.2-api/src/src/test/resources/config-1.2/spark/yarn/src/test/resources/log4j.properties
@@ -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.
+#
+
+# Set everything to be logged to the file target/unit-tests.log
+log4j.rootCategory=DEBUG, file
+log4j.appender.file=org.apache.log4j.FileAppender
+log4j.appender.file.append=true
+log4j.appender.file.file=target/unit-tests.log
+log4j.appender.file.layout=org.apache.log4j.PatternLayout
+log4j.appender.file.layout.ConversionPattern=%d{yy/MM/dd HH:mm:ss.SSS} %t %p %c{1}: %m%n
+
+# Ignore messages below warning level from a few verbose libraries.
+log4j.logger.com.sun.jersey=WARN
+log4j.logger.org.apache.hadoop=WARN
+log4j.logger.org.eclipse.jetty=WARN
+log4j.logger.org.mortbay=WARN
+log4j.logger.org.spark_project.jetty=WARN
diff --git a/log4j-1.2-api/src/src/test/resources/hello.vm b/log4j-1.2-api/src/src/test/resources/hello.vm
new file mode 100644
index 0000000..5ce9755
--- /dev/null
+++ b/log4j-1.2-api/src/src/test/resources/hello.vm
@@ -0,0 +1,6 @@
+<html>
+<body>
+ #set( $foo = "Velocity" )
+Hello $foo World!
+</body>
+<html>
\ No newline at end of file
diff --git a/log4j-1.2-api/src/src/test/resources/log-RouteWithMDC.xml b/log4j-1.2-api/src/src/test/resources/log-RouteWithMDC.xml
new file mode 100644
index 0000000..eb7e8a5
--- /dev/null
+++ b/log4j-1.2-api/src/src/test/resources/log-RouteWithMDC.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="UTF-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 "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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="ConfigTest" status="error" packages="org.apache.logging.log4j.test"
+ monitorInterval="5">
+ <Appenders>
+ <Console name="STDOUT">
+ <PatternLayout pattern="%X{Type, Name} %m%n"/>
+ </Console>
+ <List name="List">
+ <PatternLayout pattern="%X{Type, Name} %m%n"/>
+ </List>
+ <Routing name="Routing">
+ <Routes pattern="$${ctx:Type}">
+ <Route ref="STDOUT"/>
+ <Route ref="STDOUT" key="Audit"/>
+ <Route ref="List" key="Service"/>
+ </Routes>
+ </Routing>
+ </Appenders>
+ <Loggers>
+ <Logger name="org.apache.test.logging" level="debug" additivity="false">
+ <AppenderRef ref="Routing"/>
+ </Logger>
+ <Logger name="org.apache.test" level="trace" additivity="false">
+ <AppenderRef ref="List"/>
+ </Logger>
+ <Root level="error">
+ <AppenderRef ref="STDOUT"/>
+ </Root>
+ </Loggers>
+</Configuration>
\ No newline at end of file
diff --git a/log4j-1.2-api/src/src/test/resources/log4j.xml b/log4j-1.2-api/src/src/test/resources/log4j.xml
new file mode 100644
index 0000000..065c748
--- /dev/null
+++ b/log4j-1.2-api/src/src/test/resources/log4j.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-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 "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
+
+<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
+ <appender name="list" class="org.apache.log4j.ListAppender">
+ <layout class="org.apache.log4j.PatternLayout">
+ <param name="ConversionPattern" value="%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n" />
+ </layout>
+ </appender>
+
+ <appender name="events" class="org.apache.log4j.ListAppender">
+ </appender>
+
+ <root>
+ <priority value ="trace" />
+ <appender-ref ref="list" />
+ <appender-ref ref="events" />
+ </root>
+
+</log4j:configuration>
\ No newline at end of file
diff --git a/log4j-1.2-api/src/src/test/resources/log4j1-file.xml b/log4j-1.2-api/src/src/test/resources/log4j1-file.xml
new file mode 100644
index 0000000..bc7df16
--- /dev/null
+++ b/log4j-1.2-api/src/src/test/resources/log4j1-file.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="UTF-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 "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
+
+<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
+ <appender name="console" class="org.apache.log4j.ConsoleAppender">
+ <param name="Target" value="System.out"/>
+ <layout class="org.apache.log4j.PatternLayout">
+ <param name="ConversionPattern" value="%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n" />
+ </layout>
+ </appender>
+
+ <appender name="A1" class="org.apache.log4j.FileAppender">
+
+ <param name="File" value="target/temp.A1" />
+ <param name="Append" value="false" />
+
+ <layout class="org.apache.log4j.PatternLayout">
+ <param name="ConversionPattern" value="%-5p %c{2} - %m%n"/>
+ </layout>
+ </appender>
+
+ <appender name="A2" class="org.apache.log4j.FileAppender">
+ <param name="File" value="target/temp.A2" />
+ <param name="Append" value="false" />
+ <layout class="org.apache.log4j.TTCCLayout">
+ <param name="DateFormat" value="ISO8601" />
+ </layout>
+ </appender>
+
+ <logger name="org.apache.log4j.xml">
+ <level value="trace" />
+ <appender-ref ref="A1" />
+ </logger>
+
+ <root>
+ <priority value ="trace" />
+ <appender-ref ref="A1" />
+ <appender-ref ref="A2" />
+ </root>
+
+</log4j:configuration>
\ No newline at end of file
diff --git a/log4j-1.2-api/src/src/test/resources/log4j1-list.xml b/log4j-1.2-api/src/src/test/resources/log4j1-list.xml
new file mode 100644
index 0000000..065c748
--- /dev/null
+++ b/log4j-1.2-api/src/src/test/resources/log4j1-list.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-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 "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
+
+<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
+ <appender name="list" class="org.apache.log4j.ListAppender">
+ <layout class="org.apache.log4j.PatternLayout">
+ <param name="ConversionPattern" value="%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n" />
+ </layout>
+ </appender>
+
+ <appender name="events" class="org.apache.log4j.ListAppender">
+ </appender>
+
+ <root>
+ <priority value ="trace" />
+ <appender-ref ref="list" />
+ <appender-ref ref="events" />
+ </root>
+
+</log4j:configuration>
\ No newline at end of file
diff --git a/log4j-1.2-api/src/src/test/resources/log4j2-config.xml b/log4j-1.2-api/src/src/test/resources/log4j2-config.xml
new file mode 100644
index 0000000..2427af8
--- /dev/null
+++ b/log4j-1.2-api/src/src/test/resources/log4j2-config.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-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 "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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="ConfigTest" status="error" packages="org.apache.logging.log4j.test"
+ monitorInterval="5">
+ <Appenders>
+ <Console name="STDOUT">
+ <PatternLayout pattern="%m%n"/>
+ </Console>
+ <List name="List">
+ </List>
+ </Appenders>
+ <Loggers>
+ <Logger name="org.apache.test.logging" level="debug" additivity="false">
+ <AppenderRef ref="List"/>
+ </Logger>
+ <Logger name="org.apache.test" level="trace" additivity="false">
+ <AppenderRef ref="List"/>
+ </Logger>
+ <Root level="error">
+ <AppenderRef ref="STDOUT"/>
+ </Root>
+ </Loggers>
+</Configuration>
\ No newline at end of file
diff --git a/log4j-1.2-api/src/src/test/resources/logWithMDC.xml b/log4j-1.2-api/src/src/test/resources/logWithMDC.xml
new file mode 100644
index 0000000..1fe8482
--- /dev/null
+++ b/log4j-1.2-api/src/src/test/resources/logWithMDC.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="UTF-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 "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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="ConfigTest" status="error" packages="org.apache.logging.log4j.test"
+ monitorInterval="5">
+ <Appenders>
+ <Console name="STDOUT">
+ <PatternLayout pattern="%m%n"/>
+ </Console>
+ <List name="List">
+ <PatternLayout pattern="%X{Key1, Key2} %m%n"/>
+ </List>
+ </Appenders>
+ <Loggers>
+ <Logger name="org.apache.test.logging" level="debug" additivity="false">
+ <AppenderRef ref="List"/>
+ </Logger>
+ <Logger name="org.apache.test" level="trace" additivity="false">
+ <AppenderRef ref="List"/>
+ </Logger>
+ <Root level="error">
+ <AppenderRef ref="STDOUT"/>
+ </Root>
+ </Loggers>
+</Configuration>
\ No newline at end of file
diff --git a/log4j-1.2-api/src/src/test/resources/witness/serialization/info.bin b/log4j-1.2-api/src/src/test/resources/witness/serialization/info.bin
new file mode 100644
index 0000000..f887f39
--- /dev/null
+++ b/log4j-1.2-api/src/src/test/resources/witness/serialization/info.bin
Binary files differ
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 f671193..a16f2a3 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
@@ -74,7 +74,7 @@
public void testForcedLog() {
final MockCategory category = new MockCategory("org.example.foo");
category.setAdditivity(false);
- category.getLogger().addAppender(appender);
+ ((org.apache.logging.log4j.core.Logger) category.getLogger()).addAppender(appender);
category.info("Hello, World");
final List<LogEvent> list = appender.getEvents();
int events = list.size();
@@ -168,11 +168,11 @@
@Test
public void testClassName() {
final Category category = Category.getInstance("TestCategory");
- final Layout<String> layout = PatternLayout.newBuilder().withPattern("%d %p %C{1.} [%t] %m%n").build();
+ final Layout<String> 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<String> msgs = appender.getMessages();
assertTrue("Incorrect number of messages. Expected 1 got " + msgs.size(), msgs.size() == 1);
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 0000000..83c2758
--- /dev/null
+++ b/log4j-1.2-api/src/test/java/org/apache/log4j/ListAppender.java
@@ -0,0 +1,83 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.log4j;
+
+import org.apache.log4j.spi.LoggingEvent;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * 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<LoggingEvent> events = Collections.synchronizedList(new ArrayList<>());
+
+ private final List<String> messages = Collections.synchronizedList(new ArrayList<>());
+
+
+ private static final String WINDOWS_LINE_SEP = "\r\n";
+
+ @Override
+ protected void append(LoggingEvent event) {
+ Layout layout = getLayout();
+ if (layout != null) {
+ String result = layout.format(event);
+ if (result != null) {
+ messages.add(result);
+ }
+ } else {
+ events.add(event);
+ }
+ }
+
+ @Override
+ public void close() {
+
+ }
+
+ @Override
+ public boolean requiresLayout() {
+ return false;
+ }
+
+ /** Returns an immutable snapshot of captured log events */
+ public List<LoggingEvent> getEvents() {
+ return Collections.unmodifiableList(new ArrayList<>(events));
+ }
+
+ /** Returns an immutable snapshot of captured messages */
+ public List<String> 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<String> getMessages(final int minSize, final long timeout, final TimeUnit timeUnit) throws InterruptedException {
+ final long endMillis = System.currentTimeMillis() + timeUnit.toMillis(timeout);
+ while (messages.size() < minSize && System.currentTimeMillis() < endMillis) {
+ Thread.sleep(100);
+ }
+ return getMessages();
+ }
+}
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 d21fa62..1742422 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
@@ -26,6 +26,7 @@
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;
@@ -121,7 +122,7 @@
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 +135,7 @@
assertEquals(4, coutingAppender.counter);
coutingAppender.stop();
} finally {
- loggerA.getLogger().removeAppender(coutingAppender);
+ ((org.apache.logging.log4j.core.Logger) loggerA.getLogger()).removeAppender(coutingAppender);
}
}
@@ -154,8 +155,8 @@
ca2.start();
try {
- a.getLogger().addAppender(ca1);
- abc.getLogger().addAppender(ca2);
+ ((org.apache.logging.log4j.core.Logger) a.getLogger()).addAppender(ca1);
+ ((org.apache.logging.log4j.core.Logger) abc.getLogger()).addAppender(ca2);
assertEquals(ca1.counter, 0);
assertEquals(ca2.counter, 0);
@@ -174,8 +175,8 @@
ca1.stop();
ca2.stop();
} finally {
- a.getLogger().removeAppender(ca1);
- abc.getLogger().removeAppender(ca2);
+ ((org.apache.logging.log4j.core.Logger) a.getLogger()).removeAppender(ca1);
+ ((org.apache.logging.log4j.core.Logger) abc.getLogger()).removeAppender(ca2);
}}
/**
@@ -196,9 +197,9 @@
final CountingAppender caABC = new CountingAppender();
caABC.start();
try {
- root.getLogger().addAppender(caRoot);
- a.getLogger().addAppender(caA);
- abc.getLogger().addAppender(caABC);
+ ((org.apache.logging.log4j.core.Logger) root.getLogger()).addAppender(caRoot);
+ ((org.apache.logging.log4j.core.Logger) a.getLogger()).addAppender(caA);
+ ((org.apache.logging.log4j.core.Logger) abc.getLogger()).addAppender(caABC);
assertEquals(caRoot.counter, 0);
assertEquals(caA.counter, 0);
@@ -224,9 +225,9 @@
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
@@ -389,7 +390,7 @@
final ListAppender appender = new ListAppender("List");
appender.start();
final Logger root = Logger.getRootLogger();
- root.getLogger().addAppender(appender);
+ ((org.apache.logging.log4j.core.Logger) root.getLogger()).addAppender(appender);
root.setLevel(Level.INFO);
final Logger tracer = Logger.getLogger("com.example.Tracer");
@@ -405,7 +406,7 @@
assertEquals(org.apache.logging.log4j.Level.TRACE, event.getLevel());
assertEquals("Message 1", event.getMessage().getFormat());
appender.stop();
- root.getLogger().removeAppender(appender);
+ ((org.apache.logging.log4j.core.Logger) root.getLogger()).removeAppender(appender);
}
/**
@@ -417,7 +418,7 @@
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 +436,7 @@
assertEquals("Message 1", event.getMessage().getFormattedMessage());
appender.stop();
} finally {
- root.getLogger().removeAppender(appender);
+ ((org.apache.logging.log4j.core.Logger) root.getLogger()).removeAppender(appender);
}
}
@@ -448,7 +449,7 @@
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,19 +459,19 @@
assertFalse(root.isTraceEnabled());
appender.stop();
} finally {
- root.getLogger().removeAppender(appender);
+ ((org.apache.logging.log4j.core.Logger) root.getLogger()).removeAppender(appender);
}
}
@Test
@SuppressWarnings("deprecation")
public void testLog() {
- final PatternLayout layout = PatternLayout.newBuilder().withPattern("%d %C %L %m").build();
+ final PatternLayout layout = PatternLayout.newBuilder().setPattern("%d %C %L %m").build();
final ListAppender appender = new ListAppender("List", null, layout, false, false);
appender.start();
final Logger root = Logger.getRootLogger();
try {
- root.getLogger().addAppender(appender);
+ ((org.apache.logging.log4j.core.Logger) root.getLogger()).addAppender(appender);
root.setLevel(Level.INFO);
final MyLogger log = new MyLogger(root);
log.logInfo("This is a test", null);
@@ -482,7 +483,7 @@
assertTrue("Message contains incorrect class name: " + msg, msg.contains(LoggerTest.class.getName()));
appender.stop();
} finally {
- root.getLogger().removeAppender(appender);
+ ((org.apache.logging.log4j.core.Logger) root.getLogger()).removeAppender(appender);
}
}
@@ -507,7 +508,7 @@
int counter;
CountingAppender() {
- super("Counter", null, null);
+ super("Counter", null, null, true, Property.EMPTY_ARRAY);
counter = 0;
}
diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/bridge/LogEventWrapperTest.java b/log4j-1.2-api/src/test/java/org/apache/log4j/bridge/LogEventWrapperTest.java
new file mode 100644
index 0000000..af38045
--- /dev/null
+++ b/log4j-1.2-api/src/test/java/org/apache/log4j/bridge/LogEventWrapperTest.java
@@ -0,0 +1,50 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+
+package org.apache.log4j.bridge;
+
+import org.apache.log4j.spi.LoggingEvent;
+import org.apache.logging.log4j.core.LogEvent;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+
+public class LogEventWrapperTest {
+
+ @Test
+ public void testThread() {
+ Thread currentThread = Thread.currentThread();
+ String threadName = currentThread.getName();
+ LoggingEvent log4j1Event = new LoggingEvent() {
+
+ @Override
+ public String getThreadName() {
+ return threadName;
+ }
+ };
+ LogEvent log4j2Event = new LogEventWrapper(log4j1Event);
+ assertEquals(currentThread.getId(), log4j2Event.getThreadId());
+ assertEquals(currentThread.getPriority(), log4j2Event.getThreadPriority());
+ }
+
+ @Test
+ public void testToImmutable() {
+ LogEventWrapper wrapper = new LogEventWrapper(new LoggingEvent());
+ assertSame(wrapper, wrapper.toImmutable());
+ }
+}
diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/config/AsyncAppenderTest.java b/log4j-1.2-api/src/test/java/org/apache/log4j/config/AsyncAppenderTest.java
new file mode 100644
index 0000000..bac1338
--- /dev/null
+++ b/log4j-1.2-api/src/test/java/org/apache/log4j/config/AsyncAppenderTest.java
@@ -0,0 +1,103 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.log4j.config;
+
+import org.apache.log4j.ListAppender;
+import org.apache.log4j.LogManager;
+import org.apache.log4j.Logger;
+import org.apache.log4j.bridge.AppenderAdapter;
+import org.apache.log4j.spi.LoggingEvent;
+import org.apache.log4j.xml.XmlConfigurationFactory;
+import org.apache.logging.log4j.core.Appender;
+import org.apache.logging.log4j.core.LoggerContext;
+import org.apache.logging.log4j.core.config.Configuration;
+import org.apache.logging.log4j.core.config.ConfigurationSource;
+import org.apache.logging.log4j.core.config.Configurator;
+import org.apache.logging.log4j.spi.LoggerContextFactory;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Test configuration from XML.
+ */
+public class AsyncAppenderTest {
+
+ @Test
+ public void testAsyncXml() throws Exception {
+ LoggerContext loggerContext = configure("target/test-classes/log4j1-async.xml");
+ Logger logger = LogManager.getLogger("test");
+ logger.debug("This is a test of the root logger");
+ Thread.sleep(50);
+ Configuration configuration = loggerContext.getConfiguration();
+ Map<String, Appender> appenders = configuration.getAppenders();
+ ListAppender messageAppender = null;
+ for (Map.Entry<String, Appender> entry : appenders.entrySet()) {
+ if (entry.getKey().equals("list")) {
+ messageAppender = (ListAppender) ((AppenderAdapter.Adapter) entry.getValue()).getAppender();
+ }
+ }
+ assertNotNull("No Message Appender", messageAppender);
+ List<String> messages = messageAppender.getMessages();
+ assertTrue("No messages", messages != null && messages.size() > 0);
+ }
+
+ @Test
+ public void testAsyncProperties() throws Exception {
+ LoggerContext loggerContext = configure("target/test-classes/log4j1-async.properties");
+ Logger logger = LogManager.getLogger("test");
+ logger.debug("This is a test of the root logger");
+ Thread.sleep(50);
+ Configuration configuration = loggerContext.getConfiguration();
+ Map<String, Appender> appenders = configuration.getAppenders();
+ ListAppender messageAppender = null;
+ for (Map.Entry<String, Appender> entry : appenders.entrySet()) {
+ if (entry.getKey().equals("list")) {
+ messageAppender = (ListAppender) ((AppenderAdapter.Adapter) entry.getValue()).getAppender();
+ }
+ }
+ assertNotNull("No Message Appender", messageAppender);
+ List<String> messages = messageAppender.getMessages();
+ assertTrue("No messages", messages != null && messages.size() > 0);
+ }
+
+
+ private LoggerContext configure(String configLocation) throws Exception {
+ File file = new File(configLocation);
+ InputStream is = new FileInputStream(file);
+ ConfigurationSource source = new ConfigurationSource(is, file);
+ LoggerContextFactory factory = org.apache.logging.log4j.LogManager.getFactory();
+ LoggerContext context = (LoggerContext) org.apache.logging.log4j.LogManager.getContext(false);
+ Configuration configuration;
+ if (configLocation.endsWith(".xml")) {
+ configuration = new XmlConfigurationFactory().getConfiguration(context, source);
+ } else {
+ configuration = new PropertiesConfigurationFactory().getConfiguration(context, source);
+ }
+ assertNotNull("No configuration created", configuration);
+ Configurator.reconfigure(configuration);
+ return context;
+ }
+
+}
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 0000000..5b4412d
--- /dev/null
+++ b/log4j-1.2-api/src/test/java/org/apache/log4j/config/AutoConfigTest.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.log4j.config;
+
+import org.apache.log4j.ListAppender;
+import org.apache.log4j.LogManager;
+import org.apache.log4j.Logger;
+import org.apache.log4j.bridge.AppenderAdapter;
+import org.apache.log4j.spi.LoggingEvent;
+import org.apache.logging.log4j.core.Appender;
+import org.apache.logging.log4j.core.config.Configuration;
+import org.apache.logging.log4j.core.config.ConfigurationFactory;
+import org.apache.logging.log4j.spi.LoggerContext;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.io.File;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Test configuration from XML.
+ */
+public class AutoConfigTest {
+
+ @BeforeClass
+ public static void beforeClass() {
+ System.setProperty(ConfigurationFactory.LOG4J1_EXPERIMENTAL, "true");
+ }
+
+ @Test
+ public void testListAppender() {
+ Logger logger = LogManager.getLogger("test");
+ logger.debug("This is a test of the root logger");
+ LoggerContext loggerContext = org.apache.logging.log4j.LogManager.getContext(false);
+ Configuration configuration = ((org.apache.logging.log4j.core.LoggerContext) loggerContext).getConfiguration();
+ Map<String, Appender> appenders = configuration.getAppenders();
+ ListAppender eventAppender = null;
+ ListAppender messageAppender = null;
+ for (Map.Entry<String, Appender> 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("No Event Appender", eventAppender);
+ assertNotNull("No Message Appender", messageAppender);
+ List<LoggingEvent> events = eventAppender.getEvents();
+ assertTrue("No events", events != null && events.size() > 0);
+ List<String> messages = messageAppender.getMessages();
+ assertTrue("No messages", messages != null && messages.size() > 0);
+ }
+
+}
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 8d3e4e2..7d40a3c 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
@@ -169,12 +169,12 @@
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());
+ // System.out.println("expected: " + tempFileName + " Actual: " + appender.getFileName());
assertEquals(tempFileName, appender.getFileName());
} finally {
try {
Files.deleteIfExists(tempFilePath);
- } catch (FileSystemException e) {
+ } catch (final FileSystemException e) {
e.printStackTrace();
}
}
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 0000000..840971a
--- /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 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.After;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Test RewriteAppender
+ */
+public class MapRewriteAppenderTest {
+
+ @BeforeClass
+ public static void beforeClass() {
+ System.setProperty(ConfigurationFactory.LOG4J1_CONFIGURATION_FILE_PROPERTY, "target/test-classes/log4j1-mapRewrite.xml");
+ }
+
+ @After
+ public void after() {
+ ThreadContext.clearMap();
+ }
+
+ @Test
+ public void testRewrite() throws Exception {
+ Logger logger = LogManager.getLogger("test");
+ Map<String, String> map = new HashMap<>();
+ map.put("message", "This is a test");
+ map.put("hello", "world");
+ logger.debug(map);
+ LoggerContext context = (LoggerContext) org.apache.logging.log4j.LogManager.getContext(false);
+ Configuration configuration = context.getConfiguration();
+ Map<String, Appender> appenders = configuration.getAppenders();
+ ListAppender eventAppender = null;
+ for (Map.Entry<String, Appender> entry : appenders.entrySet()) {
+ if (entry.getKey().equals("events")) {
+ eventAppender = (ListAppender) ((AppenderAdapter.Adapter) entry.getValue()).getAppender();
+ }
+ }
+ assertNotNull("No Event Appender", eventAppender);
+ List<LoggingEvent> events = eventAppender.getEvents();
+ assertTrue("No events", events != null && events.size() > 0);
+ assertNotNull("No properties in the event", events.get(0).getProperties());
+ assertTrue("Key was not inserted", events.get(0).getProperties().containsKey("hello"));
+ assertEquals("Key value is incorrect", "world", events.get(0).getProperties().get("hello"));
+ }
+}
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 0000000..de81b63
--- /dev/null
+++ b/log4j-1.2-api/src/test/java/org/apache/log4j/config/PropertiesConfigurationFactoryTest.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.log4j.config;
+
+import org.apache.log4j.LogManager;
+import org.apache.log4j.Logger;
+import org.apache.logging.log4j.core.config.ConfigurationFactory;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.io.File;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Test configuration from Properties.
+ */
+public class PropertiesConfigurationFactoryTest {
+
+ @BeforeClass
+ public static void beforeClass() {
+ System.setProperty(ConfigurationFactory.LOG4J1_CONFIGURATION_FILE_PROPERTY, "target/test-classes/log4j1-file.properties");
+ }
+
+ @Test
+ public void testProperties() throws Exception {
+ Logger logger = LogManager.getLogger("test");
+ logger.debug("This is a test of the root logger");
+ File file = new File("target/temp.A1");
+ assertTrue("File A1 was not created", file.exists());
+ assertTrue("File A1 is empty", file.length() > 0);
+ file = new File("target/temp.A2");
+ assertTrue("File A2 was not created", file.exists());
+ assertTrue("File A2 is empty", file.length() > 0);
+ }
+
+}
diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/config/PropertiesConfigurationTest.java b/log4j-1.2-api/src/test/java/org/apache/log4j/config/PropertiesConfigurationTest.java
new file mode 100644
index 0000000..72ff293
--- /dev/null
+++ b/log4j-1.2-api/src/test/java/org/apache/log4j/config/PropertiesConfigurationTest.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.log4j.config;
+
+import org.apache.log4j.ListAppender;
+import org.apache.log4j.LogManager;
+import org.apache.log4j.Logger;
+import org.apache.log4j.bridge.AppenderAdapter;
+import org.apache.log4j.spi.LoggingEvent;
+import org.apache.logging.log4j.core.Appender;
+import org.apache.logging.log4j.core.LoggerContext;
+import org.apache.logging.log4j.core.config.Configuration;
+import org.apache.logging.log4j.core.config.ConfigurationSource;
+import org.apache.logging.log4j.core.config.Configurator;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Test configuration from Properties.
+ */
+public class PropertiesConfigurationTest {
+
+ @Test
+ public void testProperties() throws Exception {
+ configure("target/test-classes/log4j1-file.properties");
+ Logger logger = LogManager.getLogger("test");
+ logger.debug("This is a test of the root logger");
+ File file = new File("target/temp.A1");
+ assertTrue("File A1 was not created", file.exists());
+ assertTrue("File A1 is empty", file.length() > 0);
+ file = new File("target/temp.A2");
+ assertTrue("File A2 was not created", file.exists());
+ assertTrue("File A2 is empty", file.length() > 0);
+ }
+
+ @Test
+ public void testListAppender() throws Exception {
+ LoggerContext loggerContext = configure("target/test-classes/log4j1-list.properties");
+ Logger logger = LogManager.getLogger("test");
+ logger.debug("This is a test of the root logger");
+ Configuration configuration = loggerContext.getConfiguration();
+ Map<String, Appender> appenders = configuration.getAppenders();
+ ListAppender eventAppender = null;
+ ListAppender messageAppender = null;
+ for (Map.Entry<String, Appender> 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("No Event Appender", eventAppender);
+ assertNotNull("No Message Appender", messageAppender);
+ List<LoggingEvent> events = eventAppender.getEvents();
+ assertTrue("No events", events != null && events.size() > 0);
+ List<String> messages = messageAppender.getMessages();
+ assertTrue("No messages", messages != null && messages.size() > 0);
+ }
+
+ private LoggerContext configure(String configLocation) throws Exception {
+ File file = new File(configLocation);
+ InputStream is = new FileInputStream(file);
+ ConfigurationSource source = new ConfigurationSource(is, file);
+ LoggerContext context = (LoggerContext) org.apache.logging.log4j.LogManager.getContext(false);
+ Configuration configuration = new PropertiesConfigurationFactory().getConfiguration(context, source);
+ assertNotNull("No configuration created", configuration);
+ Configurator.reconfigure(configuration);
+ return context;
+ }
+
+}
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 0000000..d7aca5c
--- /dev/null
+++ b/log4j-1.2-api/src/test/java/org/apache/log4j/config/RewriteAppenderTest.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.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.After;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Test RewriteAppender
+ */
+public class RewriteAppenderTest {
+
+ @BeforeClass
+ public static void beforeClass() {
+ System.setProperty(ConfigurationFactory.LOG4J1_CONFIGURATION_FILE_PROPERTY, "target/test-classes/log4j1-rewrite.xml");
+ }
+
+ @After
+ public void after() {
+ ThreadContext.clearMap();
+ }
+
+ @Test
+ public void testRewrite() throws Exception {
+ Logger logger = LogManager.getLogger("test");
+ ThreadContext.put("key1", "This is a test");
+ ThreadContext.put("hello", "world");
+ logger.debug("Say hello");
+ LoggerContext context = (LoggerContext) org.apache.logging.log4j.LogManager.getContext(false);
+ Configuration configuration = context.getConfiguration();
+ Map<String, Appender> appenders = configuration.getAppenders();
+ ListAppender eventAppender = null;
+ for (Map.Entry<String, Appender> entry : appenders.entrySet()) {
+ if (entry.getKey().equals("events")) {
+ eventAppender = (ListAppender) ((AppenderAdapter.Adapter) entry.getValue()).getAppender();
+ }
+ }
+ assertNotNull("No Event Appender", eventAppender);
+ List<LoggingEvent> events = eventAppender.getEvents();
+ assertTrue("No events", events != null && events.size() > 0);
+ assertNotNull("No properties in the event", events.get(0).getProperties());
+ assertTrue("Key was not inserted", events.get(0).getProperties().containsKey("key2"));
+ assertEquals("Key value is incorrect", "Log4j", events.get(0).getProperties().get("key2"));
+ }
+}
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 0000000..7ccb463
--- /dev/null
+++ b/log4j-1.2-api/src/test/java/org/apache/log4j/config/SyslogAppenderTest.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.config;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.core.net.mock.MockSyslogServer;
+import org.apache.logging.log4j.core.net.mock.MockSyslogServerFactory;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertNotNull;
+
+
+/**
+ * Class Description goes here.
+ */
+public class SyslogAppenderTest {
+
+ private static final int PORTNUM = 9999;
+ private MockSyslogServer syslogServer;
+
+ @BeforeClass
+ public static void beforeClass() {
+ System.setProperty("log4j.configuration", "target/test-classes/log4j1-syslog.xml");
+ }
+
+ @Before
+ public void setUp() {
+ }
+
+ @After
+ public void teardown() {
+ if (syslogServer != null) {
+ syslogServer.shutdown();
+ }
+ }
+
+ @Test
+ public void sendMessage() throws Exception {
+ initTCPTestEnvironment(null);
+ Logger logger = LogManager.getLogger(SyslogAppenderTest.class);
+ logger.info("This is a test");
+ List<String> messages = null;
+ for (int i = 0; i < 5; ++i) {
+ Thread.sleep(250);
+ messages = syslogServer.getMessageList();
+ if (messages != null && messages.size() > 0) {
+ break;
+ }
+ }
+ assertNotNull("No messages received", messages);
+ assertEquals("Sent message not detected", 1, messages.size());
+ }
+
+
+ protected void initTCPTestEnvironment(final String messageFormat) throws IOException {
+ syslogServer = MockSyslogServerFactory.createTCPSyslogServer(1, PORTNUM);
+ syslogServer.start();
+ }
+}
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 0000000..65b8d47
--- /dev/null
+++ b/log4j-1.2-api/src/test/java/org/apache/log4j/config/XmlConfigurationFactoryTest.java
@@ -0,0 +1,55 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.log4j.config;
+
+import org.apache.log4j.LogManager;
+import org.apache.log4j.Logger;
+import org.apache.logging.log4j.core.config.ConfigurationFactory;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Test configuration from XML.
+ */
+public class XmlConfigurationFactoryTest {
+
+ @BeforeClass
+ public static void beforeClass() {
+ System.setProperty(ConfigurationFactory.LOG4J1_CONFIGURATION_FILE_PROPERTY, "target/test-classes/log4j1-file.xml");
+ }
+ @Test
+ public void testXML() throws Exception {
+ Logger logger = LogManager.getLogger("test");
+ logger.debug("This is a test of the root logger");
+ File file = new File("target/temp.A1");
+ assertTrue("File A1 was not created", file.exists());
+ assertTrue("File A1 is empty", file.length() > 0);
+ file = new File("target/temp.A2");
+ assertTrue("File A2 was not created", file.exists());
+ assertTrue("File A2 is empty", file.length() > 0);
+ }
+
+}
diff --git a/log4j-1.2-api/src/test/java/org/apache/log4j/config/XmlConfigurationTest.java b/log4j-1.2-api/src/test/java/org/apache/log4j/config/XmlConfigurationTest.java
new file mode 100644
index 0000000..c4cc360
--- /dev/null
+++ b/log4j-1.2-api/src/test/java/org/apache/log4j/config/XmlConfigurationTest.java
@@ -0,0 +1,97 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.log4j.config;
+
+import 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.config.Configuration;
+import org.apache.logging.log4j.core.config.ConfigurationSource;
+import org.apache.logging.log4j.core.config.Configurator;
+import org.apache.logging.log4j.core.LoggerContext;
+import org.apache.logging.log4j.spi.LoggerContextFactory;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.InputStream;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Test configuration from XML.
+ */
+public class XmlConfigurationTest {
+
+ @Test
+ public void testXML() throws Exception {
+ configure("target/test-classes/log4j1-file.xml");
+ Logger logger = LogManager.getLogger("test");
+ logger.debug("This is a test of the root logger");
+ File file = new File("target/temp.A1");
+ assertTrue("File A1 was not created", file.exists());
+ assertTrue("File A1 is empty", file.length() > 0);
+ file = new File("target/temp.A2");
+ assertTrue("File A2 was not created", file.exists());
+ assertTrue("File A2 is empty", file.length() > 0);
+ }
+
+ @Test
+ public void testListAppender() throws Exception {
+ LoggerContext loggerContext = configure("target/test-classes/log4j1-list.xml");
+ Logger logger = LogManager.getLogger("test");
+ logger.debug("This is a test of the root logger");
+ Configuration configuration = loggerContext.getConfiguration();
+ Map<String, Appender> appenders = configuration.getAppenders();
+ ListAppender eventAppender = null;
+ ListAppender messageAppender = null;
+ for (Map.Entry<String, Appender> 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("No Event Appender", eventAppender);
+ assertNotNull("No Message Appender", messageAppender);
+ List<LoggingEvent> events = eventAppender.getEvents();
+ assertTrue("No events", events != null && events.size() > 0);
+ List<String> messages = messageAppender.getMessages();
+ assertTrue("No messages", messages != null && messages.size() > 0);
+ }
+
+ private LoggerContext configure(String configLocation) throws Exception {
+ File file = new File(configLocation);
+ InputStream is = new FileInputStream(file);
+ ConfigurationSource source = new ConfigurationSource(is, file);
+ LoggerContextFactory factory = org.apache.logging.log4j.LogManager.getFactory();
+ LoggerContext context = (LoggerContext) org.apache.logging.log4j.LogManager.getContext(false);
+ Configuration configuration = new XmlConfigurationFactory().getConfiguration(context, source);
+ assertNotNull("No configuration created", configuration);
+ Configurator.reconfigure(configuration);
+ return context;
+ }
+
+}
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 0000000..065c748
--- /dev/null
+++ b/log4j-1.2-api/src/test/resources/log4j.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-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 "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
+
+<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
+ <appender name="list" class="org.apache.log4j.ListAppender">
+ <layout class="org.apache.log4j.PatternLayout">
+ <param name="ConversionPattern" value="%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n" />
+ </layout>
+ </appender>
+
+ <appender name="events" class="org.apache.log4j.ListAppender">
+ </appender>
+
+ <root>
+ <priority value ="trace" />
+ <appender-ref ref="list" />
+ <appender-ref ref="events" />
+ </root>
+
+</log4j:configuration>
\ No newline at end of file
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 0000000..8e80b46
--- /dev/null
+++ b/log4j-1.2-api/src/test/resources/log4j1-async.properties
@@ -0,0 +1,21 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+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
\ No newline at end of file
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 0000000..a0cb7f6
--- /dev/null
+++ b/log4j-1.2-api/src/test/resources/log4j1-async.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-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 "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
+
+<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
+ <appender name="list" class="org.apache.log4j.ListAppender">
+ <layout class="org.apache.log4j.PatternLayout">
+ <param name="ConversionPattern" value="%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n" />
+ </layout>
+ </appender>
+ <appender name="async" class="org.apache.log4j.AsyncAppender">
+ <appender-ref ref="list"/>
+ </appender>
+
+ <root>
+ <priority value ="trace" />
+ <appender-ref ref="async" />
+ </root>
+
+</log4j:configuration>
\ No newline at end of file
diff --git a/log4j-1.2-api/src/test/resources/log4j1-file.properties b/log4j-1.2-api/src/test/resources/log4j1-file.properties
new file mode 100644
index 0000000..ee870df
--- /dev/null
+++ b/log4j-1.2-api/src/test/resources/log4j1-file.properties
@@ -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.
+
+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
\ No newline at end of file
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 0000000..bc7df16
--- /dev/null
+++ b/log4j-1.2-api/src/test/resources/log4j1-file.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="UTF-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 "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
+
+<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
+ <appender name="console" class="org.apache.log4j.ConsoleAppender">
+ <param name="Target" value="System.out"/>
+ <layout class="org.apache.log4j.PatternLayout">
+ <param name="ConversionPattern" value="%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n" />
+ </layout>
+ </appender>
+
+ <appender name="A1" class="org.apache.log4j.FileAppender">
+
+ <param name="File" value="target/temp.A1" />
+ <param name="Append" value="false" />
+
+ <layout class="org.apache.log4j.PatternLayout">
+ <param name="ConversionPattern" value="%-5p %c{2} - %m%n"/>
+ </layout>
+ </appender>
+
+ <appender name="A2" class="org.apache.log4j.FileAppender">
+ <param name="File" value="target/temp.A2" />
+ <param name="Append" value="false" />
+ <layout class="org.apache.log4j.TTCCLayout">
+ <param name="DateFormat" value="ISO8601" />
+ </layout>
+ </appender>
+
+ <logger name="org.apache.log4j.xml">
+ <level value="trace" />
+ <appender-ref ref="A1" />
+ </logger>
+
+ <root>
+ <priority value ="trace" />
+ <appender-ref ref="A1" />
+ <appender-ref ref="A2" />
+ </root>
+
+</log4j:configuration>
\ No newline at end of file
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 0000000..43d6208
--- /dev/null
+++ b/log4j-1.2-api/src/test/resources/log4j1-list.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.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
\ No newline at end of file
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 0000000..065c748
--- /dev/null
+++ b/log4j-1.2-api/src/test/resources/log4j1-list.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-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 "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
+
+<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
+ <appender name="list" class="org.apache.log4j.ListAppender">
+ <layout class="org.apache.log4j.PatternLayout">
+ <param name="ConversionPattern" value="%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n" />
+ </layout>
+ </appender>
+
+ <appender name="events" class="org.apache.log4j.ListAppender">
+ </appender>
+
+ <root>
+ <priority value ="trace" />
+ <appender-ref ref="list" />
+ <appender-ref ref="events" />
+ </root>
+
+</log4j:configuration>
\ No newline at end of file
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 0000000..19973f4
--- /dev/null
+++ b/log4j-1.2-api/src/test/resources/log4j1-mapRewrite.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-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 "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<!--<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">-->
+
+<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
+ <appender name="events" class="org.apache.log4j.ListAppender">
+ </appender>
+
+ <appender name="rewrite" class="org.apache.log4j.rewrite.RewriteAppender">
+ <appender-ref ref="events"/>
+ <rewritePolicy class="org.apache.log4j.rewrite.MapRewritePolicy">
+ </rewritePolicy>
+ </appender>
+
+ <root>
+ <priority value ="trace" />
+ <appender-ref ref="rewrite" />
+ </root>
+
+</log4j:configuration>
\ No newline at end of file
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 0000000..25eb1b5
--- /dev/null
+++ b/log4j-1.2-api/src/test/resources/log4j1-rewrite.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-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 "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<!--<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">-->
+
+<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
+ <appender name="events" class="org.apache.log4j.ListAppender">
+ </appender>
+
+ <appender name="rewrite" class="org.apache.log4j.rewrite.RewriteAppender">
+ <appender-ref ref="events"/>
+ <rewritePolicy class="org.apache.log4j.rewrite.PropertyRewritePolicy">
+ <param name="properties" value="key2=Log4j"/>
+ </rewritePolicy>
+ </appender>
+
+ <root>
+ <priority value ="trace" />
+ <appender-ref ref="rewrite" />
+ </root>
+
+</log4j:configuration>
\ No newline at end of file
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 0000000..4fd96a8
--- /dev/null
+++ b/log4j-1.2-api/src/test/resources/log4j1-syslog.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-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 "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
+
+<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
+ <appender name="syslog" class="org.apache.log4j.net.SyslogAppender">
+ <param name="SyslogHost" value="localhost:9999"/>
+ <param name="Facility" value="USER"/>
+ <param name="FacilityPrinting" value="true"/>
+ <param name="Threshold" value="DEBUG"/>
+ <layout class="org.apache.log4j.PatternLayout">
+ <param name="ConversionPattern" value="%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n" />
+ </layout>
+ </appender>
+
+ <root>
+ <priority value ="trace" />
+ <appender-ref ref="syslog" />
+ </root>
+
+</log4j:configuration>
\ No newline at end of file
diff --git a/log4j-api-java9/pom.xml b/log4j-api-java9/pom.xml
index 6d6e534..e9bad12 100644
--- a/log4j-api-java9/pom.xml
+++ b/log4j-api-java9/pom.xml
@@ -60,7 +60,7 @@
<configuration>
<toolchains>
<jdk>
- <version>[9, )</version>
+ <version>[11, )</version>
</jdk>
</toolchains>
</configuration>
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 2cd658f..8cd6ae6 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
@@ -17,8 +17,11 @@
package org.apache.logging.log4j.util;
import java.util.List;
+import java.util.Optional;
import java.util.Stack;
+import java.util.function.Function;
import java.util.stream.Collectors;
+import java.util.stream.Stream;
/**
* <em>Consider this class private.</em> Determines the caller's class.
@@ -31,7 +34,6 @@
private final static StackLocator INSTANCE = new StackLocator();
-
public static StackLocator getInstance() {
return INSTANCE;
}
@@ -56,7 +58,6 @@
}
public Class<?> getCallerClass(final int depth) {
- ;
return walker.walk(s -> s.skip(depth).findFirst()).map(StackWalker.StackFrame::getDeclaringClass).orElse(null);
}
@@ -75,12 +76,11 @@
return stackWalker.walk(
s -> s.dropWhile(f -> !f.getClassName().equals(fqcnOfLogger)) // drop the top frames until we reach the logger
.dropWhile(f -> f.getClassName().equals(fqcnOfLogger)) // drop the logger frames
- .findFirst())
- .get()
- .toStackTraceElement();
+ .findFirst()).map(StackWalker.StackFrame::toStackTraceElement).orElse(null);
}
public StackTraceElement getStackTraceElement(final int depth) {
- return stackWalker.walk(s -> s.skip(depth).findFirst()).get().toStackTraceElement();
+ return stackWalker.walk(s -> s.skip(depth).findFirst())
+ .map(StackWalker.StackFrame::toStackTraceElement).orElse(null);
}
}
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
index 77396c9..bf76fc0 100644
--- 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
@@ -26,6 +26,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
@RunWith(BlockJUnit4ClassRunner.class)
@@ -132,7 +133,13 @@
*/
final StackTraceElement element = new Foo().foo();
assertEquals("org.apache.logging.log4j.util.StackLocatorTest$Foo", element.getClassName());
- assertEquals(100, element.getLineNumber());
+ assertEquals(101, element.getLineNumber());
+ }
+
+ @Test
+ public void testCalcLocationWhenNotInTheStack() {
+ final StackTraceElement stackTraceElement = stackLocator.calcLocation("java.util.Logger");
+ assertNull(stackTraceElement);
}
class ClassLocator {
diff --git a/log4j-api/pom.xml b/log4j-api/pom.xml
index 8cb497b..a7bd7ce 100644
--- a/log4j-api/pom.xml
+++ b/log4j-api/pom.xml
@@ -142,8 +142,8 @@
<id>default-compile</id>
<!-- recompile everything for target VM except the module-info.java -->
<configuration>
- <source>1.7</source>
- <target>1.7</target>
+ <source>1.8</source>
+ <target>1.8</target>
</configuration>
</execution>
</executions>
@@ -222,9 +222,6 @@
<Export-Package>org.apache.logging.log4j.*</Export-Package>
<Import-Package>
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,
*
</Import-Package>
<Bundle-Activator>org.apache.logging.log4j.util.Activator</Bundle-Activator>
@@ -242,11 +239,6 @@
<reporting>
<plugins>
<plugin>
- <groupId>org.codehaus.mojo</groupId>
- <artifactId>clirr-maven-plugin</artifactId>
- <version>${clirr.plugin.version}</version>
- </plugin>
- <plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-changes-plugin</artifactId>
<version>${changes.plugin.version}</version>
@@ -292,6 +284,7 @@
<links>
<link>http://www.osgi.org/javadoc/r4v43/core/</link>
</links>
+ <source>8</source>
</configuration>
<reportSets>
<reportSet>
diff --git a/log4j-api/revapi.json b/log4j-api/revapi.json
new file mode 100644
index 0000000..7c36e64
--- /dev/null
+++ b/log4j-api/revapi.json
@@ -0,0 +1,115 @@
+[
+ {
+ "extension": "revapi.java",
+ "configuration": {
+ "filter": {
+ "classes": {
+ "exclude": [
+ "org\\.apache\\.logging\\.log4j\\.util\\.Activator",
+ "org\\.apache\\.logging\\.log4j\\.util\\.LoaderUtil",
+ "org\\.apache\\.logging\\.log4j\\.util\\.PrivateSecurityManagerStackTraceUtil",
+ "org\\.apache\\.logging\\.log4j\\.util\\.PropertiesUtil",
+ "org\\.apache\\.logging\\.log4j\\.util\\.ProviderUtil",
+ "org\\.apache\\.logging\\.log4j\\.util\\.StackLocatorUtil"
+ ]
+ }
+ }
+ }
+ },
+ {
+ "extension": "revapi.ignore",
+ "configuration": [
+ {
+ "code": "java.method.removed",
+ "old": "method void org.apache.logging.log4j.Logger::entry()",
+ "justification": "Removed deprecated method"
+ },
+ {
+ "code": "java.method.removed",
+ "old": "method void org.apache.logging.log4j.Logger::entry(java.lang.Object[])",
+ "justification": "Removed deprectated method"
+ },
+ {
+ "code": "java.method.removed",
+ "old": "method void org.apache.logging.log4j.Logger::exit()",
+ "justification": "Removed deprecated method"
+ },
+ {
+ "code": "java.method.removed",
+ "old": "method <R> R org.apache.logging.log4j.Logger::exit(R)",
+ "justification": "Removed deprecated method"
+ },
+ {
+ "code": "java.method.removed",
+ "old": "method org.apache.logging.log4j.Marker org.apache.logging.log4j.MarkerManager::getMarker(java.lang.String, java.lang.String)",
+ "justification": "Removed deprecated method"
+ },
+ {
+ "code": "java.method.removed",
+ "old": "method org.apache.logging.log4j.Marker org.apache.logging.log4j.MarkerManager::getMarker(java.lang.String, org.apache.logging.log4j.Marker)",
+ "justification": "Removed deprecated method"
+ },
+ {
+ "code": "java.method.removed",
+ "old": "method void org.apache.logging.log4j.message.ParameterizedMessage::<init>(java.lang.String, java.lang.String[], java.lang.Throwable)",
+ "justification": "Removed deprecated method"
+ },
+ {
+ "code": "java.method.removed",
+ "old": "method org.apache.logging.log4j.message.EntryMessage org.apache.logging.log4j.spi.AbstractLogger::enter(java.lang.String, java.lang.String, org.apache.logging.log4j.util.MessageSupplier[])",
+ "justification": "Removed deprecated method"
+ },
+ {
+ "code": "java.method.removed",
+ "old": "method org.apache.logging.log4j.message.EntryMessage org.apache.logging.log4j.spi.AbstractLogger::enter(java.lang.String, org.apache.logging.log4j.util.MessageSupplier)",
+ "justification": "Removed deprecated method"
+ },
+ {
+ "code": "java.method.removed",
+ "old": "method void org.apache.logging.log4j.spi.AbstractLogger::entry()",
+ "justification": "Removed deprecated method"
+ },
+ {
+ "code": "java.method.removed",
+ "old": "method void org.apache.logging.log4j.spi.AbstractLogger::entry(java.lang.Object[])",
+ "justification": "Removed deprecated method"
+ },
+ {
+ "code": "java.method.removed",
+ "old": "method void org.apache.logging.log4j.spi.AbstractLogger::exit()",
+ "justification": "Removed deprecated method"
+ },
+ {
+ "code": "java.method.removed",
+ "old": "method <R> R org.apache.logging.log4j.spi.AbstractLogger::exit(R)",
+ "justification": "Removed deprecated method"
+ },
+ {
+ "code": "java.class.removed",
+ "old": "class org.apache.logging.log4j.spi.LoggerContextKey",
+ "justification": "Removed deprecated method"
+ },
+ {
+ "code": "java.class.removed",
+ "old": "class org.apache.logging.log4j.util.ProcessIdUtil",
+ "justification": "Removed deprecated method"
+ },
+ {
+ "code": "java.method.removed",
+ "old": "method void org.apache.logging.log4j.util.ProviderUtil::loadProviders(java.util.Enumeration<java.net.URL>, java.lang.ClassLoader)",
+ "justification": "Removed deprecated method"
+ },
+ {
+ "code": "java.method.removed",
+ "old": "method org.apache.logging.log4j.util.StackLocator.PrivateSecurityManager org.apache.logging.log4j.util.StackLocator::getSecurityManager()",
+ "justification": "Internal inner class moved to its own class so it can be shared with Java 9"
+ },
+ {
+ "code": "java.method.nowStatic",
+ "old": "method java.lang.String org.apache.logging.log4j.util.Strings::toRootUpperCase(java.lang.String)",
+ "new": "method java.lang.String org.apache.logging.log4j.util.Strings::toRootUpperCase(java.lang.String)",
+ "justification": "Method was inaccessible"
+ }
+ ]
+ }
+]
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/Level.java b/log4j-api/src/main/java/org/apache/logging/log4j/Level.java
index cbb4dc7..58507d0 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
@@ -284,7 +284,7 @@
if (name == null) {
return defaultLevel;
}
- final Level level = LEVELS.get(toUpperCase(name));
+ final Level level = LEVELS.get(toUpperCase(name.trim()));
return level == null ? defaultLevel : level;
}
@@ -312,7 +312,7 @@
*/
public static Level valueOf(final String name) {
Objects.requireNonNull(name, "No level name given.");
- final String levelName = toUpperCase(name);
+ final String levelName = toUpperCase(name.trim());
final Level level = LEVELS.get(levelName);
if (level != null) {
return level;
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/LogBuilder.java b/log4j-api/src/main/java/org/apache/logging/log4j/LogBuilder.java
new file mode 100644
index 0000000..0119688
--- /dev/null
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/LogBuilder.java
@@ -0,0 +1,234 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.logging.log4j;
+
+import org.apache.logging.log4j.message.Message;
+import org.apache.logging.log4j.util.Supplier;
+
+
+/**
+ * Interface for constructing log events before logging them. Instances of LogBuilders should only be created
+ * by calling one of the Logger methods that return a LogBuilder.
+ */
+public interface LogBuilder {
+
+ public static final LogBuilder NOOP = new LogBuilder() {};
+
+ default LogBuilder withMarker(Marker marker) {
+ return this;
+ }
+
+ default LogBuilder withThrowable(Throwable throwable) {
+ return this;
+ }
+
+ default LogBuilder withLocation() {
+ return this;
+ }
+
+ default LogBuilder withLocation(StackTraceElement location) {
+ return this;
+ }
+
+ default void log(CharSequence message) {
+ }
+
+ default void log(String message) {
+ }
+
+ /**
+ * Logs a message with parameters.
+ *
+ * @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(String message, Object... params) {
+ }
+
+ default void log(String message, Supplier<?>... params) {
+ }
+
+ default void log(Message message) {
+ }
+
+ default void log(Supplier<Message> messageSupplier) {
+ }
+
+ default void log(Object message) {
+
+ }
+
+ /**
+ * Logs a message with parameters.
+ *
+ * @param message the message to log; the format depends on the message factory.
+ * @param p0 parameter to the message.
+ *
+ * @see org.apache.logging.log4j.util.Unbox
+ */
+ default void log(String message, Object p0) {
+ }
+
+ /**
+ * 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.
+ *
+ * @see org.apache.logging.log4j.util.Unbox
+ */
+ default void log(String message, Object p0, Object p1) {
+ }
+
+ /**
+ * 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.
+ *
+ * @see org.apache.logging.log4j.util.Unbox
+ */
+ default void log(String message, Object p0, Object p1, Object p2) {
+ }
+
+ /**
+ * 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.
+ *
+ * @see org.apache.logging.log4j.util.Unbox
+ */
+ default void log(String message, Object p0, Object p1, Object p2, Object p3) {
+ }
+
+ /**
+ * 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.
+ *
+ * @see org.apache.logging.log4j.util.Unbox
+ */
+ default void log(String message, Object p0, Object p1, Object p2, Object p3, Object p4) {
+ }
+
+ /**
+ * 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.
+ *
+ * @see org.apache.logging.log4j.util.Unbox
+ */
+ default void log(String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5) {
+ }
+
+ /**
+ * Logs a message with parameters.
+ *
+ * @param message the message to log; the format depends on the message factory.
+ * @param p0 parameter to the message.
+ * @param p1 parameter to the message.
+ * @param p2 parameter to the message.
+ * @param p3 parameter to the message.
+ * @param p4 parameter to the message.
+ * @param p5 parameter to the message.
+ * @param p6 parameter to the message.
+ *
+ * @see org.apache.logging.log4j.util.Unbox
+ */
+ default void log(String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, Object p6) {
+ }
+
+ /**
+ * 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.
+ * @param p7 parameter to the message.
+ *
+ * @see org.apache.logging.log4j.util.Unbox
+ */
+ default void log(String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, Object p6,
+ Object p7) {
+ }
+
+ /**
+ * 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.
+ * @param p7 parameter to the message.
+ * @param p8 parameter to the message.
+ *
+ * @see org.apache.logging.log4j.util.Unbox
+ */
+ default void log(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.
+ *
+ * @param message the message to log; the format depends on the message factory.
+ * @param p0 parameter to the message.
+ * @param p1 parameter to the message.
+ * @param p2 parameter to the message.
+ * @param p3 parameter to the message.
+ * @param p4 parameter to the message.
+ * @param p5 parameter to the message.
+ * @param p6 parameter to the message.
+ * @param p7 parameter to the message.
+ * @param p8 parameter to the message.
+ * @param p9 parameter to the message.
+ *
+ * @see org.apache.logging.log4j.util.Unbox
+ */
+ default void log(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/LogManager.java b/log4j-api/src/main/java/org/apache/logging/log4j/LogManager.java
index ecff443..45b83ff 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
@@ -387,7 +387,27 @@
* @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.
+ *
+ * @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);
}
/**
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 995c6df..729e92f 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
@@ -19,7 +19,6 @@
import org.apache.logging.log4j.message.EntryMessage;
import org.apache.logging.log4j.message.Message;
import org.apache.logging.log4j.message.MessageFactory;
-import org.apache.logging.log4j.message.MessageFactory2;
import org.apache.logging.log4j.util.MessageSupplier;
import org.apache.logging.log4j.util.Supplier;
@@ -33,12 +32,12 @@
* {@link LogManager#getLogger()} method). Thus, the simplest way to use this would be like so:
* </p>
*
- * <pre>
+ * <pre><code>
* public class MyClass {
* private static final Logger LOGGER = LogManager.getLogger();
* // ...
* }
- * </pre>
+ * </code></pre>
* <p>
* For ease of filtering, searching, sorting, etc., it is generally a good idea to create Loggers for each class rather
* than sharing Loggers. Instead, {@link Marker Markers} should be used for shared, filterable identification.
@@ -52,13 +51,13 @@
* allow client code to lazily log messages without explicitly checking if the requested log level is enabled. For
* example, previously one would write:
*
- * <pre>
+ * <pre><code>
* // pre-Java 8 style optimization: explicitly check the log level
* // to make sure the expensiveOperation() method is only called if necessary
* if (logger.isTraceEnabled()) {
* logger.trace("Some long-running operation returned {}", expensiveOperation());
* }
- * </pre>
+ * </code></pre>
* <p>
* With Java 8, the same effect can be achieved with a lambda expression:
*
@@ -69,7 +68,7 @@
* </pre>
*
* <p>
- * Note that although {@link MessageSupplier} is provided, using {@link Supplier Supplier<Message>} works just the
+ * Note that although {@link MessageSupplier} is provided, using {@link Supplier {@code 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.
* </p>
@@ -621,34 +620,6 @@
Object p8, Object p9);
/**
- * Logs entry to a method. Used when the method in question has no parameters or when the parameters should not be
- * logged.
- * @deprecated Use {@link #traceEntry()} instead which performs the same function.
- */
- @Deprecated
- void entry();
-
- /**
- * Logs entry to a method along with its parameters (consider using one of the {@code traceEntry(...)} methods instead.)
- * <p>
- * For example:
- * </p>
- * <pre>
- * public void doSomething(String foo, int bar) {
- * LOGGER.entry(foo, bar);
- * // do something
- * }
- * </pre>
- * <p>
- * The use of methods such as this are more effective when combined with aspect-oriented programming or other
- * bytecode manipulation tools. It can be rather tedious (and messy) to use this type of method manually.
- * </p>
- *
- * @param params The parameters to the method.
- */
- void entry(Object... params);
-
- /**
* Logs a message with the specific Marker at the {@link Level#ERROR ERROR} level.
*
* @param marker the marker data specific to this log statement
@@ -1175,28 +1146,6 @@
Object p8, Object p9);
/**
- * Logs exit from a method. Used for methods that do not return anything.
- * @deprecated Use {@link #traceExit()} instead which performs the same function.
- */
- @Deprecated
- void exit();
-
- /**
- * Logs exiting from a method with the result. This may be coded as:
- *
- * <pre>
- * return LOGGER.exit(myResult);
- * </pre>
- *
- * @param <R> The type of the parameter and object being returned.
- * @param result The result being returned from the method call.
- * @return the result.
- * @deprecated Use {@link #traceExit(Object)} instead which performs the same function.
- */
- @Deprecated
- <R> R exit(R result);
-
- /**
* Logs a message with the specific Marker at the {@link Level#FATAL FATAL} level.
*
* @param marker the marker data specific to this log statement
@@ -1732,13 +1681,8 @@
/**
* 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 extends MessageFactory> MF}. The returned factory will always implement {@link MessageFactory2},
- * but the return type of this method could not be changed to {@link MessageFactory2} without breaking binary
- * compatibility.
- *
- * @return the message factory, as an instance of {@link MessageFactory2}
+ * @param <MF> The type of the MessageFactory.
+ * @return the message factory, as an instance of {@link MessageFactory}
*/
<MF extends MessageFactory> MF getMessageFactory();
@@ -3552,20 +3496,20 @@
/**
* Logs entry to a method along with its parameters. For example,
*
- * <pre>
+ * <pre><code>
* public void doSomething(String foo, int bar) {
* LOGGER.traceEntry("Parameters: {} and {}", foo, bar);
* // do something
* }
- * </pre>
+ * </code></pre>
* or:
- * <pre>
+ * <pre><code>
* public int doSomething(String foo, int bar) {
* Message m = LOGGER.traceEntry("doSomething(foo={}, bar={})", foo, bar);
* // do something
* return traceExit(m, value);
* }
- * </pre>
+ * </code></pre>
*
* @param format The format String for the parameters.
* @param params The parameters to the method.
@@ -3579,10 +3523,12 @@
* Logs entry to a method along with its parameters. For example,
*
* <pre>
+ * <code>
* public void doSomething(Request foo) {
- * LOGGER.traceEntry(()->gson.toJson(foo));
+ * LOGGER.traceEntry(()->gson.toJson(foo));
* // do something
* }
+ * </code>
* </pre>
*
* @param paramSuppliers The Suppliers for the parameters to the method.
@@ -3596,10 +3542,12 @@
* Logs entry to a method along with its parameters. For example,
*
* <pre>
+ * <code>
* public void doSomething(String foo, int bar) {
- * LOGGER.traceEntry("Parameters: {} and {}", ()->gson.toJson(foo), ()-> bar);
+ * LOGGER.traceEntry("Parameters: {} and {}", ()->gson.toJson(foo), ()-> bar);
* // do something
* }
+ * </code>
* </pre>
*
* @param format The format String for the parameters.
@@ -3612,12 +3560,12 @@
/**
* Logs entry to a method using a Message to describe the parameters.
- * <pre>
+ * <pre><code>
* public void doSomething(Request foo) {
* LOGGER.traceEntry(new JsonMessage(foo));
* // do something
* }
- * </pre>
+ * </code></pre>
* <p>
* Avoid passing a {@code ReusableMessage} to this method (therefore, also avoid passing messages created by
* calling {@code logger.getMessageFactory().newMessage("some message")}): Log4j will replace such messages with
@@ -3643,9 +3591,9 @@
/**
* Logs exiting from a method with the result. This may be coded as:
*
- * <pre>
+ * <pre><code>
* return LOGGER.traceExit(myResult);
- * </pre>
+ * </code></pre>
*
* @param <R> The type of the parameter and object being returned.
* @param result The result being returned from the method call.
@@ -3658,9 +3606,9 @@
/**
* Logs exiting from a method with the result. This may be coded as:
*
- * <pre>
+ * <pre><code>
* return LOGGER.traceExit("Result: {}", myResult);
- * </pre>
+ * </code></pre>
*
* @param <R> The type of the parameter and object being returned.
* @param format The format String for the result.
@@ -3674,13 +3622,13 @@
/**
* Logs exiting from a method with no result. Allows custom formatting of the result. This may be coded as:
*
- * <pre>
+ * <pre><code>
* public long doSomething(int a, int b) {
* EntryMessage m = traceEntry("doSomething(a={}, b={})", a, b);
* // ...
* return LOGGER.traceExit(m);
* }
- * </pre>
+ * </code></pre>
* @param message The Message containing the formatted result.
*
* @since 2.6
@@ -3690,13 +3638,13 @@
/**
* Logs exiting from a method with the result. Allows custom formatting of the result. This may be coded as:
*
- * <pre>
+ * <pre><code>
* public long doSomething(int a, int b) {
* EntryMessage m = traceEntry("doSomething(a={}, b={})", a, b);
* // ...
* return LOGGER.traceExit(m, myResult);
* }
- * </pre>
+ * </code></pre>
* @param message The Message containing the formatted result.
* @param result The result being returned from the method call.
*
@@ -3710,9 +3658,9 @@
/**
* Logs exiting from a method with the result. Allows custom formatting of the result. This may be coded as:
*
- * <pre>
+ * <pre><code>
* return LOGGER.traceExit(new JsonMessage(myResult), myResult);
- * </pre>
+ * </code></pre>
* @param message The Message containing the formatted result.
* @param result The result being returned from the method call.
*
@@ -4249,4 +4197,86 @@
void warn(String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, Object p6, Object p7,
Object p8, Object p9);
+ /**
+ * Logs a Message.
+ * @param level The logging Level to check.
+ * @param marker A Marker or null.
+ * @param fqcn The fully qualified class name of the logger entry point, used to determine the caller class and
+ * method when location information needs to be logged.
+ * @param location The location of the caller.
+ * @param message The message format.
+ * @param throwable the exception to log, including its stack trace.
+ * @since 2.13.0
+ */
+ default void logMessage(Level level, Marker marker, String fqcn, StackTraceElement location, Message message,
+ Throwable throwable) {
+
+ }
+
+ /**
+ * Construct a trace log event.
+ * @return a LogBuilder.
+ * @since 2.13.0
+ */
+ default LogBuilder atTrace() {
+ return LogBuilder.NOOP;
+ }
+ /**
+ * Construct a trace log event.
+ * @return a LogBuilder.
+ * @since 2.13.0
+ */
+ default LogBuilder atDebug() {
+ return LogBuilder.NOOP;
+ }
+ /**
+ * Construct a trace log event.
+ * @return a LogBuilder.
+ * @since 2.13.0
+ */
+ default LogBuilder atInfo() {
+ return LogBuilder.NOOP;
+ }
+ /**
+ * Construct a trace log event.
+ * @return a LogBuilder.
+ * @since 2.13.0
+ */
+ default LogBuilder atWarn() {
+ return LogBuilder.NOOP;
+ }
+ /**
+ * Construct a trace log event.
+ * @return a LogBuilder.
+ * @since 2.13.0
+ */
+ default LogBuilder atError() {
+ return LogBuilder.NOOP;
+ }
+ /**
+ * Construct a trace log event.
+ * @return a LogBuilder.
+ * @since 2.13.0
+ */
+ default LogBuilder atFatal() {
+ return LogBuilder.NOOP;
+ }
+ /**
+ * Construct a log event that will always be logged.
+ * @return a LogBuilder.
+ * @since 2.13.0
+ */
+ default LogBuilder always() {
+ return LogBuilder.NOOP;
+ }
+ /**
+ * Construct a log event.
+ * @param level The Logging Level.
+ * @return a LogBuilder.
+ * @since 2.13.0
+ */
+ default LogBuilder atLevel(Level level) {
+ return LogBuilder.NOOP;
+ }
+
}
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/MarkerManager.java b/log4j-api/src/main/java/org/apache/logging/log4j/MarkerManager.java
index 8843883..46d5cb0 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
@@ -69,38 +69,6 @@
}
/**
- * Retrieves or creates a Marker with the specified parent. The parent must have been previously created.
- *
- * @param name The name of the Marker.
- * @param parent The name of the parent Marker.
- * @return The Marker with the specified name.
- * @throws IllegalArgumentException if the parent Marker does not exist.
- * @deprecated Use the Marker add or set methods to add parent Markers. Will be removed by final GA release.
- */
- @Deprecated
- public static Marker getMarker(final String name, final String parent) {
- final Marker parentMarker = MARKERS.get(parent);
- if (parentMarker == null) {
- throw new IllegalArgumentException("Parent Marker " + parent + " has not been defined");
- }
- return getMarker(name, parentMarker);
- }
-
- /**
- * Retrieves or creates a Marker with the specified parent.
- *
- * @param name The name of the Marker.
- * @param parent The parent Marker.
- * @return The Marker with the specified name.
- * @throws IllegalArgumentException if any argument is {@code null}
- * @deprecated Use the Marker add or set methods to add parent Markers. Will be removed by final GA release.
- */
- @Deprecated
- public static Marker getMarker(final String name, final Marker parent) {
- return getMarker(name).addParents(parent);
- }
-
- /**
* <em>Consider this class private, it is only public to satisfy Jackson for XML and JSON IO.</em>
* <p>
* The actual Marker implementation.
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 c4ae445..18471b7 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
@@ -192,8 +192,6 @@
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;
@@ -214,9 +212,9 @@
ThreadContextMapFactory.init();
contextMap = null;
final PropertiesUtil managerProps = PropertiesUtil.getProperties();
- disableAll = managerProps.getBooleanProperty(DISABLE_ALL);
+ boolean disableAll = managerProps.getBooleanProperty(DISABLE_ALL);
useStack = !(managerProps.getBooleanProperty(DISABLE_STACK) || disableAll);
- useMap = !(managerProps.getBooleanProperty(DISABLE_MAP) || disableAll);
+ boolean useMap = !(managerProps.getBooleanProperty(DISABLE_MAP) || disableAll);
contextStack = new DefaultThreadContextStack(useStack);
if (!useMap) {
@@ -247,6 +245,24 @@
}
/**
+ * Puts a context value (the <code>value</code> parameter) as identified with the <code>key</code> parameter into
+ * the current thread's context map if the key does not exist.
+ *
+ * <p>
+ * If the current thread does not have a context map it is created as a side effect.
+ * </p>
+ *
+ * @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.
*
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 0000000..0be9215
--- /dev/null
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/internal/DefaultLogBuilder.java
@@ -0,0 +1,241 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.logging.log4j.internal;
+
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.LogBuilder;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.Marker;
+import org.apache.logging.log4j.message.Message;
+import org.apache.logging.log4j.message.SimpleMessage;
+import org.apache.logging.log4j.status.StatusLogger;
+import org.apache.logging.log4j.util.LambdaUtil;
+import org.apache.logging.log4j.util.StackLocatorUtil;
+import org.apache.logging.log4j.util.Supplier;
+
+
+/**
+ * Collects data for a log event and then logs it. This class should be considered private.
+ */
+public class DefaultLogBuilder implements LogBuilder {
+
+ private static final String FQCN = DefaultLogBuilder.class.getName();
+ private static final Logger LOGGER = StatusLogger.getLogger();
+
+ private final Logger logger;
+ private Level level;
+ private Marker marker;
+ private Throwable throwable;
+ private StackTraceElement location;
+ private volatile boolean inUse;
+ private final long threadId;
+
+ public DefaultLogBuilder(Logger logger, Level level) {
+ this.logger = logger;
+ this.level = level;
+ this.threadId = Thread.currentThread().getId();
+ this.inUse = true;
+ }
+
+ public DefaultLogBuilder(Logger logger) {
+ this.logger = logger;
+ this.inUse = false;
+ this.threadId = Thread.currentThread().getId();
+ }
+
+ /**
+ * 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(Level level) {
+ this.inUse = true;
+ this.level = level;
+ this.marker = null;
+ this.throwable = null;
+ this.location = null;
+ return this;
+ }
+
+ public LogBuilder withMarker(Marker marker) {
+ this.marker = marker;
+ return this;
+ }
+
+ public LogBuilder withThrowable(Throwable throwable) {
+ this.throwable = throwable;
+ return this;
+ }
+
+ public LogBuilder withLocation() {
+ location = StackLocatorUtil.getStackTraceElement(2);
+ return this;
+ }
+
+ public LogBuilder withLocation(StackTraceElement location) {
+ this.location = location;
+ return this;
+ }
+
+ public boolean isInUse() {
+ return inUse;
+ }
+
+ @Override
+ public void log(Message message) {
+ if (isValid()) {
+ logMessage(message);
+ }
+ }
+
+ @Override
+ public void log(CharSequence message) {
+ if (isValid()) {
+ logMessage(logger.getMessageFactory().newMessage(message));
+ }
+ }
+
+ @Override
+ public void log(String message) {
+ if (isValid()) {
+ logMessage(logger.getMessageFactory().newMessage(message));
+ }
+ }
+
+ @Override
+ public void log(String message, Object... params) {
+ if (isValid()) {
+ logMessage(logger.getMessageFactory().newMessage(message, params));
+ }
+ }
+
+ @Override
+ public void log(String message, Supplier<?>... params) {
+ if (isValid()) {
+ logMessage(logger.getMessageFactory().newMessage(message, LambdaUtil.getAll(params)));
+ }
+ }
+
+ @Override
+ public void log(Supplier<Message> messageSupplier) {
+ if (isValid()) {
+ logMessage(messageSupplier.get());
+ }
+ }
+
+ @Override
+ public void log(Object message) {
+ if (isValid()) {
+ logMessage(logger.getMessageFactory().newMessage(message));
+ }
+ }
+
+ @Override
+ public void log(String message, Object p0) {
+ if (isValid()) {
+ logMessage(logger.getMessageFactory().newMessage(message, p0));
+ }
+ }
+
+ @Override
+ public void log(String message, Object p0, Object p1) {
+ if (isValid()) {
+ logMessage(logger.getMessageFactory().newMessage(message, p0, p1));
+ }
+ }
+
+ @Override
+ public void log(String message, Object p0, Object p1, Object p2) {
+ if (isValid()) {
+ logMessage(logger.getMessageFactory().newMessage(message, p0, p1, p2));
+ }
+ }
+
+ @Override
+ public void log(String message, Object p0, Object p1, Object p2, Object p3) {
+ if (isValid()) {
+ 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()) {
+ 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()) {
+ 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()) {
+ 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()) {
+ 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()) {
+ 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()) {
+ logMessage(logger.getMessageFactory().newMessage(message, p0, p1, p2, p3, p4, p5, p6, p7, p8, p9));
+ }
+ }
+
+ 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;
+ }
+}
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 6429ed7..9cd9b4d 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/message/AbstractMessageFactory.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/message/AbstractMessageFactory.java
@@ -19,128 +19,12 @@
import java.io.Serializable;
/**
- * Provides an abstract superclass for {@link MessageFactory2} implementations with default implementations (and for
- * {@link MessageFactory} by extension).
- * <p>
- * This class is immutable.
- * </p>
- * <h4>Note to implementors</h4>
- * <p>
- * Subclasses can implement the {@link MessageFactory2} methods when they can most effectively build {@link Message}
- * instances. If a subclass does not implement {@link MessageFactory2} methods, these calls are routed through
- * {@link #newMessage(String, Object...)} in this class.
- * </p>
+ * Provides an abstract superclass for {@link MessageFactory}. This class is now unnecessary as all default
+ * methods are provided by the (@link MessageFactory) interface.
+ *
+ * @deprecated MessageFactory has default methods that implement all the methods that were here.
*/
-public abstract class AbstractMessageFactory implements MessageFactory2, Serializable {
+public abstract class AbstractMessageFactory implements MessageFactory, Serializable {
private static final long serialVersionUID = -1307891137684031187L;
- @Override
- public Message newMessage(final CharSequence message) {
- return new SimpleMessage(message);
- }
-
- /*
- * (non-Javadoc)
- *
- * @see org.apache.logging.log4j.message.MessageFactory#newMessage(java.lang.Object)
- */
- @Override
- public Message newMessage(final Object message) {
- return new ObjectMessage(message);
- }
-
- /*
- * (non-Javadoc)
- *
- * @see org.apache.logging.log4j.message.MessageFactory#newMessage(java.lang.String)
- */
- @Override
- public Message newMessage(final String message) {
- return new SimpleMessage(message);
- }
-
- /**
- * @since 2.6.1
- */
- @Override
- public Message newMessage(final String message, final Object p0) {
- return newMessage(message, new Object[] { p0 });
- }
-
- /**
- * @since 2.6.1
- */
- @Override
- public Message newMessage(final String message, final Object p0, final Object p1) {
- return newMessage(message, new Object[] { p0, p1 });
- }
-
- /**
- * @since 2.6.1
- */
- @Override
- public Message newMessage(final String message, final Object p0, final Object p1, final Object p2) {
- return newMessage(message, new Object[] { p0, p1, p2 });
- }
-
- /**
- * @since 2.6.1
- */
- @Override
- public Message newMessage(final String message, final Object p0, final Object p1, final Object p2, final Object p3) {
- return newMessage(message, new Object[] { p0, p1, p2, p3 });
- }
-
- /**
- * @since 2.6.1
- */
- @Override
- public Message newMessage(final String message, final Object p0, final Object p1, final Object p2, final Object p3, final Object p4) {
- return newMessage(message, new Object[] { p0, p1, p2, p3, p4 });
- }
-
- /**
- * @since 2.6.1
- */
- @Override
- public Message newMessage(final String message, final Object p0, final Object p1, final Object p2, final Object p3, final Object p4, final Object p5) {
- return newMessage(message, new Object[] { p0, p1, p2, p3, p4, p5 });
- }
-
- /**
- * @since 2.6.1
- */
- @Override
- public Message newMessage(final String message, final Object p0, final Object p1, final Object p2, final Object p3, final Object p4, final Object p5,
- final Object p6) {
- return newMessage(message, new Object[] { p0, p1, p2, p3, p4, p5, p6 });
- }
-
- /**
- * @since 2.6.1
- */
- @Override
- public Message newMessage(final String message, final Object p0, final Object p1, final Object p2, final Object p3, final Object p4, final Object p5,
- final Object p6, final Object p7) {
- return newMessage(message, new Object[] { p0, p1, p2, p3, p4, p5, p6, p7 });
- }
-
- /**
- * @since 2.6.1
- */
- @Override
- public Message newMessage(final String message, final Object p0, final Object p1, final Object p2, final Object p3, final Object p4, final Object p5,
- final Object p6, final Object p7, final Object p8) {
- return newMessage(message, new Object[] { p0, p1, p2, p3, p4, p5, p6, p7, p8 });
- }
-
- /**
- * @since 2.6.1
- */
- @Override
- public Message newMessage(final String message, final Object p0, final Object p1, final Object p2, final Object p3, final Object p4, final Object p5,
- final Object p6, final Object p7, final Object p8, final Object p9) {
- return newMessage(message, new Object[] { p0, p1, p2, p3, p4, p5, p6, p7, p8, p9 });
- }
-
}
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/message/FormattedMessageFactory.java b/log4j-api/src/main/java/org/apache/logging/log4j/message/FormattedMessageFactory.java
index 805e24b..9e3a616 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/message/FormattedMessageFactory.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/message/FormattedMessageFactory.java
@@ -16,16 +16,18 @@
*/
package org.apache.logging.log4j.message;
+import java.io.Serializable;
+
/**
- * Creates {@link FormattedMessage} instances for {@link MessageFactory2} methods (and {@link MessageFactory} by
- * extension.)
- *
+ * Creates {@link FormattedMessage} instances for {@link MessageFactory} methods.
+ *
+ * <h3></h3>
* <h4>Note to implementors</h4>
* <p>
- * This class implements all {@link MessageFactory2} methods.
+ * This class implements all {@link MessageFactory} methods.
* </p>
*/
-public class FormattedMessageFactory extends AbstractMessageFactory {
+public class FormattedMessageFactory implements MessageFactory, Serializable {
private static final long serialVersionUID = 1L;
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/message/LocalizedMessage.java b/log4j-api/src/main/java/org/apache/logging/log4j/message/LocalizedMessage.java
index caf04f7..b201020 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/message/LocalizedMessage.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/message/LocalizedMessage.java
@@ -103,6 +103,8 @@
}
/**
+ * @param bundle The ResourceBundle for this message.
+ * @param key The key of the message in the bundle.
* @since 2.8
*/
public LocalizedMessage(final ResourceBundle bundle, final String key) {
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/message/LocalizedMessageFactory.java b/log4j-api/src/main/java/org/apache/logging/log4j/message/LocalizedMessageFactory.java
index b7e9803..28379e7 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/message/LocalizedMessageFactory.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/message/LocalizedMessageFactory.java
@@ -16,19 +16,20 @@
*/
package org.apache.logging.log4j.message;
+import java.io.Serializable;
import java.util.ResourceBundle;
/**
- * Creates {@link FormattedMessage} instances for {@link MessageFactory2} methods (and {@link MessageFactory} by
- * extension.)
- *
+ * Creates {@link FormattedMessage} instances for {@link MessageFactory} methods.
+ *
+ * <h3></h3>
* <h4>Note to implementors</h4>
* <p>
- * This class does <em>not</em> implement any {@link MessageFactory2} methods and lets the superclass funnel those calls
+ * This class does <em>not</em> implement any {@link MessageFactory} methods and lets the superclass funnel those calls
* through {@link #newMessage(String, Object...)}.
* </p>
*/
-public class LocalizedMessageFactory extends AbstractMessageFactory {
+public class LocalizedMessageFactory implements MessageFactory, Serializable {
private static final long serialVersionUID = -1996295808703146741L;
// FIXME: cannot use ResourceBundle name for serialization until Java 8
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 38319d2..0199e67 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/message/MapMessage.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/message/MapMessage.java
@@ -16,6 +16,7 @@
*/
package org.apache.logging.log4j.message;
+import java.util.AbstractMap;
import java.util.Collections;
import java.util.Map;
import java.util.TreeMap;
@@ -57,19 +58,26 @@
* When set as the format specifier causes the Map to be formatted as XML.
*/
public enum MapFormat {
-
+
/** The map should be formatted as XML. */
XML,
-
+
/** The map should be formatted as JSON. */
JSON,
-
- /** The map should be formatted the same as documented by java.util.AbstractMap.toString(). */
- JAVA;
+
+ /** The map should be formatted the same as documented by {@link AbstractMap#toString()}. */
+ JAVA,
+
+ /**
+ * The map should be formatted the same as documented by {@link AbstractMap#toString()} but without quotes.
+ *
+ * @since 2.11.2
+ */
+ JAVA_UNQUOTED;
/**
* Maps a format name to an {@link MapFormat} while ignoring case.
- *
+ *
* @param format a MapFormat name
* @return a MapFormat
*/
@@ -77,16 +85,17 @@
return XML.name().equalsIgnoreCase(format) ? XML //
: JSON.name().equalsIgnoreCase(format) ? JSON //
: JAVA.name().equalsIgnoreCase(format) ? JAVA //
+ : JAVA_UNQUOTED.name().equalsIgnoreCase(format) ? JAVA_UNQUOTED //
: null;
}
/**
* All {@code MapFormat} names.
- *
+ *
* @return All {@code MapFormat} names.
*/
public static String[] names() {
- return new String[] {XML.name(), JSON.name(), JAVA.name()};
+ return new String[] {XML.name(), JSON.name(), JAVA.name(), JAVA_UNQUOTED.name()};
}
}
@@ -101,7 +110,7 @@
/**
* Constructs a new instance.
- *
+ *
* @param initialCapacity the initial capacity.
*/
public MapMessage(final int initialCapacity) {
@@ -185,13 +194,14 @@
/**
* 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);
}
@@ -228,6 +238,17 @@
}
/**
+ * Allows subclasses to change a candidate key to an actual key.
+ *
+ * @param candidateKey The candidate key.
+ * @return The candidate key.
+ * @since 2.12
+ */
+ protected String toKey(final String candidateKey) {
+ return candidateKey;
+ }
+
+ /**
* Formats the Structured data as described in <a href="https://tools.ietf.org/html/rfc5424">RFC 5424</a>.
*
* @return The formatted String.
@@ -249,7 +270,7 @@
return asString();
}
}
-
+
/**
* Performs the given action for each key-value pair in this data structure
* until all entries have been processed or the action throws an exception.
@@ -300,7 +321,7 @@
public <CV, S> void forEach(final TriConsumer<String, ? super CV, S> action, final S state) {
data.forEach(action, state);
}
-
+
/**
* Formats the Structured data as described in <a href="https://tools.ietf.org/html/rfc5424">RFC 5424</a>.
*
@@ -324,6 +345,9 @@
asJava(sb);
break;
}
+ case JAVA_UNQUOTED:
+ asJavaUnquoted(sb);
+ break;
default : {
appendMap(sb);
}
@@ -400,34 +424,31 @@
}
protected void asJson(final StringBuilder sb) {
- sb.append('{');
- for (int i = 0; i < data.size(); i++) {
- if (i > 0) {
- sb.append(", ");
- }
- sb.append(Chars.DQUOTE);
- int start = sb.length();
- sb.append(data.getKeyAt(i));
- StringBuilders.escapeJson(sb, start);
- sb.append(Chars.DQUOTE).append(':').append(Chars.DQUOTE);
- start = sb.length();
- ParameterFormatter.recursiveDeepToString(data.getValueAt(i), sb, null);
- StringBuilders.escapeJson(sb, start);
- sb.append(Chars.DQUOTE);
- }
- sb.append('}');
+ MapMessageJsonFormatter.format(sb, data);
}
+ protected void asJavaUnquoted(final StringBuilder sb) {
+ asJava(sb, false);
+ }
protected void asJava(final StringBuilder sb) {
+ asJava(sb, true);
+ }
+
+ private void asJava(final StringBuilder sb, 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);
+ sb.append(data.getKeyAt(i)).append(Chars.EQ);
+ if (quoted) {
+ sb.append(Chars.DQUOTE);
+ }
ParameterFormatter.recursiveDeepToString(data.getValueAt(i), sb, null);
- sb.append(Chars.DQUOTE);
+ if (quoted) {
+ sb.append(Chars.DQUOTE);
+ }
}
sb.append('}');
}
@@ -485,10 +506,12 @@
public Throwable getThrowable() {
return null;
}
-
+
/**
* Default implementation does nothing.
- *
+ * @param key The key.
+ * @param value The boolean value.
+ *
* @since 2.9
*/
protected void validate(final String key, final boolean value) {
@@ -497,7 +520,9 @@
/**
* Default implementation does nothing.
- *
+ * @param key The key.
+ * @param value The byte value.
+ *
* @since 2.9
*/
protected void validate(final String key, final byte value) {
@@ -506,7 +531,9 @@
/**
* Default implementation does nothing.
- *
+ * @param key The key.
+ * @param value The char value.
+ *
* @since 2.9
*/
protected void validate(final String key, final char value) {
@@ -515,7 +542,9 @@
/**
* Default implementation does nothing.
- *
+ * @param key The key.
+ * @param value The double value.
+ *
* @since 2.9
*/
protected void validate(final String key, final double value) {
@@ -524,16 +553,20 @@
/**
* Default implementation does nothing.
- *
+ * @param key The key.
+ * @param value The float value.
+ *
* @since 2.9
*/
protected void validate(final String key, final float value) {
// do nothing
}
-
+
/**
* Default implementation does nothing.
- *
+ * @param key The key.
+ * @param value The integer value.
+ *
* @since 2.9
*/
protected void validate(final String key, final int value) {
@@ -542,16 +575,20 @@
/**
* Default implementation does nothing.
- *
+ * @param key The key.
+ * @param value The long value.
+ *
* @since 2.9
*/
protected void validate(final String key, final long value) {
// do nothing
}
-
+
/**
* Default implementation does nothing.
- *
+ * @param key The key.
+ * @param value The Object value.
+ *
* @since 2.9
*/
protected void validate(final String key, final Object value) {
@@ -560,7 +597,9 @@
/**
* Default implementation does nothing.
- *
+ * @param key The key.
+ * @param value The short value.
+ *
* @since 2.9
*/
protected void validate(final String key, final short value) {
@@ -569,7 +608,9 @@
/**
* Default implementation does nothing.
- *
+ * @param key The key.
+ * @param value The string value.
+ *
* @since 2.9
*/
protected void validate(final String key, final String value) {
@@ -578,13 +619,14 @@
/**
* Adds an item to the data Map.
- * @param key The name of the data item.
+ * @param candidateKey The name of the data item.
* @param value The value of the data item.
* @return this object
* @since 2.9
*/
@SuppressWarnings("unchecked")
- public M with(final String key, final boolean value) {
+ public M with(final String candidateKey, final boolean value) {
+ final String key = toKey(candidateKey);
validate(key, value);
data.putValue(key, value);
return (M) this;
@@ -592,13 +634,14 @@
/**
* Adds an item to the data Map.
- * @param key The name of the data item.
+ * @param candidateKey The name of the data item.
* @param value The value of the data item.
* @return this object
* @since 2.9
*/
@SuppressWarnings("unchecked")
- public M with(final String key, final byte value) {
+ public M with(final String candidateKey, final byte value) {
+ final String key = toKey(candidateKey);
validate(key, value);
data.putValue(key, value);
return (M) this;
@@ -606,13 +649,14 @@
/**
* Adds an item to the data Map.
- * @param key The name of the data item.
+ * @param candidateKey The name of the data item.
* @param value The value of the data item.
* @return this object
* @since 2.9
*/
@SuppressWarnings("unchecked")
- public M with(final String key, final char value) {
+ public M with(final String candidateKey, final char value) {
+ final String key = toKey(candidateKey);
validate(key, value);
data.putValue(key, value);
return (M) this;
@@ -621,13 +665,14 @@
/**
* Adds an item to the data Map.
- * @param key The name of the data item.
+ * @param candidateKey The name of the data item.
* @param value The value of the data item.
* @return this object
* @since 2.9
*/
@SuppressWarnings("unchecked")
- public M with(final String key, final double value) {
+ public M with(final String candidateKey, final double value) {
+ final String key = toKey(candidateKey);
validate(key, value);
data.putValue(key, value);
return (M) this;
@@ -635,13 +680,14 @@
/**
* Adds an item to the data Map.
- * @param key The name of the data item.
+ * @param candidateKey The name of the data item.
* @param value The value of the data item.
* @return this object
* @since 2.9
*/
@SuppressWarnings("unchecked")
- public M with(final String key, final float value) {
+ public M with(final String candidateKey, final float value) {
+ final String key = toKey(candidateKey);
validate(key, value);
data.putValue(key, value);
return (M) this;
@@ -649,13 +695,14 @@
/**
* Adds an item to the data Map.
- * @param key The name of the data item.
+ * @param candidateKey The name of the data item.
* @param value The value of the data item.
* @return this object
* @since 2.9
*/
@SuppressWarnings("unchecked")
- public M with(final String key, final int value) {
+ public M with(final String candidateKey, final int value) {
+ final String key = toKey(candidateKey);
validate(key, value);
data.putValue(key, value);
return (M) this;
@@ -663,13 +710,14 @@
/**
* Adds an item to the data Map.
- * @param key The name of the data item.
+ * @param candidateKey The name of the data item.
* @param value The value of the data item.
* @return this object
* @since 2.9
*/
@SuppressWarnings("unchecked")
- public M with(final String key, final long value) {
+ public M with(final String candidateKey, final long value) {
+ final String key = toKey(candidateKey);
validate(key, value);
data.putValue(key, value);
return (M) this;
@@ -677,13 +725,14 @@
/**
* Adds an item to the data Map.
- * @param key The name of the data item.
+ * @param candidateKey The name of the data item.
* @param value The value of the data item.
* @return this object
* @since 2.9
*/
@SuppressWarnings("unchecked")
- public M with(final String key, final Object value) {
+ public M with(final String candidateKey, final Object value) {
+ final String key = toKey(candidateKey);
validate(key, value);
data.putValue(key, value);
return (M) this;
@@ -691,13 +740,14 @@
/**
* Adds an item to the data Map.
- * @param key The name of the data item.
+ * @param candidateKey The name of the data item.
* @param value The value of the data item.
* @return this object
* @since 2.9
*/
@SuppressWarnings("unchecked")
- public M with(final String key, final short value) {
+ public M with(final String candidateKey, final short value) {
+ final String key = toKey(candidateKey);
validate(key, value);
data.putValue(key, value);
return (M) this;
@@ -705,12 +755,13 @@
/**
* Adds an item to the data Map in fluent style.
- * @param key The name of the data item.
+ * @param candidateKey The name of the data item.
* @param value The value of the data item.
* @return {@code this}
*/
@SuppressWarnings("unchecked")
- public M with(final String key, final String value) {
+ public M with(final String candidateKey, final String value) {
+ final String key = toKey(candidateKey);
put(key, value);
return (M) this;
}
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/message/MapMessageJsonFormatter.java b/log4j-api/src/main/java/org/apache/logging/log4j/message/MapMessageJsonFormatter.java
new file mode 100644
index 0000000..3887f97
--- /dev/null
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/message/MapMessageJsonFormatter.java
@@ -0,0 +1,419 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.IndexedStringMap;
+import org.apache.logging.log4j.util.PropertiesUtil;
+import org.apache.logging.log4j.util.StringBuilderFormattable;
+import org.apache.logging.log4j.util.StringBuilders;
+
+import java.math.BigDecimal;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * The default JSON formatter for {@link MapMessage}s.
+ * <p>
+ * The following types have specific handlers:
+ * <p>
+ * <ul>
+ * <li>{@link Map}
+ * <li>{@link Collection} ({@link List}, {@link Set}, etc.)
+ * <li>{@link Number} ({@link BigDecimal}, {@link Double}, {@link Long}, {@link Byte}, etc.)
+ * <li>{@link Boolean}
+ * <li>{@link StringBuilderFormattable}
+ * <li><tt>char/boolean/byte/short/int/long/float/double/Object</tt> arrays
+ * <li>{@link String}
+ * </ul>
+ * <p>
+ * It supports nesting up to a maximum depth of 8, which is set by
+ * <tt>log4j2.mapMessage.jsonFormatter.maxDepth</tt> 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<Object, Object> map = (Map<Object, Object>) object;
+ formatMap(sb, map, depth);
+ }
+
+ // list & collection
+ else if (object instanceof List) {
+ @SuppressWarnings("unchecked")
+ final List<Object> list = (List<Object>) object;
+ formatList(sb, list, depth);
+ } else if (object instanceof Collection) {
+ @SuppressWarnings("unchecked")
+ final Collection<Object> collection = (Collection<Object>) 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<Object, Object> 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<Object> 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<Object> items,
+ final int depth) {
+ sb.append(LBRACE);
+ final int nextDepth = depth + 1;
+ final boolean[] firstItem = {true};
+ items.forEach((final Object item) -> {
+ if (firstItem[0]) {
+ firstItem[0] = false;
+ } else {
+ sb.append(COMMA);
+ }
+ format(sb, item, nextDepth);
+ });
+ sb.append(RBRACE);
+ }
+
+ private static void formatNumber(final StringBuilder sb, final Number number) {
+ if (number instanceof BigDecimal) {
+ final BigDecimal decimalNumber = (BigDecimal) number;
+ sb.append(decimalNumber.toString());
+ } else if (number instanceof Double) {
+ final double doubleNumber = (Double) number;
+ sb.append(doubleNumber);
+ } else if (number instanceof Float) {
+ final float floatNumber = (float) number;
+ sb.append(floatNumber);
+ } else if (number instanceof Byte ||
+ number instanceof Short ||
+ number instanceof Integer ||
+ number instanceof Long) {
+ final long longNumber = number.longValue();
+ sb.append(longNumber);
+ } else {
+ final long longNumber = number.longValue();
+ final double doubleValue = number.doubleValue();
+ if (Double.compare(longNumber, doubleValue) == 0) {
+ sb.append(longNumber);
+ } else {
+ sb.append(doubleValue);
+ }
+ }
+ }
+
+ private static void formatBoolean(final StringBuilder sb, final boolean booleanValue) {
+ sb.append(booleanValue);
+ }
+
+ private static void formatFormattable(
+ final StringBuilder sb,
+ final StringBuilderFormattable formattable) {
+ sb.append(DQUOTE);
+ final int startIndex = sb.length();
+ formattable.formatTo(sb);
+ StringBuilders.escapeJson(sb, startIndex);
+ sb.append(DQUOTE);
+ }
+
+ private static void formatCharArray(final StringBuilder sb, final char[] items) {
+ sb.append(LBRACE);
+ for (int itemIndex = 0; itemIndex < items.length; itemIndex++) {
+ if (itemIndex > 0) {
+ sb.append(COMMA);
+ }
+ final char item = items[itemIndex];
+ sb.append(DQUOTE);
+ final int startIndex = sb.length();
+ sb.append(item);
+ StringBuilders.escapeJson(sb, startIndex);
+ sb.append(DQUOTE);
+ }
+ sb.append(RBRACE);
+ }
+
+ private static void formatBooleanArray(final StringBuilder sb, final boolean[] items) {
+ sb.append(LBRACE);
+ for (int itemIndex = 0; itemIndex < items.length; itemIndex++) {
+ if (itemIndex > 0) {
+ sb.append(COMMA);
+ }
+ final boolean item = items[itemIndex];
+ sb.append(item);
+ }
+ sb.append(RBRACE);
+ }
+
+ private static void formatByteArray(final StringBuilder sb, final byte[] items) {
+ sb.append(LBRACE);
+ for (int itemIndex = 0; itemIndex < items.length; itemIndex++) {
+ if (itemIndex > 0) {
+ sb.append(COMMA);
+ }
+ final byte item = items[itemIndex];
+ sb.append(item);
+ }
+ sb.append(RBRACE);
+ }
+
+ private static void formatShortArray(final StringBuilder sb, final short[] items) {
+ sb.append(LBRACE);
+ for (int itemIndex = 0; itemIndex < items.length; itemIndex++) {
+ if (itemIndex > 0) {
+ sb.append(COMMA);
+ }
+ final short item = items[itemIndex];
+ sb.append(item);
+ }
+ sb.append(RBRACE);
+ }
+
+ private static void formatIntArray(final StringBuilder sb, final int[] items) {
+ sb.append(LBRACE);
+ for (int itemIndex = 0; itemIndex < items.length; itemIndex++) {
+ if (itemIndex > 0) {
+ sb.append(COMMA);
+ }
+ final int item = items[itemIndex];
+ sb.append(item);
+ }
+ sb.append(RBRACE);
+ }
+
+ private static void formatLongArray(final StringBuilder sb, final long[] items) {
+ sb.append(LBRACE);
+ for (int itemIndex = 0; itemIndex < items.length; itemIndex++) {
+ if (itemIndex > 0) {
+ sb.append(COMMA);
+ }
+ final long item = items[itemIndex];
+ sb.append(item);
+ }
+ sb.append(RBRACE);
+ }
+
+ private static void formatFloatArray(final StringBuilder sb, final float[] items) {
+ sb.append(LBRACE);
+ for (int itemIndex = 0; itemIndex < items.length; itemIndex++) {
+ if (itemIndex > 0) {
+ sb.append(COMMA);
+ }
+ final float item = items[itemIndex];
+ sb.append(item);
+ }
+ sb.append(RBRACE);
+ }
+
+ private static void formatDoubleArray(
+ final StringBuilder sb,
+ final double[] items) {
+ sb.append(LBRACE);
+ for (int itemIndex = 0; itemIndex < items.length; itemIndex++) {
+ if (itemIndex > 0) {
+ sb.append(COMMA);
+ }
+ final double item = items[itemIndex];
+ sb.append(item);
+ }
+ sb.append(RBRACE);
+ }
+
+ private static void formatObjectArray(
+ final StringBuilder sb,
+ final Object[] items,
+ final int depth) {
+ sb.append(LBRACE);
+ final int nextDepth = depth + 1;
+ for (int itemIndex = 0; itemIndex < items.length; itemIndex++) {
+ if (itemIndex > 0) {
+ sb.append(COMMA);
+ }
+ final Object item = items[itemIndex];
+ format(sb, item, nextDepth);
+ }
+ sb.append(RBRACE);
+ }
+
+ private static void formatString(final StringBuilder sb, final Object value) {
+ sb.append(DQUOTE);
+ final int startIndex = sb.length();
+ final String valueString = String.valueOf(value);
+ sb.append(valueString);
+ StringBuilders.escapeJson(sb, startIndex);
+ sb.append(DQUOTE);
+ }
+
+}
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/message/MessageFactory.java b/log4j-api/src/main/java/org/apache/logging/log4j/message/MessageFactory.java
index 009d5a6..fa7c672 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/message/MessageFactory.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/message/MessageFactory.java
@@ -31,7 +31,9 @@
* a message object
* @return a new message
*/
- Message newMessage(Object message);
+ default Message newMessage(Object message) {
+ return new ObjectMessage(message);
+ }
/**
* Creates a new message based on a String.
@@ -40,7 +42,9 @@
* a message String
* @return a new message
*/
- Message newMessage(String message);
+ default Message newMessage(String message) {
+ return new SimpleMessage(message);
+ }
/**
* Creates a new parameterized message.
@@ -54,4 +58,182 @@
* @see StringFormatterMessageFactory
*/
Message newMessage(String message, Object... params);
+
+ /**
+ * Creates a new message for the specified CharSequence.
+ * @param charSequence the (potentially mutable) CharSequence
+ * @return a new message for the specified CharSequence
+ */
+ default Message newMessage(CharSequence charSequence) {
+ return new SimpleMessage(charSequence);
+ }
+
+ /**
+ * Creates a new parameterized message.
+ *
+ * @param message a message template, the kind of message template depends on the implementation.
+ * @param p0 a message parameter
+ * @return a new message
+ * @see ParameterizedMessageFactory
+ */
+ default Message newMessage(String message, Object p0) {
+ return newMessage(message, new Object[] { p0 });
+ }
+
+ /**
+ * Creates a new parameterized message.
+ *
+ * @param message a message template, the kind of message template depends on the implementation.
+ * @param p0 a message parameter
+ * @param p1 a message parameter
+ * @return a new message
+ * @see ParameterizedMessageFactory
+ */
+ default Message newMessage(String message, Object p0, Object p1) {
+ return newMessage(message, new Object[] { p0, p1 });
+ }
+
+ /**
+ * Creates a new parameterized message.
+ *
+ * @param message a message template, the kind of message template depends on the implementation.
+ * @param p0 a message parameter
+ * @param p1 a message parameter
+ * @param p2 a message parameter
+ * @return a new message
+ * @see ParameterizedMessageFactory
+ */
+ default Message newMessage(String message, Object p0, Object p1, Object p2) {
+ return newMessage(message, new Object[] { p0, p1, p2 });
+ }
+
+ /**
+ * Creates a new parameterized message.
+ *
+ * @param message a message template, the kind of message template depends on the implementation.
+ * @param p0 a message parameter
+ * @param p1 a message parameter
+ * @param p2 a message parameter
+ * @param p3 a message parameter
+ * @return a new message
+ * @see ParameterizedMessageFactory
+ */
+ default Message newMessage(String message, Object p0, Object p1, Object p2, Object p3) {
+ return newMessage(message, new Object[] { p0, p1, p2, p3 });
+ }
+
+ /**
+ * Creates a new parameterized message.
+ *
+ * @param message a message template, the kind of message template depends on the implementation.
+ * @param p0 a message parameter
+ * @param p1 a message parameter
+ * @param p2 a message parameter
+ * @param p3 a message parameter
+ * @param p4 a message parameter
+ * @return a new message
+ * @see ParameterizedMessageFactory
+ */
+ default Message newMessage(String message, Object p0, Object p1, Object p2, Object p3, Object p4) {
+ return newMessage(message, new Object[] { p0, p1, p2, p3, p4 });
+ }
+
+ /**
+ * Creates a new parameterized message.
+ *
+ * @param message a message template, the kind of message template depends on the implementation.
+ * @param p0 a message parameter
+ * @param p1 a message parameter
+ * @param p2 a message parameter
+ * @param p3 a message parameter
+ * @param p4 a message parameter
+ * @param p5 a message parameter
+ * @return a new message
+ * @see ParameterizedMessageFactory
+ */
+ default Message newMessage(String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5) {
+ return newMessage(message, new Object[] { p0, p1, p2, p3, p4, p5 });
+ }
+
+ /**
+ * Creates a new parameterized message.
+ *
+ * @param message a message template, the kind of message template depends on the implementation.
+ * @param p0 a message parameter
+ * @param p1 a message parameter
+ * @param p2 a message parameter
+ * @param p3 a message parameter
+ * @param p4 a message parameter
+ * @param p5 a message parameter
+ * @param p6 a message parameter
+ * @return a new message
+ * @see ParameterizedMessageFactory
+ */
+ default Message newMessage(String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5,
+ Object p6) {
+ return newMessage(message, new Object[] { p0, p1, p2, p3, p4, p5, p6 });
+ }
+
+ /**
+ * Creates a new parameterized message.
+ *
+ * @param message a message template, the kind of message template depends on the implementation.
+ * @param p0 a message parameter
+ * @param p1 a message parameter
+ * @param p2 a message parameter
+ * @param p3 a message parameter
+ * @param p4 a message parameter
+ * @param p5 a message parameter
+ * @param p6 a message parameter
+ * @param p7 a message parameter
+ * @return a new message
+ * @see ParameterizedMessageFactory
+ */
+ default Message newMessage(String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5,
+ Object p6, Object p7) {
+ return newMessage(message, new Object[] { p0, p1, p2, p3, p4, p5, p6, p7 });
+ }
+
+ /**
+ * Creates a new parameterized message.
+ *
+ * @param message a message template, the kind of message template depends on the implementation.
+ * @param p0 a message parameter
+ * @param p1 a message parameter
+ * @param p2 a message parameter
+ * @param p3 a message parameter
+ * @param p4 a message parameter
+ * @param p5 a message parameter
+ * @param p6 a message parameter
+ * @param p7 a message parameter
+ * @param p8 a message parameter
+ * @return a new message
+ * @see ParameterizedMessageFactory
+ */
+ default Message newMessage(String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5,
+ Object p6, Object p7, Object p8) {
+ return newMessage(message, new Object[] { p0, p1, p2, p3, p4, p5, p6, p7, p8 });
+ }
+
+ /**
+ * Creates a new parameterized message.
+ *
+ * @param message a message template, the kind of message template depends on the implementation.
+ * @param p0 a message parameter
+ * @param p1 a message parameter
+ * @param p2 a message parameter
+ * @param p3 a message parameter
+ * @param p4 a message parameter
+ * @param p5 a message parameter
+ * @param p6 a message parameter
+ * @param p7 a message parameter
+ * @param p8 a message parameter
+ * @param p9 a message parameter
+ * @return a new message
+ * @see ParameterizedMessageFactory
+ */
+ default Message newMessage(String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5,
+ Object p6, Object p7, Object p8, Object p9) {
+ return newMessage(message, new Object[] { p0, p1, p2, p3, p4, p5, p6, p7, p8, p9 });
+ }
}
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/message/MessageFactory2.java b/log4j-api/src/main/java/org/apache/logging/log4j/message/MessageFactory2.java
index 33004dd..2e2ad70 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/message/MessageFactory2.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/message/MessageFactory2.java
@@ -20,162 +20,8 @@
* Creates messages. Implementations can provide different message format syntaxes.
*
* @see ParameterizedMessageFactory
+ * @deprecated MessageFactory now contains all interface methods.
* @since 2.6
*/
public interface MessageFactory2 extends MessageFactory {
-
- /**
- * Creates a new message for the specified CharSequence.
- * @param charSequence the (potentially mutable) CharSequence
- * @return a new message for the specified CharSequence
- */
- Message newMessage(CharSequence charSequence);
-
- /**
- * Creates a new parameterized message.
- *
- * @param message a message template, the kind of message template depends on the implementation.
- * @param p0 a message parameter
- * @return a new message
- * @see ParameterizedMessageFactory
- */
- Message newMessage(String message, Object p0);
-
- /**
- * Creates a new parameterized message.
- *
- * @param message a message template, the kind of message template depends on the implementation.
- * @param p0 a message parameter
- * @param p1 a message parameter
- * @return a new message
- * @see ParameterizedMessageFactory
- */
- Message newMessage(String message, Object p0, Object p1);
-
- /**
- * Creates a new parameterized message.
- *
- * @param message a message template, the kind of message template depends on the implementation.
- * @param p0 a message parameter
- * @param p1 a message parameter
- * @param p2 a message parameter
- * @return a new message
- * @see ParameterizedMessageFactory
- */
- Message newMessage(String message, Object p0, Object p1, Object p2);
-
- /**
- * Creates a new parameterized message.
- *
- * @param message a message template, the kind of message template depends on the implementation.
- * @param p0 a message parameter
- * @param p1 a message parameter
- * @param p2 a message parameter
- * @param p3 a message parameter
- * @return a new message
- * @see ParameterizedMessageFactory
- */
- Message newMessage(String message, Object p0, Object p1, Object p2, Object p3);
-
- /**
- * Creates a new parameterized message.
- *
- * @param message a message template, the kind of message template depends on the implementation.
- * @param p0 a message parameter
- * @param p1 a message parameter
- * @param p2 a message parameter
- * @param p3 a message parameter
- * @param p4 a message parameter
- * @return a new message
- * @see ParameterizedMessageFactory
- */
- Message newMessage(String message, Object p0, Object p1, Object p2, Object p3, Object p4);
-
- /**
- * Creates a new parameterized message.
- *
- * @param message a message template, the kind of message template depends on the implementation.
- * @param p0 a message parameter
- * @param p1 a message parameter
- * @param p2 a message parameter
- * @param p3 a message parameter
- * @param p4 a message parameter
- * @param p5 a message parameter
- * @return a new message
- * @see ParameterizedMessageFactory
- */
- Message newMessage(String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5);
-
- /**
- * Creates a new parameterized message.
- *
- * @param message a message template, the kind of message template depends on the implementation.
- * @param p0 a message parameter
- * @param p1 a message parameter
- * @param p2 a message parameter
- * @param p3 a message parameter
- * @param p4 a message parameter
- * @param p5 a message parameter
- * @param p6 a message parameter
- * @return a new message
- * @see ParameterizedMessageFactory
- */
- Message newMessage(String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, Object p6);
-
- /**
- * Creates a new parameterized message.
- *
- * @param message a message template, the kind of message template depends on the implementation.
- * @param p0 a message parameter
- * @param p1 a message parameter
- * @param p2 a message parameter
- * @param p3 a message parameter
- * @param p4 a message parameter
- * @param p5 a message parameter
- * @param p6 a message parameter
- * @param p7 a message parameter
- * @return a new message
- * @see ParameterizedMessageFactory
- */
- Message newMessage(String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, Object p6,
- Object p7);
-
- /**
- * Creates a new parameterized message.
- *
- * @param message a message template, the kind of message template depends on the implementation.
- * @param p0 a message parameter
- * @param p1 a message parameter
- * @param p2 a message parameter
- * @param p3 a message parameter
- * @param p4 a message parameter
- * @param p5 a message parameter
- * @param p6 a message parameter
- * @param p7 a message parameter
- * @param p8 a message parameter
- * @return a new message
- * @see ParameterizedMessageFactory
- */
- Message newMessage(String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, Object p6,
- Object p7, Object p8);
-
- /**
- * Creates a new parameterized message.
- *
- * @param message a message template, the kind of message template depends on the implementation.
- * @param p0 a message parameter
- * @param p1 a message parameter
- * @param p2 a message parameter
- * @param p3 a message parameter
- * @param p4 a message parameter
- * @param p5 a message parameter
- * @param p6 a message parameter
- * @param p7 a message parameter
- * @param p8 a message parameter
- * @param p9 a message parameter
- * @return a new message
- * @see ParameterizedMessageFactory
- */
- Message newMessage(String message, Object p0, Object p1, Object p2, Object p3, Object p4, Object p5, Object p6,
- Object p7, Object p8, Object p9);
}
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/message/MessageFormatMessageFactory.java b/log4j-api/src/main/java/org/apache/logging/log4j/message/MessageFormatMessageFactory.java
index f75e68b..1fae368 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/message/MessageFormatMessageFactory.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/message/MessageFormatMessageFactory.java
@@ -16,16 +16,18 @@
*/
package org.apache.logging.log4j.message;
+import java.io.Serializable;
+
/**
- * Creates {@link FormattedMessage} instances for {@link MessageFactory2} methods (and {@link MessageFactory} by
- * extension.)
- *
+ * Creates {@link FormattedMessage} instances for {@link MessageFactory} methods.
+ *
+ * <h3></h3>
* <h4>Note to implementors</h4>
* <p>
- * This class implements all {@link MessageFactory2} methods.
+ * This class implements all {@link MessageFactory} methods.
* </p>
*/
-public class MessageFormatMessageFactory extends AbstractMessageFactory {
+public class MessageFormatMessageFactory implements MessageFactory, Serializable {
private static final long serialVersionUID = 3584821740584192453L;
/**
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/message/ParameterConsumer.java b/log4j-api/src/main/java/org/apache/logging/log4j/message/ParameterConsumer.java
index 8b81e1a..7513e95 100644
--- 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
@@ -19,7 +19,7 @@
*
* @param parameter the parameter
* @param parameterIndex Index of the parameter
- * @param state
+ * @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 5c4cc73..c01e686 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/message/ParameterFormatter.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/message/ParameterFormatter.java
@@ -16,7 +16,8 @@
*/
package org.apache.logging.log4j.message;
-import java.text.SimpleDateFormat;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
@@ -59,8 +60,8 @@
private static final char DELIM_START = '{';
private static final char DELIM_STOP = '}';
private static final char ESCAPE_CHAR = '\\';
-
- private static ThreadLocal<SimpleDateFormat> threadLocalSimpleDateFormat = new ThreadLocal<>();
+ private static final DateTimeFormatter FORMATTER =
+ DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSZ").withZone(ZoneId.systemDefault());
private ParameterFormatter() {
}
@@ -467,21 +468,10 @@
if (!(o instanceof Date)) {
return false;
}
- final Date date = (Date) o;
- final SimpleDateFormat format = getSimpleDateFormat();
- str.append(format.format(date));
+ str.append(FORMATTER.format(((Date) o).toInstant()));
return true;
}
- private static SimpleDateFormat getSimpleDateFormat() {
- SimpleDateFormat result = threadLocalSimpleDateFormat.get();
- if (result == null) {
- result = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
- threadLocalSimpleDateFormat.set(result);
- }
- return result;
- }
-
/**
* Returns {@code true} if the specified object is an array, a Map or a Collection.
*/
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 c4a1bbf..3289667 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/message/ParameterizedMessage.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/message/ParameterizedMessage.java
@@ -69,7 +69,7 @@
private static final int HASHVAL = 31;
// storing JDK classes in ThreadLocals does not cause memory leaks in web apps, so this is okay
- private static ThreadLocal<StringBuilder> threadLocalStringBuilder = new ThreadLocal<>();
+ private static final ThreadLocal<StringBuilder> threadLocalStringBuilder = new ThreadLocal<>();
private String messagePattern;
private transient Object[] argArray;
@@ -85,21 +85,6 @@
* where parameters should be substituted.
* @param arguments The arguments for substitution.
* @param throwable A Throwable.
- * @deprecated Use constructor ParameterizedMessage(String, Object[], Throwable) instead
- */
- @Deprecated
- public ParameterizedMessage(final String messagePattern, final String[] arguments, final Throwable throwable) {
- this.argArray = arguments;
- this.throwable = throwable;
- init(messagePattern);
- }
-
- /**
- * Creates a parameterized message.
- * @param messagePattern The message "format" string. This will be a String containing "{}" placeholders
- * where parameters should be substituted.
- * @param arguments The arguments for substitution.
- * @param throwable A Throwable.
*/
public ParameterizedMessage(final String messagePattern, final Object[] arguments, final Throwable throwable) {
this.argArray = arguments;
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 5878cac..bc9b16b 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/message/ParameterizedMessageFactory.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/message/ParameterizedMessageFactory.java
@@ -16,9 +16,10 @@
*/
package org.apache.logging.log4j.message;
+import java.io.Serializable;
+
/**
- * Creates {@link FormattedMessage} instances for {@link MessageFactory2} methods (and {@link MessageFactory} by
- * extension.)
+ * Creates {@link FormattedMessage} instances for {@link MessageFactory}.
* <p>
* Enables the use of <code>{}</code> parameter markers in message strings.
* </p>
@@ -28,13 +29,14 @@
* <p>
* This class is immutable.
* </p>
- *
+ *
+ * <h3></h3>
* <h4>Note to implementors</h4>
* <p>
- * This class implements all {@link MessageFactory2} methods.
+ * This class implements all {@link MessageFactory} methods.
* </p>
*/
-public final class ParameterizedMessageFactory extends AbstractMessageFactory {
+public final class ParameterizedMessageFactory implements MessageFactory, Serializable {
/**
* Instance of ParameterizedMessageFactory.
*/
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/message/ParameterizedNoReferenceMessageFactory.java b/log4j-api/src/main/java/org/apache/logging/log4j/message/ParameterizedNoReferenceMessageFactory.java
index fdf397b..0d9375d 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/message/ParameterizedNoReferenceMessageFactory.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/message/ParameterizedNoReferenceMessageFactory.java
@@ -18,9 +18,10 @@
import org.apache.logging.log4j.status.StatusLogger;
+import java.io.Serializable;
+
/**
- * Creates {@link FormattedMessage} instances for {@link MessageFactory2} methods (and {@link MessageFactory} by
- * extension.)
+ * Creates {@link FormattedMessage} instances for {@link MessageFactory} methods.
* <p>
* Creates {@link SimpleMessage} objects that do not retain a reference to the parameter object.
* </p>
@@ -31,13 +32,14 @@
* <p>
* This class is immutable.
* </p>
+ * <h3></h3>
* <h4>Note to implementors</h4>
* <p>
- * This class does <em>not</em> implement any {@link MessageFactory2} methods and lets the superclass funnel those calls
+ * This class does <em>not</em> implement any {@link MessageFactory} methods and lets the superclass funnel those calls
* through {@link #newMessage(String, Object...)}.
* </p>
*/
-public final class ParameterizedNoReferenceMessageFactory extends AbstractMessageFactory {
+public final class ParameterizedNoReferenceMessageFactory implements MessageFactory, Serializable {
private static final long serialVersionUID = 5027639245636870500L;
/**
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/message/ReusableMessage.java b/log4j-api/src/main/java/org/apache/logging/log4j/message/ReusableMessage.java
index 82e40ac..f1ab320 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/message/ReusableMessage.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/message/ReusableMessage.java
@@ -35,7 +35,7 @@
* Returns the parameter array that was used to initialize this reusable message and replaces it with the specified
* array. The returned parameter array will no longer be modified by this reusable message. The specified array is
* now "owned" by this reusable message and can be modified if necessary for the next log event.
- * </p><p>
+ * <p>
* ReusableMessages that have no parameters return the specified array.
* </p><p>
* This method is used by asynchronous loggers to pass the parameter array to a background thread without
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/message/ReusableMessageFactory.java b/log4j-api/src/main/java/org/apache/logging/log4j/message/ReusableMessageFactory.java
index bf30cfa..d7d7b8c 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/message/ReusableMessageFactory.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/message/ReusableMessageFactory.java
@@ -30,7 +30,7 @@
* @since 2.6
*/
@PerformanceSensitive("allocation")
-public final class ReusableMessageFactory implements MessageFactory2, Serializable {
+public final class ReusableMessageFactory implements MessageFactory, Serializable {
/**
* Instance of ReusableMessageFactory..
@@ -38,9 +38,9 @@
public static final ReusableMessageFactory INSTANCE = new ReusableMessageFactory();
private static final long serialVersionUID = -8970940216592525651L;
- private static ThreadLocal<ReusableParameterizedMessage> threadLocalParameterized = new ThreadLocal<>();
- private static ThreadLocal<ReusableSimpleMessage> threadLocalSimpleMessage = new ThreadLocal<>();
- private static ThreadLocal<ReusableObjectMessage> threadLocalObjectMessage = new ThreadLocal<>();
+ private static final ThreadLocal<ReusableParameterizedMessage> threadLocalParameterized = new ThreadLocal<>();
+ private static final ThreadLocal<ReusableSimpleMessage> threadLocalSimpleMessage = new ThreadLocal<>();
+ private static final ThreadLocal<ReusableObjectMessage> threadLocalObjectMessage = new ThreadLocal<>();
/**
* Constructs a message factory.
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 2a186be..8c264aa 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
@@ -25,7 +25,7 @@
@PerformanceSensitive("allocation")
public class ReusableSimpleMessage implements ReusableMessage, CharSequence, ParameterVisitable, Clearable {
private static final long serialVersionUID = -9199974506498249809L;
- private static Object[] EMPTY_PARAMS = new Object[0];
+ private static final Object[] EMPTY_PARAMS = new Object[0];
private CharSequence charSequence;
public void set(final String message) {
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 e85b8c0..aa1c5f5 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/message/SimpleMessageFactory.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/message/SimpleMessageFactory.java
@@ -16,9 +16,10 @@
*/
package org.apache.logging.log4j.message;
+import java.io.Serializable;
+
/**
- * Creates {@link FormattedMessage} instances for {@link MessageFactory2} methods (and {@link MessageFactory} by
- * extension.)
+ * Creates {@link FormattedMessage} instances for {@link MessageFactory} methods.
* <p>
* This uses is the simplest possible implementation of {@link Message}, the where you give the message to the
* constructor argument as a String.
@@ -29,15 +30,16 @@
* <p>
* This class is immutable.
* </p>
- *
+ *
+ * <h3></h3>
* <h4>Note to implementors</h4>
* <p>
- * This class implements all {@link MessageFactory2} methods.
+ * This class implements all {@link MessageFactory} methods.
* </p>
*
* @since 2.5
*/
-public final class SimpleMessageFactory extends AbstractMessageFactory {
+public final class SimpleMessageFactory implements MessageFactory, Serializable {
/**
* Instance of StringFormatterMessageFactory.
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/message/StringFormattedMessage.java b/log4j-api/src/main/java/org/apache/logging/log4j/message/StringFormattedMessage.java
index a99e2ef..a366a24 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/message/StringFormattedMessage.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/message/StringFormattedMessage.java
@@ -28,7 +28,8 @@
/**
* Handles messages that consist of a format string conforming to {@link java.util.Formatter}.
- *
+ *
+ * <h3></h3>
* <h4>Note to implementors</h4>
* <p>
* This class implements the unrolled args API even though StringFormattedMessage does not. This leaves the room for
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 b6554d5..00f4e09 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/message/StringFormatterMessageFactory.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/message/StringFormatterMessageFactory.java
@@ -16,9 +16,10 @@
*/
package org.apache.logging.log4j.message;
+import java.io.Serializable;
+
/**
- * Creates {@link FormattedMessage} instances for {@link MessageFactory2} methods (and {@link MessageFactory} by
- * extension.)
+ * Creates {@link FormattedMessage} instances for {@link MessageFactory} methods.
* <p>
* Enables the use of {@link java.util.Formatter} strings in message strings.
* </p>
@@ -28,12 +29,13 @@
* <p>
* This class is immutable.
* </p>
+ * <h3></h3>
* <h4>Note to implementors</h4>
* <p>
- * This class implements all {@link MessageFactory2} methods.
+ * This class implements all {@link MessageFactory} methods.
* </p>
*/
-public final class StringFormatterMessageFactory extends AbstractMessageFactory {
+public final class StringFormatterMessageFactory implements MessageFactory, Serializable {
/**
* Instance of StringFormatterMessageFactory.
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/message/StructuredDataCollectionMessage.java b/log4j-api/src/main/java/org/apache/logging/log4j/message/StructuredDataCollectionMessage.java
index e58aed3..7a617cd 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/message/StructuredDataCollectionMessage.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/message/StructuredDataCollectionMessage.java
@@ -29,7 +29,7 @@
MessageCollectionMessage<StructuredDataMessage> {
private static final long serialVersionUID = 5725337076388822924L;
- private List<StructuredDataMessage> structuredDataMessageList;
+ private final List<StructuredDataMessage> structuredDataMessageList;
public StructuredDataCollectionMessage(List<StructuredDataMessage> messages) {
this.structuredDataMessageList = messages;
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 232f156..a5169aa 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
@@ -78,10 +78,10 @@
}
/**
- *
- * @param name
- * @param required
- * @param optional
+ * A Constructor that helps conformance to RFC 5424.
+ * @param name The name portion of the id.
+ * @param required The list of keys that are required for this id.
+ * @param optional The list of keys that are optional for this id.
*/
public StructuredDataId(final String name, final String[] required, final String[] optional) {
this(name, required, optional, MAX_LENGTH);
@@ -93,13 +93,16 @@
* @param name The name portion of the id.
* @param required The list of keys that are required for this id.
* @param optional The list of keys that are optional for this id.
+ * @param maxLength The maximum length of the id.
* @since 2.9
*/
- public StructuredDataId(final String name, final String[] required, final String[] optional,
- final int maxLength) {
+ public StructuredDataId(final String name, final String[] required, final String[] optional, int maxLength) {
int index = -1;
if (name != null) {
- if (maxLength > 0 && name.length() > MAX_LENGTH) {
+ 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));
}
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 2229948..db0bd05 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
@@ -171,7 +171,6 @@
* <p>
* Implementations of this class are loaded via the standard java Service Provider interface.
* </p>
- * @see /log4j-core/src/main/resources/META-INF/services/org.apache.logging.log4j.message.ThreadDumpMessage$ThreadInfoFactory
*/
public static interface ThreadInfoFactory {
Map<ThreadInformation, StackTraceElement[]> createThreadInfo();
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 8ea57a8..ce8e242 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
@@ -26,7 +26,7 @@
*/
public class SimpleLoggerContextFactory implements LoggerContextFactory {
- private static LoggerContext context = new SimpleLoggerContext();
+ private static final LoggerContext context = new SimpleLoggerContext();
@Override
public LoggerContext getContext(final String fqcn, final ClassLoader loader, final Object externalContext,
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 6e67d5b..fdf0f44 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
@@ -16,18 +16,17 @@
*/
package org.apache.logging.log4j.spi;
-import java.io.Serializable;
-
import org.apache.logging.log4j.Level;
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.LogBuilder;
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;
@@ -40,9 +39,15 @@
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.StackLocatorUtil;
import org.apache.logging.log4j.util.Strings;
import org.apache.logging.log4j.util.Supplier;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.Serializable;
+import java.lang.reflect.Field;
+
/**
* Base implementation of a Logger. It is highly recommended that any Logger implementation extend this class.
*/
@@ -102,9 +107,11 @@
private static final String CATCHING = "Catching";
protected final String name;
- private final MessageFactory2 messageFactory;
+ private final MessageFactory messageFactory;
private final FlowMessageFactory flowMessageFactory;
- private static ThreadLocal<int[]> recursionDepthHolder = new ThreadLocal<>(); // LOG4J2-1518, LOG4J2-2031
+ private static final ThreadLocal<int[]> recursionDepthHolder = new ThreadLocal<>(); // LOG4J2-1518, LOG4J2-2031
+ protected final transient ThreadLocal<DefaultLogBuilder> logBuilder;
+
/**
* Creates a new logger named after this class (or subclass).
@@ -113,6 +120,7 @@
this.name = getClass().getName();
this.messageFactory = createDefaultMessageFactory();
this.flowMessageFactory = createDefaultFlowMessageFactory();
+ this.logBuilder = new LocalLogBuilder(this);
}
/**
@@ -132,8 +140,9 @@
*/
public AbstractLogger(final String name, final MessageFactory messageFactory) {
this.name = name;
- this.messageFactory = messageFactory == null ? createDefaultMessageFactory() : narrow(messageFactory);
+ this.messageFactory = messageFactory == null ? createDefaultMessageFactory() : messageFactory;
this.flowMessageFactory = createDefaultFlowMessageFactory();
+ this.logBuilder = new LocalLogBuilder(this);
}
/**
@@ -214,22 +223,14 @@
}
}
- private static MessageFactory2 createDefaultMessageFactory() {
+ private static MessageFactory createDefaultMessageFactory() {
try {
- final MessageFactory result = DEFAULT_MESSAGE_FACTORY_CLASS.newInstance();
- return narrow(result);
+ return DEFAULT_MESSAGE_FACTORY_CLASS.newInstance();
} 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();
@@ -504,6 +505,7 @@
* @param fqcn The fully qualified class name of the <b>caller</b>.
* @param format Format String for the parameters.
* @param paramSuppliers The Suppliers of the parameters.
+ * @return The EntryMessage.
*/
protected EntryMessage enter(final String fqcn, final String format, final Supplier<?>... paramSuppliers) {
EntryMessage entryMsg = null;
@@ -518,23 +520,8 @@
*
* @param fqcn The fully qualified class name of the <b>caller</b>.
* @param format The format String for the parameters.
- * @param paramSuppliers The parameters to the method.
- */
- @Deprecated
- protected EntryMessage enter(final String fqcn, final String format, final MessageSupplier... paramSuppliers) {
- EntryMessage entryMsg = null;
- if (isEnabled(Level.TRACE, ENTRY_MARKER, (Object) null, null)) {
- logMessageSafely(fqcn, Level.TRACE, ENTRY_MARKER, entryMsg = entryMsg(format, paramSuppliers), null);
- }
- return entryMsg;
- }
-
- /**
- * Logs entry to a method with location information.
- *
- * @param fqcn The fully qualified class name of the <b>caller</b>.
- * @param format The format String for the parameters.
* @param params The parameters to the method.
+ * @return The EntryMessage.
*/
protected EntryMessage enter(final String fqcn, final String format, final Object... params) {
EntryMessage entryMsg = null;
@@ -548,25 +535,8 @@
* Logs entry to a method with location information.
*
* @param fqcn The fully qualified class name of the <b>caller</b>.
- * @param msgSupplier The Supplier of the Message.
- */
- @Deprecated
- protected EntryMessage enter(final String fqcn, final MessageSupplier msgSupplier) {
- EntryMessage message = null;
- if (isEnabled(Level.TRACE, ENTRY_MARKER, (Object) null, null)) {
- logMessageSafely(fqcn, Level.TRACE, ENTRY_MARKER, message = flowMessageFactory.newEntryMessage(
- msgSupplier.get()), null);
- }
- return message;
- }
-
- /**
- * Logs entry to a method with location information.
- *
- * @param fqcn
- * The fully qualified class name of the <b>caller</b>.
- * @param message
- * the Message.
+ * @param message the Message.
+ * @return The EntryMessage.
* @since 2.6
*/
protected EntryMessage enter(final String fqcn, final Message message) {
@@ -578,17 +548,6 @@
return flowMessage;
}
- @Deprecated
- @Override
- public void entry() {
- entry(FQCN, (Object[]) null);
- }
-
- @Override
- public void entry(final Object... params) {
- entry(FQCN, params);
- }
-
/**
* Logs entry to a method with location information.
*
@@ -908,18 +867,6 @@
logIfEnabled(FQCN, Level.ERROR, null, message, p0, p1, p2, p3, p4, p5, p6, p7, p8, p9);
}
- @Deprecated
- @Override
- public void exit() {
- exit(FQCN, (Object) null);
- }
-
- @Deprecated
- @Override
- public <R> R exit(final R result) {
- return exit(FQCN, result);
- }
-
/**
* Logs exiting from a method with the result and location information.
*
@@ -939,6 +886,7 @@
* Logs exiting from a method with the result and location information.
*
* @param fqcn The fully qualified class name of the <b>caller</b>.
+ * @param format The format string.
* @param <R> 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.
@@ -2083,6 +2031,24 @@
logMessageSafely(fqcn, level, marker, msg, msg.getThrowable());
}
+ public void logMessage(final Level level, final Marker marker, final String fqcn, final StackTraceElement location,
+ final Message message, final Throwable throwable) {
+ try {
+ incrementRecursionDepth();
+ log(level, marker, fqcn, location, message, throwable);
+ } catch (Exception ex) {
+ handleLogMessageException(ex, fqcn, message);
+ } finally {
+ decrementRecursionDepth();
+ ReusableMessageFactory.release(message);
+ }
+ }
+
+ protected void log(final Level level, final Marker marker, final String fqcn, final StackTraceElement location,
+ final Message message, final Throwable throwable) {
+ logMessage(fqcn, level, marker, message, throwable);
+ }
+
@Override
public void printf(final Level level, final Marker marker, final String format, final Object... params) {
if (isEnabled(level, marker, format, params)) {
@@ -2113,7 +2079,7 @@
}
@PerformanceSensitive
- // NOTE: This is a hot method. Current implementation compiles to 29 bytes of byte code.
+ // NOTE: This is a hot method. Current implementation compiles to 33 bytes of byte code.
// This is within the 35 byte MaxInlineSize threshold. Modify with care!
private void logMessageTrackRecursion(final String fqcn,
final Level level,
@@ -2122,7 +2088,7 @@
final Throwable throwable) {
try {
incrementRecursionDepth(); // LOG4J2-1518, LOG4J2-2031
- tryLogMessage(fqcn, level, marker, msg, throwable);
+ tryLogMessage(fqcn, getLocation(fqcn), level, marker, msg, throwable);
} finally {
decrementRecursionDepth();
}
@@ -2159,21 +2125,29 @@
}
@PerformanceSensitive
- // NOTE: This is a hot method. Current implementation compiles to 26 bytes of byte code.
+ // NOTE: This is a hot method. Current implementation compiles to 27 bytes of byte code.
// This is within the 35 byte MaxInlineSize threshold. Modify with care!
private void tryLogMessage(final String fqcn,
+ final StackTraceElement location,
final Level level,
final Marker marker,
final Message msg,
final Throwable throwable) {
try {
- logMessage(fqcn, level, marker, msg, throwable);
+ log(level, marker, fqcn, location, msg, throwable);
} catch (final Exception e) {
// LOG4J2-1990 Log4j2 suppresses all exceptions that occur once application called the logger
handleLogMessageException(e, fqcn, msg);
}
}
+ @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(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) {
@@ -2789,4 +2763,117 @@
final Object p7, final Object p8, final Object p9) {
logIfEnabled(FQCN, Level.WARN, null, message, p0, p1, p2, p3, p4, p5, p6, p7, p8, p9);
}
+
+ protected boolean requiresLocation() {
+ return false;
+ }
+
+ /**
+ * Construct a trace log event.
+ * @return a LogBuilder.
+ * @since 2.13.0
+ */
+ @Override
+ public LogBuilder atTrace() {
+ return atLevel(Level.TRACE);
+ }
+ /**
+ * Construct a debug log event.
+ * @return a LogBuilder.
+ * @since 2.13.0
+ */
+ @Override
+ public LogBuilder atDebug() {
+ return atLevel(Level.DEBUG);
+ }
+ /**
+ * Construct an informational log event.
+ * @return a LogBuilder.
+ * @since 2.13.0
+ */
+ @Override
+ public LogBuilder atInfo() {
+ return atLevel(Level.INFO);
+ }
+ /**
+ * Construct a warning log event.
+ * @return a LogBuilder.
+ * @since 2.13.0
+ */
+ @Override
+ public LogBuilder atWarn() {
+ return atLevel(Level.WARN);
+ }
+ /**
+ * Construct an error log event.
+ * @return a LogBuilder.
+ * @since 2.13.0
+ */
+ @Override
+ public LogBuilder atError() {
+ return atLevel(Level.ERROR);
+ }
+ /**
+ * Construct a fatal log event.
+ * @return a LogBuilder.
+ * @since 2.13.0
+ */
+ @Override
+ public LogBuilder atFatal() {
+ return atLevel(Level.FATAL);
+ }
+ /**
+ * Construct a fatal log event.
+ * @return a LogBuilder.
+ * @since 2.13.0
+ */
+ @Override
+ public LogBuilder always() {
+ DefaultLogBuilder builder = logBuilder.get();
+ if (builder.isInUse()) {
+ return new DefaultLogBuilder(this);
+ }
+ return builder.reset(Level.OFF);
+ }
+ /**
+ * Construct a log event.
+ * @return a LogBuilder.
+ * @since 2.13.0
+ */
+ @Override
+ public LogBuilder atLevel(Level level) {
+ if (isEnabled(level)) {
+ return (getLogBuilder(level).reset(level));
+ } else {
+ return LogBuilder.NOOP;
+ }
+ }
+
+ private DefaultLogBuilder getLogBuilder(Level level) {
+ DefaultLogBuilder builder = logBuilder.get();
+ return Constants.ENABLE_THREADLOCALS && !builder.isInUse() ? builder : new DefaultLogBuilder(this, level);
+ }
+
+ private void readObject (final ObjectInputStream s) throws ClassNotFoundException, IOException {
+ s.defaultReadObject( );
+ try {
+ Field f = this.getClass().getDeclaredField("logBuilder");
+ f.setAccessible(true);
+ f.set(this, new LocalLogBuilder(this));
+ } catch (NoSuchFieldException | IllegalAccessException ex) {
+ StatusLogger.getLogger().warn("Unable to initialize LogBuilder");
+ }
+ }
+
+ private class LocalLogBuilder extends ThreadLocal<DefaultLogBuilder> {
+ private final AbstractLogger logger;
+ LocalLogBuilder(AbstractLogger logger) {
+ this.logger = logger;
+ }
+
+ @Override
+ protected DefaultLogBuilder initialValue() {
+ return new DefaultLogBuilder(logger);
+ }
+ }
}
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/AbstractLoggerAdapter.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/AbstractLoggerAdapter.java
index e86f990..55746e9 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/AbstractLoggerAdapter.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/AbstractLoggerAdapter.java
@@ -16,28 +16,29 @@
*/
package org.apache.logging.log4j.spi;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.util.LoaderUtil;
+
+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 <L> the Logger class to adapt
* @since 2.1
*/
-public abstract class AbstractLoggerAdapter<L> implements LoggerAdapter<L> {
+public abstract class AbstractLoggerAdapter<L> implements LoggerAdapter<L>, LoggerContextShutdownAware {
/**
* A map to store loggers for their given LoggerContexts.
*/
- protected final Map<LoggerContext, ConcurrentMap<String, L>> registry = new WeakHashMap<>();
+ protected final Map<LoggerContext, ConcurrentMap<String, L>> registry = new ConcurrentHashMap<>();
private final ReadWriteLock lock = new ReentrantReadWriteLock (true);
@@ -53,6 +54,11 @@
return loggers.get(name);
}
+ @Override
+ public void contextShutdown(LoggerContext loggerContext) {
+ registry.remove(loggerContext);
+ }
+
/**
* Gets or creates the ConcurrentMap of named loggers for a given LoggerContext.
*
@@ -77,6 +83,9 @@
if (loggers == null) {
loggers = new ConcurrentHashMap<> ();
registry.put (context, loggers);
+ if (context instanceof LoggerContextShutdownEnabled) {
+ ((LoggerContextShutdownEnabled) context).addShutdownListener(this);
+ }
}
return loggers;
} finally {
@@ -85,6 +94,14 @@
}
/**
+ * For unit testing. Consider to be private.
+ * @return The Set of LoggerContexts.
+ */
+ public Set<LoggerContext> getLoggerContexts() {
+ return new HashSet<>(registry.keySet());
+ }
+
+ /**
* Creates a new named logger for a given {@link LoggerContext}.
*
* @param name the name of the logger to create
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 c3fdc88..dfc369d 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/ExtendedLoggerWrapper.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/ExtendedLoggerWrapper.java
@@ -20,6 +20,7 @@
import org.apache.logging.log4j.Marker;
import org.apache.logging.log4j.message.Message;
import org.apache.logging.log4j.message.MessageFactory;
+import org.apache.logging.log4j.util.StackLocatorUtil;
/**
* Wrapper class that exposes the protected AbstractLogger methods to support wrapped loggers.
@@ -214,6 +215,9 @@
@Override
public void logMessage(final String fqcn, final Level level, final Marker marker, final Message message,
final Throwable t) {
+ if (requiresLocation()) {
+ logger.logMessage(level, marker, fqcn, StackLocatorUtil.calcLocation(fqcn), message, t);
+ }
logger.logMessage(fqcn, level, marker, message, t);
}
}
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 88c3b7e..3cc540a 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
@@ -30,6 +30,59 @@
Object getExternalContext();
/**
+ * Retrieve 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;
+ }
+
+ /**
+ * Store 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;
+ }
+
+ /**
+ * Store 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;
+ }
+
+ /**
+ * Remove 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;
+ }
+
+ /**
+ * Remove 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;
+ }
+
+ /**
* Returns an ExtendedLogger.
* @param name The name of the Logger to return.
* @return The logger with the specified name.
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 235ad0c..3dcee76 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
@@ -17,6 +17,7 @@
package org.apache.logging.log4j.spi;
import java.net.URI;
+import java.util.concurrent.TimeUnit;
/**
* Implemented by factories that create {@link LoggerContext} objects.
@@ -24,6 +25,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)) {
+ 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}.
*
* @param fqcn The fully qualified class name of the caller.
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggerContextKey.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggerContextKey.java
deleted file mode 100644
index 113bc45..0000000
--- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggerContextKey.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache license, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the license for the specific language governing permissions and
- * limitations under the license.
- */
-
-package org.apache.logging.log4j.spi;
-
-import org.apache.logging.log4j.message.MessageFactory;
-
-/**
- * Creates keys used in maps for use in LoggerContext implementations.
- *
- * @deprecated with no replacement - no longer used
- * @since 2.5
- */
-@Deprecated
-public class LoggerContextKey {
-
- public static String create(final String name) {
- return create(name, AbstractLogger.DEFAULT_MESSAGE_FACTORY_CLASS);
- }
-
- public static String create(final String name, final MessageFactory messageFactory) {
- final Class<? extends MessageFactory> messageFactoryClass = messageFactory != null ? messageFactory.getClass()
- : AbstractLogger.DEFAULT_MESSAGE_FACTORY_CLASS;
- return create(name, messageFactoryClass);
- }
-
- public static String create(final String name, final Class<? extends MessageFactory> messageFactoryClass) {
- final Class<? extends MessageFactory> 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 0000000..d31c583
--- /dev/null
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggerContextShutdownAware.java
@@ -0,0 +1,26 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.logging.log4j.spi;
+
+/**
+ * Interface allowing interested classes to know when a LoggerContext has shutdown - if the LoggerContext
+ * implementation provides a way to register listeners.
+ */
+public interface LoggerContextShutdownAware {
+
+ void contextShutdown(LoggerContext loggerContext);
+}
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggerContextShutdownEnabled.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggerContextShutdownEnabled.java
new file mode 100644
index 0000000..f3d59c1
--- /dev/null
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/spi/LoggerContextShutdownEnabled.java
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.logging.log4j.spi;
+
+import java.util.List;
+
+/**
+ * LoggerContexts implementing this are able register LoggerContextShutdownAware classes.
+ */
+public interface LoggerContextShutdownEnabled {
+
+ void addShutdownListener(LoggerContextShutdownAware listener);
+
+ List<LoggerContextShutdownAware> getListeners();
+}
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/MessageFactory2Adapter.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/MessageFactory2Adapter.java
deleted file mode 100644
index ff31515..0000000
--- a/log4j-api/src/main/java/org/apache/logging/log4j/spi/MessageFactory2Adapter.java
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache license, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the license for the specific language governing permissions and
- * limitations under the license.
- */
-package org.apache.logging.log4j.spi;
-
-import java.util.Objects;
-
-import org.apache.logging.log4j.message.Message;
-import org.apache.logging.log4j.message.MessageFactory;
-import org.apache.logging.log4j.message.MessageFactory2;
-import org.apache.logging.log4j.message.SimpleMessage;
-
-/**
- * Adapts a legacy MessageFactory to the new MessageFactory2 interface.
- *
- * @since 2.6
- */
-public class MessageFactory2Adapter implements MessageFactory2 {
- private final MessageFactory wrapped;
-
- public MessageFactory2Adapter(final MessageFactory wrapped) {
- this.wrapped = Objects.requireNonNull(wrapped);
- }
-
- public MessageFactory getOriginal() {
- return wrapped;
- }
-
- @Override
- public Message newMessage(final CharSequence charSequence) {
- return new SimpleMessage(charSequence);
- }
-
- @Override
- public Message newMessage(final String message, final Object p0) {
- return wrapped.newMessage(message, p0);
- }
-
- @Override
- public Message newMessage(final String message, final Object p0, final Object p1) {
- return wrapped.newMessage(message, p0, p1);
- }
-
- @Override
- public Message newMessage(final String message, final Object p0, final Object p1, final Object p2) {
- return wrapped.newMessage(message, p0, p1, p2);
- }
-
- @Override
- public Message newMessage(final String message, final Object p0, final Object p1, final Object p2,
- final Object p3) {
- return wrapped.newMessage(message, p0, p1, p2, p3);
- }
-
- @Override
- public Message newMessage(final String message, final Object p0, final Object p1, final Object p2, final Object p3,
- final Object p4) {
- return wrapped.newMessage(message, p0, p1, p2, p3, p4);
- }
-
- @Override
- public Message newMessage(final String message, final Object p0, final Object p1, final Object p2, final Object p3,
- final Object p4, final Object p5) {
- return wrapped.newMessage(message, p0, p1, p2, p3, p4, p5);
- }
-
- @Override
- public Message newMessage(final String message, final Object p0, final Object p1, final Object p2, final Object p3,
- final Object p4, final Object p5, final Object p6) {
- return wrapped.newMessage(message, p0, p1, p2, p3, p4, p5, p6);
- }
-
- @Override
- public Message newMessage(final String message, final Object p0, final Object p1, final Object p2, final Object p3,
- final Object p4, final Object p5, final Object p6, final Object p7) {
- return wrapped.newMessage(message, p0, p1, p2, p3, p4, p5, p6, p7);
- }
-
- @Override
- public Message newMessage(final String message, final Object p0, final Object p1, final Object p2, final Object p3,
- final Object p4, final Object p5, final Object p6, final Object p7, final Object p8) {
- return wrapped.newMessage(message, p0, p1, p2, p3, p4, p5, p6, p7, p8);
- }
-
- @Override
- public Message newMessage(final String message, final Object p0, final Object p1, final Object p2, final Object p3,
- final Object p4, final Object p5, final Object p6, final Object p7, final Object p8, final Object p9) {
- return wrapped.newMessage(message, p0, p1, p2, p3, p4, p5, p6, p7, p8, p9);
- }
-
- @Override
- public Message newMessage(final Object message) {
- return wrapped.newMessage(message);
- }
-
- @Override
- public Message newMessage(final String message) {
- return wrapped.newMessage(message);
- }
-
- @Override
- public Message newMessage(final String message, final Object... params) {
- return wrapped.newMessage(message, params);
- }
-}
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/spi/MutableThreadContextStack.java b/log4j-api/src/main/java/org/apache/logging/log4j/spi/MutableThreadContextStack.java
index 03c77da..be111e3 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
@@ -46,7 +46,7 @@
/**
* Constructs a new instance.
- * @param list
+ * @param list The list of items in the stack.
*/
public MutableThreadContextStack(final List<String> list) {
this.list = new ArrayList<>(list);
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 2148ddb..c4bc014 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
@@ -32,6 +32,7 @@
* collection.
*
* @param key the key whose value to return
+ * @param <V> The type of the returned value.
* @return the value for the specified key or {@code null}
*/
<V> V getValue(String key);
@@ -40,6 +41,7 @@
* Puts the specified key-value pair into the collection.
*
* @param key the key to add or remove. Keys may be {@code null}.
+ * @param <V> The type of the stored and returned value.
* @param value the value to add. Values may be {@code null}.
*/
<V> void putValue(String key, V value);
@@ -48,6 +50,7 @@
* Puts all given key-value pairs into the collection.
*
* @param values the map of key-value pairs to add
+ * @param <V> The type of the value being added.
*/
<V> void putAllValues(Map<String, V> values);
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/Assert.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/Assert.java
new file mode 100644
index 0000000..7a7a925
--- /dev/null
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/Assert.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 java.util.Collection;
+import java.util.Map;
+
+/**
+ * Utility class providing common validation logic.
+ */
+public final class Assert {
+ private Assert() {
+ }
+
+ /**
+ * Checks if an object has empty semantics. The following scenarios are considered empty:
+ * <ul>
+ * <li>{@code null}</li>
+ * <li>empty {@link CharSequence}</li>
+ * <li>empty array</li>
+ * <li>empty {@link Iterable}</li>
+ * <li>empty {@link Map}</li>
+ * </ul>
+ *
+ * @param o value to check for emptiness
+ * @return true if the value is empty, false otherwise
+ * @since 2.8
+ */
+ public static boolean isEmpty(final Object o) {
+ if (o == null) {
+ return true;
+ }
+ if (o instanceof CharSequence) {
+ return ((CharSequence) o).length() == 0;
+ }
+ if (o.getClass().isArray()) {
+ return ((Object[]) o).length == 0;
+ }
+ if (o instanceof Collection) {
+ return ((Collection<?>) o).isEmpty();
+ }
+ if (o instanceof Map) {
+ return ((Map<?, ?>) o).isEmpty();
+ }
+ return false;
+ }
+
+ /**
+ * Opposite of {@link #isEmpty(Object)}.
+ *
+ * @param o value to check for non-emptiness
+ * @return true if the value is non-empty, false otherwise
+ * @since 2.8
+ */
+ public static boolean isNonEmpty(final Object o) {
+ return !isEmpty(o);
+ }
+
+ /**
+ * Checks a value for emptiness and throws an IllegalArgumentException if it's empty.
+ *
+ * @param value value to check for emptiness
+ * @param <T> type of value
+ * @return the provided value if non-empty
+ * @since 2.8
+ */
+ public static <T> T requireNonEmpty(final T value) {
+ return requireNonEmpty(value, "");
+ }
+
+ /**
+ * Checks a value for emptiness and throws an IllegalArgumentException if it's empty.
+ *
+ * @param value value to check for emptiness
+ * @param message message to provide in exception
+ * @param <T> type of value
+ * @return the provided value if non-empty
+ * @since 2.8
+ */
+ public static <T> T requireNonEmpty(final T value, final String message) {
+ if (isEmpty(value)) {
+ throw new IllegalArgumentException(message);
+ }
+ return value;
+ }
+
+ public static int valueIsAtLeast(final int value, final int minValue) {
+ if (value < minValue) {
+ throw new IllegalArgumentException("Value should be at least " + minValue + " but was " + value);
+ }
+ return value;
+ }
+}
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 c0d18d7..61e299a 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
@@ -46,7 +46,7 @@
* 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.
* <p>
- * 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".
* </p>
* @since 2.9
@@ -89,7 +89,10 @@
}
private static int getMajorVersion() {
- final String version = System.getProperty("java.version");
+ return getMajorVersion(System.getProperty("java.version"));
+ }
+
+ static int getMajorVersion(final String version) {
final String[] parts = version.split("-|\\.");
boolean isJEP223;
try {
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 5c67b01..9209ded 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/util/IndexedReadOnlyStringMap.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/IndexedReadOnlyStringMap.java
@@ -44,6 +44,7 @@
* or {@code null} if the specified index is less than zero or greater or equal to the size of this collection.
*
* @param index the index of the value to return
+ * @param <V> The type of the returned value.
* @return the value at the specified index or {@code null}
*/
<V> V getValueAt(final int index);
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 0000000..06472e3
--- /dev/null
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/InternalException.java
@@ -0,0 +1,54 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.logging.log4j.util;
+
+/**
+ * Exception thrown when an error occurs while accessing internal resources. This is generally used to
+ * convert checked exceptions to runtime exceptions.
+ */
+public class InternalException extends RuntimeException {
+
+ private static final long serialVersionUID = 6366395965071580537L;
+
+ /**
+ * Construct an exception with a message.
+ *
+ * @param message The reason for the exception
+ */
+ public InternalException(final String message) {
+ super(message);
+ }
+
+ /**
+ * Construct an exception with a message and underlying cause.
+ *
+ * @param message The reason for the exception
+ * @param cause The underlying cause of the exception
+ */
+ public InternalException(final String message, final Throwable cause) {
+ super(message, cause);
+ }
+
+ /**
+ * Construct an exception with an underlying cause.
+ *
+ * @param cause The underlying cause of the exception
+ */
+ public InternalException(final Throwable cause) {
+ super(cause);
+ }
+}
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/LambdaUtil.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/LambdaUtil.java
index a9f1684..515e24f 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/util/LambdaUtil.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/LambdaUtil.java
@@ -81,6 +81,7 @@
* Returns a Message, either the value supplied by the specified function, or a new Message created by the specified
* Factory.
* @param supplier a lambda expression or {@code null}
+ * @param messageFactory The MessageFactory.
* @return the Message resulting from evaluating the lambda expression or the Message created by the factory for
* supplied values that are not of type Message
*/
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/LoaderUtil.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/LoaderUtil.java
index 021dcde..12b60db 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/util/LoaderUtil.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/LoaderUtil.java
@@ -21,11 +21,9 @@
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;
/**
@@ -77,6 +75,26 @@
}
/**
+ * Returns the ClassLoader to use.
+ * @return the ClassLoader.
+ */
+ public static ClassLoader getClassLoader() {
+ return getClassLoader(LoaderUtil.class, null);
+ }
+
+ // TODO: this method could use some explanation
+ public static ClassLoader getClassLoader(final Class<?> class1, final Class<?> class2) {
+ final ClassLoader threadContextClassLoader = getThreadContextClassLoader();
+ final ClassLoader loader1 = class1 == null ? null : class1.getClassLoader();
+ final ClassLoader loader2 = class2 == null ? null : class2.getClassLoader();
+
+ if (isChild(threadContextClassLoader, loader1)) {
+ return isChild(threadContextClassLoader, loader2) ? threadContextClassLoader : loader2;
+ }
+ return isChild(loader1, loader2) ? loader1 : loader2;
+ }
+
+ /**
* Gets the current Thread ClassLoader. Returns the system ClassLoader if the TCCL is {@code null}. If the system
* ClassLoader is {@code null} as well, then the ClassLoader for this class is returned. If running with a
* {@link SecurityManager} that does not allow access to the Thread ClassLoader or system ClassLoader, then the
@@ -94,6 +112,26 @@
}
/**
+ * Determines if one ClassLoader is a child of another ClassLoader. Note that a {@code null} ClassLoader is
+ * interpreted as the system ClassLoader as per convention.
+ *
+ * @param loader1 the ClassLoader to check for childhood.
+ * @param loader2 the ClassLoader to check for parenthood.
+ * @return {@code true} if the first ClassLoader is a strict descendant of the second ClassLoader.
+ */
+ private static boolean isChild(final ClassLoader loader1, final ClassLoader loader2) {
+ if (loader1 != null && loader2 != null) {
+ ClassLoader parent = loader1.getParent();
+ while (parent != null && parent != loader2) {
+ parent = parent.getParent();
+ }
+ // once parent is null, we're at the system CL, which would indicate they have separate ancestry
+ return parent != null;
+ }
+ return loader1 != null;
+ }
+
+ /**
*
*/
private static class ThreadContextClassLoaderGetter implements PrivilegedAction<ClassLoader> {
@@ -109,26 +147,16 @@
}
public static ClassLoader[] getClassLoaders() {
- List<ClassLoader> classLoaders = new ArrayList<>();
- ClassLoader tcl = getThreadContextClassLoader();
- classLoaders.add(tcl);
- if (!isForceTccl()) {
- // Some implementations may use null to represent the bootstrap class loader.
- ClassLoader current = LoaderUtil.class.getClassLoader();
- if (current != null && current != tcl) {
- classLoaders.add(current);
- ClassLoader parent = current.getParent();
- while (parent != null && !classLoaders.contains(parent)) {
- classLoaders.add(parent);
- }
- }
- ClassLoader parent = tcl == null ? null : tcl.getParent();
- while (parent != null && !classLoaders.contains(parent)) {
- classLoaders.add(parent);
- parent = parent.getParent();
- }
- ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
- if (!classLoaders.contains(systemClassLoader)) {
+ final Collection<ClassLoader> classLoaders = new LinkedHashSet<>();
+ final ClassLoader tcl = getThreadContextClassLoader();
+ if (tcl != null) {
+ classLoaders.add(tcl);
+ }
+ if (!isForceTccl()) {
+ accumulateClassLoaders(LoaderUtil.class.getClassLoader(), classLoaders);
+ accumulateClassLoaders(tcl == null ? null : tcl.getParent(), classLoaders);
+ final ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
+ if (systemClassLoader != null) {
classLoaders.add(systemClassLoader);
}
}
@@ -136,6 +164,17 @@
}
/**
+ * Adds the provided loader to the loaders collection, and traverses up the tree until either a null
+ * value or a classloader which has already been added is encountered.
+ */
+ private static void accumulateClassLoaders(ClassLoader loader, Collection<ClassLoader> loaders) {
+ // Some implementations may use null to represent the bootstrap class loader.
+ if (loader != null && loaders.add(loader)) {
+ accumulateClassLoaders(loader.getParent(), loaders);
+ }
+ }
+
+ /**
* Determines if a named Class can be loaded or not.
*
* @param className The class name.
@@ -168,16 +207,20 @@
return Class.forName(className);
}
try {
- return getThreadContextClassLoader().loadClass(className);
+ ClassLoader tccl = getThreadContextClassLoader();
+ if (tccl != null) {
+ return tccl.loadClass(className);
+ }
} catch (final Throwable ignored) {
- return Class.forName(className);
}
+ return Class.forName(className);
}
/**
* Loads and instantiates a Class using the default constructor.
*
* @param clazz The class.
+ * @param <T> The Class's type.
* @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
@@ -198,6 +241,7 @@
* Loads and instantiates a Class using the default constructor.
*
* @param className The class name.
+ * @param <T> The class's type.
* @return new instance of the class.
* @throws ClassNotFoundException if the class isn't available to the usual ClassLoaders
* @throws IllegalAccessException if the class can't be instantiated through a public constructor
@@ -230,7 +274,7 @@
public static <T> T newCheckedInstanceOf(final String className, final Class<T> clazz)
throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException,
IllegalAccessException {
- return clazz.cast(newInstanceOf(className));
+ return newInstanceOf(loadClass(className).asSubclass(clazz));
}
/**
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 f38f75c..70edb22 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
@@ -28,7 +28,7 @@
* <p>Implementors are free to cache values or return a new or distinct value each time the supplier is invoked.
*
* <p><strong>DEPRECATED:</strong> this class should not normally be used outside a Java 8+ lambda syntax. Instead,
- * {@link Supplier Supplier<Message>} should be used as an anonymous class. Both this and {@link Supplier} will be
+ * {@link Supplier Supplier<Message>} should be used as an anonymous class. Both this and {@link Supplier} will be
* removed in 3.0.
* </p>
*
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/NameUtil.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/NameUtil.java
new file mode 100644
index 0000000..31cbf7e
--- /dev/null
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/NameUtil.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 java.security.MessageDigest;
+
+import org.apache.logging.log4j.util.Strings;
+
+/**
+ *
+ */
+public final class NameUtil {
+
+ private static final int MASK = 0xff;
+
+ private NameUtil() {
+ }
+
+ public static String getSubName(final String name) {
+ if (Strings.isEmpty(name)) {
+ return null;
+ }
+ final int i = name.lastIndexOf('.');
+ return i > 0 ? name.substring(0, i) : Strings.EMPTY;
+ }
+
+ public static String md5(final String string) {
+ try {
+ final MessageDigest digest = MessageDigest.getInstance("MD5");
+ digest.update(string.getBytes());
+ final byte[] bytes = digest.digest();
+ final StringBuilder md5 = new StringBuilder();
+ for (final byte b : bytes) {
+ final String hex = Integer.toHexString(MASK & b);
+ if (hex.length() == 1) {
+ md5.append('0');
+ }
+ md5.append(hex);
+ }
+ return md5.toString();
+ } catch (final Exception ex) {
+ return string;
+ }
+ }
+}
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 694175c..837347c 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
@@ -32,6 +32,9 @@
// No @Target: No restrictions yet on what code elements may be annotated or not.
@Retention(RetentionPolicy.CLASS) // Currently no need to reflectively discover this annotation at runtime.
public @interface PerformanceSensitive {
- /** Description of why this is written the way it is. */
+ /**
+ * Is this ever used?
+ * @return An array of strings.
+ */
String[] value() default "";
}
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/PropertiesUtil.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/PropertiesUtil.java
index 2a9ff7f..0c0318b 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
@@ -19,6 +19,9 @@
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.List;
@@ -46,6 +49,7 @@
public final class PropertiesUtil {
private static final String LOG4J_PROPERTIES_FILE_NAME = "log4j2.component.properties";
+ private static final String LOG4J_SYSTEM_PROPERTIES_FILE_NAME = "log4j2.system.properties";
private static final PropertiesUtil LOG4J_PROPERTIES = new PropertiesUtil(LOG4J_PROPERTIES_FILE_NAME);
private final Environment environment;
@@ -153,6 +157,24 @@
}
/**
+ * 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, String key, Supplier<Boolean> supplier) {
+ for (String prefix : prefixes) {
+ if (hasProperty(prefix + key)) {
+ return getBooleanProperty(prefix + key);
+ }
+ }
+ return supplier != null ? supplier.get() : null;
+ }
+
+ /**
* Gets the named property as a Charset value.
*
* @param name the name of the property to look up
@@ -178,9 +200,9 @@
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);
}
@@ -230,6 +252,24 @@
}
/**
+ * 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, String key, Supplier<Integer> supplier) {
+ for (String prefix : prefixes) {
+ if (hasProperty(prefix + key)) {
+ return getIntegerProperty(prefix + key, 0);
+ }
+ }
+ return supplier != null ? supplier.get() : null;
+ }
+
+ /**
* Gets the named property as a long.
*
* @param name the name of the property to look up
@@ -247,6 +287,76 @@
}
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, String key, Supplier<Long> supplier) {
+ for (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, Duration defaultValue) {
+ final String prop = getStringProperty(name);
+ if (prop != null) {
+ return TimeUnit.getDuration(prop);
+ }
+ return defaultValue;
+ }
+
+ /**
+ * Retrieves a property that may be prefixed by more than one string.
+ * @param prefixes The array of prefixes.
+ * @param key The key to locate.
+ * @param supplier The method to call to derive the default value. If the value is null, null will be returned
+ * if no property is found.
+ * @return The value or null if it is not found.
+ * @since 2.13.0
+ */
+ public Duration getDurationProperty(final String[] prefixes, String key, Supplier<Duration> supplier) {
+ for (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, String key, Supplier<String> supplier) {
+ for (String prefix : prefixes) {
+ String result = getStringProperty(prefix + key);
+ if (result != null) {
+ return result;
+ }
+ }
+ return supplier != null ? supplier.get() : null;
+ }
/**
* Gets the named property as a String.
@@ -315,10 +425,32 @@
private final Map<List<CharSequence>, String> tokenized = new ConcurrentHashMap<>();
private Environment(final PropertySource propertySource) {
- sources.add(propertySource);
- for (final PropertySource source : ServiceLoader.load(PropertySource.class, PropertySource.class.getClassLoader())) {
- sources.add(source);
+ PropertyFilePropertySource sysProps = new PropertyFilePropertySource(LOG4J_SYSTEM_PROPERTIES_FILE_NAME);
+ try {
+ sysProps.forEach(new BiConsumer<String, String>() {
+ @Override
+ public void accept(String key, String value) {
+ if (System.getProperty(key) == null) {
+ System.setProperty(key, value);
+ }
+ }
+ });
+ } catch (SecurityException ex) {
+ // Access to System Properties is restricted so just skip it.
}
+ sources.add(propertySource);
+ for (final ClassLoader classLoader : LoaderUtil.getClassLoaders()) {
+ try {
+ for (final PropertySource source : ServiceLoader.load(PropertySource.class, classLoader)) {
+ sources.add(source);
+ }
+ } catch (final Throwable ex) {
+ /* Don't log anything to the console. It may not be a problem that a PropertySource
+ * isn't accessible.
+ */
+ }
+ }
+
reload();
}
@@ -363,6 +495,11 @@
if (hasSystemProperty(key)) {
return System.getProperty(key);
}
+ for (final PropertySource source : sources) {
+ if (source.containsProperty(key)) {
+ return source.getProperty(key);
+ }
+ }
return tokenized.get(PropertySource.Util.tokenize(key));
}
@@ -439,4 +576,40 @@
return getStringProperty("os.name", "").startsWith("Windows");
}
+ private enum TimeUnit {
+ NANOS("ns,nano,nanos,nanosecond,nanoseconds", ChronoUnit.NANOS),
+ MICROS("us,micro,micros,microsecond,microseconds", ChronoUnit.MICROS),
+ MILLIS("ms,milli,millis,millsecond,milliseconds", ChronoUnit.MILLIS),
+ SECONDS("s,second,seconds", ChronoUnit.SECONDS),
+ MINUTES("m,minute,minutes", ChronoUnit.MINUTES),
+ HOURS("h,hour,hours", ChronoUnit.HOURS),
+ DAYS("d,day,days", ChronoUnit.DAYS);
+
+ private final String[] descriptions;
+ private final ChronoUnit timeUnit;
+
+ TimeUnit(String descriptions, ChronoUnit timeUnit) {
+ this.descriptions = descriptions.split(",");
+ this.timeUnit = timeUnit;
+ }
+
+ ChronoUnit getTimeUnit() {
+ return this.timeUnit;
+ }
+
+ static Duration getDuration(String time) {
+ String value = time.trim();
+ TemporalUnit temporalUnit = ChronoUnit.MILLIS;
+ long timeVal = 0;
+ for (TimeUnit timeUnit : values()) {
+ for (String suffix : timeUnit.descriptions) {
+ if (value.endsWith(suffix)) {
+ temporalUnit = timeUnit.timeUnit;
+ timeVal = Long.parseLong(value.substring(0, value.length() - suffix.length()));
+ }
+ }
+ }
+ return Duration.of(timeVal, temporalUnit);
+ }
+ }
}
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/PropertySource.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/PropertySource.java
index 75399d9..77ccd4b 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
@@ -45,7 +45,8 @@
*
* @param action action to perform on each key/value pair
*/
- void forEach(BiConsumer<String, String> action);
+ default void forEach(BiConsumer<String, String> action) {
+ }
/**
* Converts a list of property name tokens into a normal form. For example, a list of tokens such as
@@ -54,7 +55,30 @@
* @param tokens list of property name tokens
* @return a normalized property name using the given tokens
*/
- CharSequence getNormalForm(Iterable<? extends CharSequence> tokens);
+ default CharSequence getNormalForm(Iterable<? extends CharSequence> 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(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(String key) {
+ return false;
+ }
/**
* Comparator for ordering PropertySource instances by priority.
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 9645a71..93fad64 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
@@ -113,18 +113,6 @@
}
}
- /**
- * @deprecated Use {@link #loadProvider(java.net.URL, ClassLoader)} instead. Will be removed in 3.0.
- */
- @Deprecated
- protected static void loadProviders(final Enumeration<URL> urls, final ClassLoader cl) {
- if (urls != null) {
- while (urls.hasMoreElements()) {
- loadProvider(urls.nextElement(), cl);
- }
- }
- }
-
public static Iterable<Provider> getProviders() {
lazyInit();
return PROVIDERS;
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 df9a1c8..cd76c57 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/util/ReadOnlyStringMap.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/ReadOnlyStringMap.java
@@ -88,6 +88,7 @@
* Returns the value for the specified key, or {@code null} if the specified key does not exist in this collection.
*
* @param key the key whose value to return.
+ * @param <V> The type of the value to be returned.
* @return the value for the specified key or {@code null}.
*/
<V> V getValue(final String key);
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/ReflectionUtil.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/ReflectionUtil.java
new file mode 100644
index 0000000..3869fe2
--- /dev/null
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/ReflectionUtil.java
@@ -0,0 +1,205 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+
+package org.apache.logging.log4j.util;
+
+import java.lang.reflect.AccessibleObject;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Member;
+import java.lang.reflect.Modifier;
+import java.util.Objects;
+
+/**
+ * Utility class for performing common reflective operations.
+ *
+ * @since 2.1
+ */
+public final class ReflectionUtil {
+ private ReflectionUtil() {
+ }
+
+ /**
+ * Indicates whether or not a {@link Member} is both public and is contained in a public class.
+ *
+ * @param <T> type of the object whose accessibility to test
+ * @param member the Member to check for public accessibility (must not be {@code null}).
+ * @return {@code true} if {@code member} is public and contained in a public class.
+ * @throws NullPointerException if {@code member} is {@code null}.
+ */
+ public static <T extends AccessibleObject & Member> boolean isAccessible(final T member) {
+ Objects.requireNonNull(member, "No member provided");
+ return Modifier.isPublic(member.getModifiers()) && Modifier.isPublic(member.getDeclaringClass().getModifiers());
+ }
+
+ /**
+ * Makes a {@link Member} {@link AccessibleObject#isAccessible() accessible} if the member is not public.
+ *
+ * @param <T> type of the object to make accessible
+ * @param member the Member to make accessible (must not be {@code null}).
+ * @throws NullPointerException if {@code member} is {@code null}.
+ */
+ public static <T extends AccessibleObject & Member> void makeAccessible(final T member) {
+ if (!isAccessible(member) && !member.isAccessible()) {
+ member.setAccessible(true);
+ }
+ }
+
+ /**
+ * Makes a {@link Field} {@link AccessibleObject#isAccessible() accessible} if it is not public or if it is final.
+ *
+ * <p>Note that using this method to make a {@code final} field writable will most likely not work very well due to
+ * compiler optimizations and the like.</p>
+ *
+ * @param field the Field to make accessible (must not be {@code null}).
+ * @throws NullPointerException if {@code field} is {@code null}.
+ */
+ public static void makeAccessible(final Field field) {
+ Objects.requireNonNull(field, "No field provided");
+ if ((!isAccessible(field) || Modifier.isFinal(field.getModifiers())) && !field.isAccessible()) {
+ field.setAccessible(true);
+ }
+ }
+
+ /**
+ * Gets the value of a {@link Field}, making it accessible if required.
+ *
+ * @param field the Field to obtain a value from (must not be {@code null}).
+ * @param instance the instance to obtain the field value from or {@code null} only if the field is static.
+ * @return the value stored by the field.
+ * @throws NullPointerException if {@code field} is {@code null}, or if {@code instance} is {@code null} but
+ * {@code field} is not {@code static}.
+ * @see Field#get(Object)
+ */
+ public static Object getFieldValue(final Field field, final Object instance) {
+ makeAccessible(field);
+ if (!Modifier.isStatic(field.getModifiers())) {
+ Objects.requireNonNull(instance, "No instance given for non-static field");
+ }
+ try {
+ return field.get(instance);
+ } catch (final IllegalAccessException e) {
+ throw new UnsupportedOperationException(e);
+ }
+ }
+
+ /**
+ * Gets the value of a static {@link Field}, making it accessible if required.
+ *
+ * @param field the Field to obtain a value from (must not be {@code null}).
+ * @return the value stored by the static field.
+ * @throws NullPointerException if {@code field} is {@code null}, or if {@code field} is not {@code static}.
+ * @see Field#get(Object)
+ */
+ public static Object getStaticFieldValue(final Field field) {
+ return getFieldValue(field, null);
+ }
+
+ /**
+ * Sets the value of a {@link Field}, making it accessible if required.
+ *
+ * @param field the Field to write a value to (must not be {@code null}).
+ * @param instance the instance to write the value to or {@code null} only if the field is static.
+ * @param value the (possibly wrapped) value to write to the field.
+ * @throws NullPointerException if {@code field} is {@code null}, or if {@code instance} is {@code null} but
+ * {@code field} is not {@code static}.
+ * @see Field#set(Object, Object)
+ */
+ public static void setFieldValue(final Field field, final Object instance, final Object value) {
+ makeAccessible(field);
+ if (!Modifier.isStatic(field.getModifiers())) {
+ Objects.requireNonNull(instance, "No instance given for non-static field");
+ }
+ try {
+ field.set(instance, value);
+ } catch (final IllegalAccessException e) {
+ throw new UnsupportedOperationException(e);
+ }
+ }
+
+ /**
+ * Sets the value of a static {@link Field}, making it accessible if required.
+ *
+ * @param field the Field to write a value to (must not be {@code null}).
+ * @param value the (possibly wrapped) value to write to the field.
+ * @throws NullPointerException if {@code field} is {@code null}, or if {@code field} is not {@code static}.
+ * @see Field#set(Object, Object)
+ */
+ public static void setStaticFieldValue(final Field field, final Object value) {
+ setFieldValue(field, null, value);
+ }
+
+ /**
+ * Gets the default (no-arg) constructor for a given class.
+ *
+ * @param clazz the class to find a constructor for
+ * @param <T> the type made by the constructor
+ * @return the default constructor for the given class
+ * @throws IllegalStateException if no default constructor can be found
+ */
+ public static <T> Constructor<T> getDefaultConstructor(final Class<T> clazz) {
+ Objects.requireNonNull(clazz, "No class provided");
+ try {
+ final Constructor<T> constructor = clazz.getDeclaredConstructor();
+ makeAccessible(constructor);
+ return constructor;
+ } catch (final NoSuchMethodException ignored) {
+ try {
+ final Constructor<T> constructor = clazz.getConstructor();
+ makeAccessible(constructor);
+ return constructor;
+ } catch (final NoSuchMethodException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+ }
+
+ /**
+ * Constructs a new {@code T} object using the default constructor of its class. Any exceptions thrown by the
+ * constructor will be rethrown by this method, possibly wrapped in an {@link InternalException}.
+ *
+ * @param clazz the class to use for instantiation.
+ * @param <T> the type of the object to construct.
+ * @return a new instance of T made from its default constructor.
+ * @throws IllegalArgumentException if the given class is abstract, an interface, an array class, a primitive type,
+ * or void
+ * @throws IllegalStateException if access is denied to the constructor, or there are no default constructors
+ * @throws InternalException wrapper of the the underlying exception if checked
+ */
+ public static <T> T instantiate(final Class<T> clazz) {
+ Objects.requireNonNull(clazz, "No class provided");
+ final Constructor<T> constructor = getDefaultConstructor(clazz);
+ try {
+ return constructor.newInstance();
+ } catch (final LinkageError | InstantiationException e) {
+ // LOG4J2-1051
+ // On platforms like Google App Engine and Android, some JRE classes are not supported: JMX, JNDI, etc.
+ throw new IllegalArgumentException(e);
+ } catch (final IllegalAccessException e) {
+ throw new IllegalStateException(e);
+ } catch (final InvocationTargetException e) {
+ if (e.getCause() != null) {
+ if (e.getCause() instanceof RuntimeException) {
+ throw (RuntimeException) e.getCause();
+ }
+ throw new InternalException(e.getCause());
+ }
+ throw new InternalException("Error creating new instance of " + clazz.getName(), e);
+ }
+ }
+}
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/SortedArrayStringMap.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/SortedArrayStringMap.java
index 6d3eadb..2404ac0 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
@@ -502,6 +502,9 @@
* Save the state of the {@code SortedArrayStringMap} instance to a stream (i.e.,
* serialize it).
*
+ * @param s The ObjectOutputStream.
+ * @throws IOException if there is an error serializing the object to the stream.
+ *
* @serialData The <i>capacity</i> of the SortedArrayStringMap (the length of the
* bucket array) is emitted (int), followed by the
* <i>size</i> (an int, the number of key-value
@@ -590,6 +593,9 @@
/**
* Reconstitute the {@code SortedArrayStringMap} instance from a stream (i.e.,
* deserialize it).
+ * @param s The ObjectInputStream.
+ * @throws IOException If there is an error reading the input stream.
+ * @throws ClassNotFoundException if the class to be instantiated could not be found.
*/
private void readObject(final java.io.ObjectInputStream s) throws IOException, ClassNotFoundException {
if (!(s instanceof FilteredObjectInputStream) && setObjectInputFilter == null) {
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/StackLocator.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/StackLocator.java
index 4eba44b..7a9c234 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
@@ -102,6 +102,9 @@
if (depth < 0) {
throw new IndexOutOfBoundsException(Integer.toString(depth));
}
+ if (GET_CALLER_CLASS == null) {
+ return null;
+ }
// 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 {
@@ -170,13 +173,17 @@
}
// 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;
}
- last = stackTrace[i];
+ if (found && !fqcnOfLogger.equals(className)) {
+ return stackTrace[i];
+ }
}
return null;
}
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/StackLocatorUtil.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/StackLocatorUtil.java
index 56812fe..3d51f1d 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/util/StackLocatorUtil.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/StackLocatorUtil.java
@@ -16,6 +16,9 @@
*/
package org.apache.logging.log4j.util;
+import org.apache.logging.log4j.status.StatusLogger;
+
+import java.util.NoSuchElementException;
import java.util.Stack;
/**
@@ -23,6 +26,7 @@
*/
public final class StackLocatorUtil {
private static StackLocator stackLocator = null;
+ private static volatile boolean errorLogged;
static {
stackLocator = StackLocator.getInstance();
@@ -68,6 +72,14 @@
}
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/StringBuilders.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/StringBuilders.java
index 48fa98f..7ae03eb 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
@@ -35,7 +35,9 @@
* @return {@code "value"}
*/
public static StringBuilder appendDqValue(final StringBuilder sb, final Object value) {
- return sb.append(Chars.DQUOTE).append(value).append(Chars.DQUOTE);
+ sb.append(Chars.DQUOTE);
+ appendValue(sb, value);
+ return sb.append(Chars.DQUOTE);
}
/**
@@ -58,7 +60,8 @@
* @return the specified StringBuilder
*/
public static StringBuilder appendKeyDqValue(final StringBuilder sb, final String key, final Object value) {
- return sb.append(key).append(Chars.EQ).append(Chars.DQUOTE).append(value).append(Chars.DQUOTE);
+ sb.append(key).append(Chars.EQ);
+ return appendDqValue(sb, value);
}
/**
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 e4ce7e8..ef1c28f 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/util/StringMap.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/StringMap.java
@@ -95,4 +95,4 @@
* @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 c369a4e..be50ab8 100644
--- a/log4j-api/src/main/java/org/apache/logging/log4j/util/Strings.java
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/Strings.java
@@ -19,6 +19,7 @@
import java.util.Iterator;
import java.util.Locale;
import java.util.Objects;
+import java.util.Optional;
/**
* <em>Consider this class private.</em>
@@ -54,14 +55,23 @@
}
/**
- * 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 or all characters are {@link Character#isWhitespace(char)}
*/
public static boolean isBlank(final String s) {
- return s == null || s.trim().isEmpty();
+ if (s == null || s.isEmpty()) {
+ return true;
+ }
+ for (int i = 0; i < s.length(); i++) {
+ char c = s.charAt(i);
+ if (!Character.isWhitespace(c)) {
+ return false;
+ }
+ }
+ return true;
}
/**
@@ -128,6 +138,43 @@
}
/**
+ * <p>Gets the leftmost {@code len} characters of a String.</p>
+ *
+ * <p>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.</p>
+ *
+ * <pre>
+ * StringUtils.left(null, *) = null
+ * StringUtils.left(*, -ve) = ""
+ * StringUtils.left("", *) = ""
+ * StringUtils.left("abc", 0) = ""
+ * StringUtils.left("abc", 2) = "ab"
+ * StringUtils.left("abc", 4) = "abc"
+ * </pre>
+ *
+ * <p>
+ * Copied from Apache Commons Lang org.apache.commons.lang3.StringUtils.
+ * </p>
+ *
+ * @param str the String to get the leftmost characters from, may be null
+ * @param len the length of the required String
+ * @return the leftmost characters, {@code null} if null String input
+ */
+ public static String left(final String str, final int len) {
+ if (str == null) {
+ return null;
+ }
+ if (len < 0) {
+ return EMPTY;
+ }
+ if (str.length() <= len) {
+ return str;
+ }
+ return str.substring(0, len);
+ }
+
+ /**
* Returns a quoted string.
*
* @param str a String
@@ -143,7 +190,7 @@
* @return a new string
* @see String#toLowerCase(Locale)
*/
- public String toRootUpperCase(final String str) {
+ public static String toRootUpperCase(final String str) {
return str.toUpperCase(Locale.ROOT);
}
@@ -177,6 +224,18 @@
}
/**
+ * Removes control characters from both ends of this String returning {@code Optional.empty()} if the String is
+ * empty ("") after the trim or if it is {@code null}.
+ * @param str The String to trim.
+ * @return An Optional containing the String.
+ *
+ * @see #trimToNull(String)
+ */
+ public static Optional<String> trimToOptional(final String str) {
+ return Optional.ofNullable(str).map(String::trim).filter(s -> !s.isEmpty());
+ }
+
+ /**
* <p>Joins the elements of the provided {@code Iterable} into
* a single String containing the provided elements.</p>
*
@@ -236,4 +295,23 @@
return buf.toString();
}
+ /**
+ * Creates a new string repeating given {@code str} {@code count} times.
+ * @param str input string
+ * @param count the repetition count
+ * @return the new string
+ * @throws IllegalArgumentException if either {@code str} is null or {@code count} is negative
+ */
+ public static String repeat(final String str, final int count) {
+ Objects.requireNonNull(str, "str");
+ if (count < 0) {
+ throw new IllegalArgumentException("count");
+ }
+ StringBuilder sb = new StringBuilder(str.length() * count);
+ for (int index = 0; index < count; index++) {
+ sb.append(str);
+ }
+ return sb.toString();
+ }
+
}
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 0000000..a597aae
--- /dev/null
+++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/Timer.java
@@ -0,0 +1,284 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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 the times to be aggregated.
+ */
+public class Timer implements Serializable, StringBuilderFormattable
+{
+ private static final long serialVersionUID = 9175191792439630013L;
+
+ private final String name; // The timer's name
+ 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 final long NANO_PER_SECOND = 1000000000L;
+ private static final long NANO_PER_MINUTE = NANO_PER_SECOND * 60;
+ private static final long NANO_PER_HOUR = NANO_PER_MINUTE * 60;
+ private final ThreadLocal<Long> startTime = new ThreadLocal<Long>() {
+ @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.
+ * @param iterations the number of iterations that will take place.
+ */
+ 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.
+ * @return the String result of the timer completing.
+ */
+ 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);
+ long time = startTime.get();
+ result = 29 * result + (int) (time ^ (time >>> 32));
+ result = 29 * result + (int) (elapsedTime ^ (elapsedTime >>> 32));
+ return result;
+ }
+
+}
diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/Unbox.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/Unbox.java
index bb59d46..70cd35f 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
@@ -120,8 +120,8 @@
return false;
}
}
- private static ThreadLocal<State> threadLocalState = new ThreadLocal<>();
- private static WebSafeState webSafeState = new WebSafeState();
+ private static final ThreadLocal<State> threadLocalState = new ThreadLocal<>();
+ private static final WebSafeState webSafeState = new WebSafeState();
private Unbox() {
// this is a utility
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
index a152aad..4a703cd 100644
--- a/log4j-api/src/test/java/org/apache/logging/log4j/AbstractLoggerTest.java
+++ b/log4j-api/src/test/java/org/apache/logging/log4j/AbstractLoggerTest.java
@@ -31,7 +31,6 @@
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.util.MessageSupplier;
@@ -989,7 +988,7 @@
private int objectCount;
CountingLogger() {
- super("CountingLogger", new MessageFactory2Adapter(ParameterizedMessageFactory.INSTANCE));
+ super("CountingLogger", ParameterizedMessageFactory.INSTANCE);
}
void setCurrentLevel(final Level currentLevel) {
@@ -1216,7 +1215,7 @@
private final boolean expectingThrowables;
ThrowableExpectingLogger(final boolean expectingThrowables) {
- super("ThrowableExpectingLogger", new MessageFactory2Adapter(ParameterizedMessageFactory.INSTANCE));
+ super("ThrowableExpectingLogger", ParameterizedMessageFactory.INSTANCE);
this.expectingThrowables = expectingThrowables;
}
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
index 5691e0e..814fc10 100644
--- a/log4j-api/src/test/java/org/apache/logging/log4j/LevelTest.java
+++ b/log4j-api/src/test/java/org/apache/logging/log4j/LevelTest.java
@@ -54,6 +54,17 @@
}
@Test
+ public void testLevelsWithSpaces() {
+ Level level = Level.toLevel(" INFO ");
+ assertNotNull(level);
+ assertEquals(Level.INFO, level);
+
+ level = Level.valueOf(" INFO ");
+ assertNotNull(level);
+ assertEquals(Level.INFO, level);
+ }
+
+ @Test
public void testIsInRangeErrorToDebug() {
assertFalse(Level.OFF.isInRange(Level.ERROR, Level.DEBUG));
assertFalse(Level.FATAL.isInRange(Level.ERROR, Level.DEBUG));
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
index 2321c64..53afb51 100644
--- a/log4j-api/src/test/java/org/apache/logging/log4j/LoggerTest.java
+++ b/log4j-api/src/test/java/org/apache/logging/log4j/LoggerTest.java
@@ -1,619 +1,628 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache license, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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<String> 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<Message>() {
- @Override
- public Message get() {
- return new ObjectMessage(1);
- }
- }, new Supplier<Message>() {
- @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<String>() {
- @Override
- public String get() {
- return "1";
- }
- }, new Supplier<String>() {
- @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;
- }
- }
-}
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY 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.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.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<String> results = logger.getEntries();
+
+ @Test
+ public void builder() {
+ logger.atDebug().withLocation().log("Hello");
+ logger.atError().withMarker(marker).log("Hello {}", "John");
+ logger.atWarn().withThrowable(new Throwable("This is a test")).log((Message) new SimpleMessage("Log4j rocks!"));
+ assertEquals(3, results.size());
+ assertThat("Incorrect message 1", results.get(0),
+ equalTo(" DEBUG org.apache.logging.log4j.LoggerTest.builder(LoggerTest.java:62) 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("at org.apache.logging.log4j.LoggerTest.builder(LoggerTest.java:64)"));
+ }
+
+ @Test
+ public void basicFlow() {
+ logger.traceEntry();
+ logger.traceExit();
+ 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<Message>() {
+ @Override
+ public Message get() {
+ return new ObjectMessage(1);
+ }
+ }, new Supplier<Message>() {
+ @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<String>() {
+ @Override
+ public String get() {
+ return "1";
+ }
+ }, new Supplier<String>() {
+ @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) {
+ 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();
+ 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/TestLogger.java b/log4j-api/src/test/java/org/apache/logging/log4j/TestLogger.java
index 5f7abb7..b7a820c 100644
--- a/log4j-api/src/test/java/org/apache/logging/log4j/TestLogger.java
+++ b/log4j-api/src/test/java/org/apache/logging/log4j/TestLogger.java
@@ -53,6 +53,12 @@
@Override
public void logMessage(final String fqcn, final Level level, final Marker marker, final Message msg, final Throwable throwable) {
+ log(level, marker, fqcn, null, msg, throwable);
+ }
+
+ @Override
+ protected void log(final Level level, final Marker marker, final String fqcn, final StackTraceElement location,
+ final Message message, final Throwable throwable) {
final StringBuilder sb = new StringBuilder();
if (marker != null) {
sb.append(marker);
@@ -60,14 +66,18 @@
sb.append(' ');
sb.append(level.toString());
sb.append(' ');
- sb.append(msg.getFormattedMessage());
+ if (location != null) {
+ sb.append(location.toString());
+ sb.append(' ');
+ }
+ sb.append(message.getFormattedMessage());
final Map<String, String> mdc = ThreadContext.getImmutableContext();
if (mdc.size() > 0) {
sb.append(' ');
sb.append(mdc.toString());
sb.append(' ');
}
- final Object[] params = msg.getParameters();
+ final Object[] params = message.getParameters();
Throwable t;
if (throwable == null && params != null && params.length > 0 && params[params.length - 1] instanceof Throwable) {
t = (Throwable) params[params.length - 1];
@@ -81,7 +91,6 @@
sb.append(baos.toString());
}
list.add(sb.toString());
- //System.out.println(sb.toString());
}
@Override
diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/ThreadContextTest.java b/log4j-api/src/test/java/org/apache/logging/log4j/ThreadContextTest.java
index baec493..63f21df 100644
--- a/log4j-api/src/test/java/org/apache/logging/log4j/ThreadContextTest.java
+++ b/log4j-api/src/test/java/org/apache/logging/log4j/ThreadContextTest.java
@@ -108,6 +108,18 @@
}
@Test
+ public void testPutIfNotNull() {
+ ThreadContext.clearMap();
+ assertNull(ThreadContext.get("testKey"));
+ ThreadContext.put("testKey", "testValue");
+ assertEquals("testValue", ThreadContext.get("testKey"));
+ assertEquals("Incorrect value in test key", "testValue", ThreadContext.get("testKey"));
+ ThreadContext.putIfNull("testKey", "new Value");
+ assertEquals("Incorrect value in test key", "testValue", ThreadContext.get("testKey"));
+ ThreadContext.clearMap();
+ }
+
+ @Test
public void testPutAll() {
assertTrue(ThreadContext.isEmpty());
assertFalse(ThreadContext.containsKey("key"));
diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/ThreadContextUtilityClass.java b/log4j-api/src/test/java/org/apache/logging/log4j/ThreadContextUtilityClass.java
index c6e1826..1ec07b8 100644
--- a/log4j-api/src/test/java/org/apache/logging/log4j/ThreadContextUtilityClass.java
+++ b/log4j-api/src/test/java/org/apache/logging/log4j/ThreadContextUtilityClass.java
@@ -18,7 +18,7 @@
import java.util.Map;
-import org.apache.logging.log4j.Timer;
+import org.apache.logging.log4j.util.Timer;
import org.apache.logging.log4j.ThreadContext;
import static org.junit.Assert.*;
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 5dbe837..0000000
--- 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/junit/AbstractExternalFileCleaner.java b/log4j-api/src/test/java/org/apache/logging/log4j/junit/AbstractExternalFileCleaner.java
new file mode 100644
index 0000000..f5d4d62
--- /dev/null
+++ b/log4j-api/src/test/java/org/apache/logging/log4j/junit/AbstractExternalFileCleaner.java
@@ -0,0 +1,185 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.logging.log4j.junit;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.junit.Assert;
+import org.junit.rules.ExternalResource;
+
+/**
+ * This class should not perform logging using Log4j to avoid accidentally
+ * loading or re-loading Log4j configurations.
+ */
+public abstract class AbstractExternalFileCleaner extends ExternalResource {
+
+ protected static final String CLEANER_MARKER = "CLEANER";
+
+ private static final int SLEEP_RETRY_MILLIS = 200;
+ private final boolean cleanAfter;
+ private final boolean cleanBefore;
+ private final Set<Path> 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<Path, IOException> 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<Path, IOException> 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<Path> getPaths() {
+ return files;
+ }
+
+ public PrintStream getPrintStream() {
+ return printStream;
+ }
+
+ protected void printf(final String format, final Object... args) {
+ if (printStream != null) {
+ printStream.printf(format, args);
+ }
+ }
+
+ protected void println(final String msg) {
+ if (printStream != null) {
+ printStream.println(msg);
+ }
+ }
+
+ protected void printStackTrace(final Throwable t) {
+ if (printStream != null) {
+ t.printStackTrace(printStream);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getSimpleName() + " [files=" + files + ", cleanAfter=" + cleanAfter + ", cleanBefore="
+ + cleanBefore + "]";
+ }
+
+}
diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/junit/ClassMatchers.java b/log4j-api/src/test/java/org/apache/logging/log4j/junit/ClassMatchers.java
new file mode 100644
index 0000000..74f6f7e
--- /dev/null
+++ b/log4j-api/src/test/java/org/apache/logging/log4j/junit/ClassMatchers.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.junit;
+
+import org.apache.logging.log4j.util.LoaderUtil;
+import org.hamcrest.CustomTypeSafeMatcher;
+import org.hamcrest.Matcher;
+
+public final class ClassMatchers {
+
+ private static final Matcher<String> LOADABLE_CLASS_NAME = new CustomTypeSafeMatcher<String>("a loadable class name") {
+ @Override
+ protected boolean matchesSafely(final String item) {
+ return LoaderUtil.isClassAvailable(item);
+ }
+ };
+
+ public static Matcher<String> loadableClassName() {
+ return LOADABLE_CLASS_NAME;
+ }
+
+}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/junit/CleanFiles.java b/log4j-api/src/test/java/org/apache/logging/log4j/junit/CleanFiles.java
similarity index 100%
rename from log4j-core/src/test/java/org/apache/logging/log4j/junit/CleanFiles.java
rename to log4j-api/src/test/java/org/apache/logging/log4j/junit/CleanFiles.java
diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/junit/CleanFolders.java b/log4j-api/src/test/java/org/apache/logging/log4j/junit/CleanFolders.java
new file mode 100644
index 0000000..19fe195
--- /dev/null
+++ b/log4j-api/src/test/java/org/apache/logging/log4j/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.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.
+ * <p>
+ * This class should not perform logging using Log4j to avoid accidentally
+ * loading or re-loading Log4j configurations.
+ * </p>
+ */
+public class CleanFolders extends AbstractExternalFileCleaner {
+
+ public static final class DeleteAllFileVisitor extends SimpleFileVisitor<Path> {
+
+ private final PrintStream printStream;
+
+ public DeleteAllFileVisitor(final PrintStream logger) {
+ this.printStream = logger;
+ }
+
+ @Override
+ public FileVisitResult postVisitDirectory(final Path dir, final IOException exc) throws IOException {
+ printf("%s Deleting directory %s\n", CLEANER_MARKER, dir);
+ final boolean deleted = Files.deleteIfExists(dir);
+ printf("%s Deleted directory %s: %s\n", CLEANER_MARKER, dir, deleted);
+ return FileVisitResult.CONTINUE;
+ }
+
+ protected void printf(final String format, final Object... args) {
+ if (printStream != null) {
+ printStream.printf(format, args);
+ }
+ }
+
+ @Override
+ public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) throws IOException {
+ printf("%s Deleting file %s with %s\n", CLEANER_MARKER, file, attrs);
+ final boolean deleted = Files.deleteIfExists(file);
+ printf(CLEANER_MARKER, "%s Deleted file %s: %s\n", file, deleted);
+ return FileVisitResult.CONTINUE;
+ }
+ }
+
+ private static final int MAX_TRIES = 10;
+
+ public CleanFolders(final boolean before, final boolean after, final int maxTries, final File... files) {
+ super(before, after, maxTries, null, files);
+ }
+
+ public CleanFolders(final boolean before, final boolean after, final int maxTries, final Path... paths) {
+ super(before, after, maxTries, null, paths);
+ }
+
+ public CleanFolders(final boolean before, final boolean after, final int maxTries, final String... fileNames) {
+ super(before, after, maxTries, null, fileNames);
+ }
+
+ public CleanFolders(final File... folders) {
+ super(true, true, MAX_TRIES, null, folders);
+ }
+
+ public CleanFolders(final Path... paths) {
+ super(true, true, MAX_TRIES, null, paths);
+ }
+
+ public CleanFolders(final PrintStream logger, final File... folders) {
+ super(true, true, MAX_TRIES, logger, folders);
+ }
+
+ public CleanFolders(final String... folderNames) {
+ super(true, true, MAX_TRIES, null, folderNames);
+ }
+
+ @Override
+ protected boolean clean(final Path path, final int tryIndex) throws IOException {
+ cleanFolder(path, tryIndex);
+ return true;
+ }
+
+ private void cleanFolder(final Path folder, final int tryIndex) throws IOException {
+ if (Files.exists(folder) && Files.isDirectory(folder)) {
+ Files.walkFileTree(folder, new DeleteAllFileVisitor(getPrintStream()));
+ }
+ }
+}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/junit/URLStreamHandlerFactoryRule.java b/log4j-api/src/test/java/org/apache/logging/log4j/junit/URLStreamHandlerFactoryRule.java
similarity index 100%
rename from log4j-core/src/test/java/org/apache/logging/log4j/junit/URLStreamHandlerFactoryRule.java
rename to log4j-api/src/test/java/org/apache/logging/log4j/junit/URLStreamHandlerFactoryRule.java
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
index 44a15aa..c70afa0 100644
--- 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
@@ -16,10 +16,18 @@
*/
package org.apache.logging.log4j.message;
+import com.google.common.base.Strings;
import org.apache.logging.log4j.util.StringBuilderFormattable;
import org.junit.Test;
-import static org.junit.Assert.*;
+import java.math.BigDecimal;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
/**
*
@@ -79,7 +87,7 @@
msg.put("message", testMsg);
msg.put("project", "Log4j");
final String result = msg.getFormattedMessage(new String[]{"JSON"});
- final String expected = "{\"message\":\"Test message {}\", \"project\":\"Log4j\"}";
+ final String expected = "{'message':'Test message {}','project':'Log4j'}".replace('\'', '"');
assertEquals(expected, result);
}
@@ -105,6 +113,87 @@
}
@Test
+ public void testJsonFormatterNestedObjectSupport() {
+ final String actualJson = new ObjectMapMessage()
+ .with("key1", "val1")
+ .with("key2", Collections.singletonMap("key2.1", "val2.1"))
+ .with("key3", Arrays.asList(
+ 3,
+ (byte) 127,
+ 4.5D,
+ 4.6F,
+ Arrays.asList(true, false),
+ new BigDecimal(30),
+ Collections.singletonMap("key3.3", "val3.3")))
+ .with("key4", new LinkedHashMap<String, Object>() {{
+ put("chars", new char[]{'a', 'b', 'c'});
+ put("booleans", new boolean[]{true, false});
+ put("bytes", new byte[]{1, 2});
+ put("shorts", new short[]{3, 4});
+ put("ints", new int[]{256, 257});
+ put("longs", new long[]{2147483648L, 2147483649L});
+ put("floats", new float[]{1.0F, 1.1F});
+ put("doubles", new double[]{2.0D, 2.1D});
+ put("objects", new Object[]{"foo", "bar"});
+ }})
+ .getFormattedMessage(new String[]{"JSON"});
+ final String expectedJson = ("{" +
+ "'key1':'val1'," +
+ "'key2':{'key2.1':'val2.1'}," +
+ "'key3':[3,127,4.5,4.6,[true,false],30,{'key3.3':'val3.3'}]," +
+ "'key4':{" +
+ "'chars':['a','b','c']," +
+ "'booleans':[true,false]," +
+ "'bytes':[1,2]," +
+ "'shorts':[3,4]," +
+ "'ints':[256,257]," +
+ "'longs':[2147483648,2147483649]," +
+ "'floats':[1.0,1.1]," +
+ "'doubles':[2.0,2.1]," +
+ "'objects':['foo','bar']" +
+ "}}").replace('\'', '"');
+ assertEquals(expectedJson, actualJson);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testJsonFormatterInfiniteRecursionPrevention() {
+ final List<Object> recursiveValue = Arrays.asList(1, null);
+ // noinspection CollectionAddedToSelf
+ recursiveValue.set(1, recursiveValue);
+ new ObjectMapMessage()
+ .with("key", recursiveValue)
+ .getFormattedMessage(new String[]{"JSON"});
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testJsonFormatterMaxDepthViolation() {
+ testJsonFormatterMaxDepth(MapMessageJsonFormatter.MAX_DEPTH - 1);
+ }
+
+ @Test
+ public void testJsonFormatterMaxDepthConformance() {
+ int depth = MapMessageJsonFormatter.MAX_DEPTH - 2;
+ String expectedJson = String
+ .format("{'key':%s1%s}",
+ Strings.repeat("[", depth),
+ Strings.repeat("]", depth))
+ .replace('\'', '"');
+ String actualJson = testJsonFormatterMaxDepth(depth);
+ assertEquals(expectedJson, actualJson);
+ }
+
+ public static String testJsonFormatterMaxDepth(int depth) {
+ List<Object> list = new LinkedList<>();
+ list.add(1);
+ while (--depth > 0) {
+ list = new LinkedList<>(Collections.singletonList(list));
+ }
+ return new ObjectMapMessage()
+ .with("key", list)
+ .getFormattedMessage(new String[]{"JSON"});
+ }
+
+ @Test
public void testJava() {
final String testMsg = "Test message {}";
final StringMapMessage msg = new StringMapMessage();
@@ -151,10 +240,9 @@
@Test
public void testJSONFormatNonStringValue() {
- final ObjectMapMessage msg = new ObjectMapMessage()
- .with("key", 1L);
+ final ObjectMapMessage msg = new ObjectMapMessage().with("key", 1L);
final String result = msg.getFormattedMessage(new String[]{"JSON"});
- final String expected = "{\"key\":\"1\"}";
+ final String expected = "{'key':1}".replace('\'', '"');
assertEquals(expected, result);
}
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
index a151eac..7ebd325 100644
--- 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
@@ -16,7 +16,7 @@
*/
package org.apache.logging.log4j.message;
-import org.apache.logging.log4j.Timer;
+import org.apache.logging.log4j.util.Timer;
import org.junit.AfterClass;
import org.junit.Test;
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
index 6e728f6..8c509b4 100644
--- 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
@@ -188,4 +188,4 @@
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/ReusableMessageFactoryTest.java b/log4j-api/src/test/java/org/apache/logging/log4j/message/ReusableMessageFactoryTest.java
index 7942e5f..63135dc 100644
--- 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
@@ -127,4 +127,4 @@
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
index 614d54c..955c786 100644
--- 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
@@ -119,4 +119,4 @@
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/ReusableSimpleMessageTest.java b/log4j-api/src/test/java/org/apache/logging/log4j/message/ReusableSimpleMessageTest.java
index 1070a53..6a095fd 100644
--- 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
@@ -119,4 +119,4 @@
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/spi/LoggerAdapterTest.java b/log4j-api/src/test/java/org/apache/logging/log4j/spi/LoggerAdapterTest.java
index 8a56b40..c65d748 100644
--- 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
@@ -16,12 +16,18 @@
*/
package org.apache.logging.log4j.spi;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.TestLogger;
+import org.apache.logging.log4j.TestLoggerContext;
+import org.apache.logging.log4j.TestLoggerContextFactory;
import org.apache.logging.log4j.simple.SimpleLoggerContext;
import org.junit.Test;
+import java.util.HashSet;
import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
-import java.util.logging.Logger;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertSame;
@@ -68,12 +74,6 @@
}
- private static class TestLogger extends Logger {
- public TestLogger() {
- super("test", null);
- }
- }
-
private static class TestLoggerAdapter extends AbstractLoggerAdapter<Logger> {
@Override
@@ -87,6 +87,67 @@
}
}
+ private static class TestLoggerAdapter2 extends AbstractLoggerAdapter<Logger> {
+
+ @Override
+ protected Logger newLogger(String name, LoggerContext context) {
+ return context.getLogger(name);
+ }
+
+ @Override
+ protected LoggerContext getContext() {
+ return null;
+ }
+
+ public LoggerContext getContext(String fqcn) {
+ for (LoggerContext lc : registry.keySet()) {
+ TestLoggerContext2 context = (TestLoggerContext2) lc;
+ if (fqcn.equals(context.getName())) {
+ return context;
+ }
+ }
+ LoggerContext lc = new TestLoggerContext2(fqcn, this);
+ registry.put(lc, new ConcurrentHashMap<>());
+ return lc;
+ }
+ }
+
+ private static class TestLoggerContext2 extends TestLoggerContext {
+ private final String name;
+ private final LoggerContextShutdownAware listener;
+
+ public TestLoggerContext2(String name, LoggerContextShutdownAware listener) {
+ this.name = name;
+ this.listener = listener;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void shutdown() {
+ listener.contextShutdown(this);
+ }
+ }
+
+ @Test
+ public void testCleanup() throws Exception {
+ final LoggerContextFactory factory = new TestLoggerContextFactory();
+ final TestLoggerAdapter2 adapter = new TestLoggerAdapter2();
+ for (int i = 0; i < 5; ++i) {
+ LoggerContext lc = adapter.getContext(Integer.toString(i));
+ lc.getLogger(Integer.toString(i));
+ }
+ assertEquals("Expected 5 LoggerContexts", 5, adapter.registry.size());
+ Set<LoggerContext> contexts = new HashSet<>(adapter.registry.keySet());
+ for (LoggerContext context : contexts) {
+ ((TestLoggerContext2) context).shutdown();
+ }
+ assertEquals("Expected 0 LoggerContexts", 0, adapter.registry.size());
+ }
+
+
+
/**
* Testing synchronization in the getLoggersInContext() method
*/
@@ -123,4 +184,4 @@
assertEquals(2, resultMap1.size());
}
}
-}
\ No newline at end of file
+}
diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/util/AssertTest.java b/log4j-api/src/test/java/org/apache/logging/log4j/util/AssertTest.java
new file mode 100644
index 0000000..9a11647
--- /dev/null
+++ b/log4j-api/src/test/java/org/apache/logging/log4j/util/AssertTest.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.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+
+import org.apache.logging.log4j.util.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import static org.junit.Assert.*;
+
+/**
+ *
+ */
+@RunWith(Parameterized.class)
+public class AssertTest {
+
+ private final Object value;
+ private final boolean isEmpty;
+
+ @Parameterized.Parameters
+ public static Object[][] data() {
+ return new Object[][]{
+ // value, isEmpty
+ {null, true},
+ {"", true},
+ {new Object[0], true},
+ {new ArrayList<>(), true},
+ {new HashMap<>(), true},
+ {0, false},
+ {1, false},
+ {false, false},
+ {true, false},
+ {new Object[]{null}, false},
+ {Collections.singletonList(null), false},
+ {Collections.singletonMap("", null), false},
+ {"null", false}
+ };
+ }
+
+ public AssertTest(final Object value, final boolean isEmpty) {
+ this.value = value;
+ this.isEmpty = isEmpty;
+ }
+
+ @Test
+ public void isEmpty() throws Exception {
+ assertEquals(isEmpty, Assert.isEmpty(value));
+ }
+
+}
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
index d1447f2..e799825 100644
--- 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
@@ -43,4 +43,4 @@
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/ConstantsTest.java b/log4j-api/src/test/java/org/apache/logging/log4j/util/ConstantsTest.java
new file mode 100644
index 0000000..abc5fef
--- /dev/null
+++ b/log4j-api/src/test/java/org/apache/logging/log4j/util/ConstantsTest.java
@@ -0,0 +1,16 @@
+package org.apache.logging.log4j.util;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+public class ConstantsTest {
+
+ @Test
+ public void testJdkVersionDetection() {
+ assertEquals(1, Constants.getMajorVersion("1.1.2"));
+ assertEquals(8, Constants.getMajorVersion("1.8.2"));
+ assertEquals(9, Constants.getMajorVersion("9.1.1"));
+ assertEquals(11, Constants.getMajorVersion("11.1.1"));
+ }
+}
diff --git a/log4j-api/src/test/java/org/apache/logging/log4j/util/EnvironmentPropertySourceTest.java b/log4j-api/src/test/java/org/apache/logging/log4j/util/EnvironmentPropertySourceTest.java
index 3beea89..0a25a2a 100644
--- 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
@@ -55,4 +55,4 @@
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/LegacyPropertiesCompatibilityTest.java b/log4j-api/src/test/java/org/apache/logging/log4j/util/LegacyPropertiesCompatibilityTest.java
index b979d9a..6104071 100644
--- a/log4j-api/src/test/java/org/apache/logging/log4j/util/LegacyPropertiesCompatibilityTest.java
+++ b/log4j-api/src/test/java/org/apache/logging/log4j/util/LegacyPropertiesCompatibilityTest.java
@@ -39,7 +39,7 @@
public static Object[][] data() {
return new Object[][]{
{"log4j2.configurationFile", "log4j.configurationFile"},
- {"log4j2.mergeFactory", "log4j.mergeFactory"},
+ {"log4j2.mergeStrategy", "log4j.mergeStrategy"},
{"log4j2.contextSelector", "Log4jContextSelector"},
{"log4j2.logEventFactory", "Log4jLogEventFactory"},
{"log4j2.configurationFactory", "log4j.configurationFactory"},
@@ -100,4 +100,4 @@
final List<CharSequence> oldTokens = PropertySource.Util.tokenize(oldName);
assertEquals(oldTokens, newTokens);
}
-}
\ No newline at end of file
+}
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
index c1c97ef..34d2dd6 100644
--- 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
@@ -53,4 +53,4 @@
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
index 49b62de..3d0c923 100644
--- 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
@@ -108,4 +108,13 @@
System.getProperties().remove(key2);
}
}
+
+ @Test
+ public void testPublish() {
+ final Properties props = new Properties();
+ final PropertiesUtil util = new PropertiesUtil(props);
+ String value = System.getProperty("Application");
+ assertNotNull("System property was not published", value);
+ assertEquals("Log4j", value);
+ }
}
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
index a197085..8b86c1d 100644
--- 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
@@ -51,4 +51,4 @@
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
index dd1d49c..d53fd99 100644
--- 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
@@ -63,4 +63,4 @@
List<CharSequence> 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/SortedArrayStringMapTest.java b/log4j-api/src/test/java/org/apache/logging/log4j/util/SortedArrayStringMapTest.java
index ea333ae..3b91008 100644
--- 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
@@ -1116,4 +1116,4 @@
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
index abade29..c4becc7 100644
--- 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
@@ -16,24 +16,35 @@
*/
package org.apache.logging.log4j.util;
+import java.lang.invoke.MethodHandle;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.MethodType;
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.apache.logging.log4j.junit.ClassMatchers.*;
+import static org.hamcrest.CoreMatchers.is;
import static org.junit.Assert.*;
+import static org.junit.Assume.assumeThat;
@RunWith(BlockJUnit4ClassRunner.class)
public class StackLocatorUtilTest {
@Test
- public void testStackTraceEquivalence() throws Exception {
+ public void testStackTraceEquivalence() throws Throwable {
+ String reflectionClassName = "sun.reflect.Reflection";
+ assumeThat("Running in a JDK without deprecated " + reflectionClassName,
+ reflectionClassName, is(loadableClassName()));
+ Class<?> reflectionClass = LoaderUtil.loadClass(reflectionClassName);
+ MethodHandle getCallerClass = MethodHandles.lookup()
+ .findStatic(reflectionClass, "getCallerClass", MethodType.methodType(Class.class, int.class));
for (int i = 1; i < 15; i++) {
- final Class<?> expected = Reflection.getCallerClass(i + StackLocator.JDK_7u25_OFFSET);
+ final Class<?> expected = (Class<?>) getCallerClass.invoke(i + StackLocator.JDK_7u25_OFFSET);
final Class<?> actual = StackLocatorUtil.getCallerClass(i);
final Class<?> fallbackActual = Class.forName(
StackLocatorUtil.getStackTraceElement(i).getClassName());
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
index 6feecbb..903c97f 100644
--- 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
@@ -89,4 +89,4 @@
StringBuilders.escapeXml(sb, 0);
assertEquals(xmlValueEscaped, sb.toString());
}
-}
\ 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
index 162d162..e91e298 100644
--- 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
@@ -25,6 +25,25 @@
public class StringsTest {
+ @Test
+ public void testIsEmpty() {
+ Assert.assertTrue(Strings.isEmpty(null));
+ Assert.assertTrue(Strings.isEmpty(""));
+ Assert.assertFalse(Strings.isEmpty(" "));
+ Assert.assertFalse(Strings.isEmpty("a"));
+ }
+
+ @Test
+ public void testIsBlank() {
+ Assert.assertTrue(Strings.isBlank(null));
+ Assert.assertTrue(Strings.isBlank(""));
+ Assert.assertTrue(Strings.isBlank(" "));
+ Assert.assertTrue(Strings.isBlank("\n"));
+ Assert.assertTrue(Strings.isBlank("\r"));
+ Assert.assertTrue(Strings.isBlank("\t"));
+ Assert.assertFalse(Strings.isEmpty("a"));
+ }
+
/**
* A sanity test to make sure a typo does not mess up {@link Strings#EMPTY}.
*/
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
index 2e8cb77..d0330cf 100644
--- 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
@@ -167,4 +167,4 @@
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
index 2deda12..807e7c2 100644
--- 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
@@ -88,4 +88,4 @@
}
}
}
-}
\ No newline at end of file
+}
diff --git a/log4j-api/src/test/resources/log4j2.system.properties b/log4j-api/src/test/resources/log4j2.system.properties
new file mode 100644
index 0000000..6e4f8ae
--- /dev/null
+++ b/log4j-api/src/test/resources/log4j2.system.properties
@@ -0,0 +1 @@
+Application=Log4j
\ No newline at end of file
diff --git a/log4j-appserver/pom.xml b/log4j-appserver/pom.xml
index 9c2f20b..88ba72a 100644
--- a/log4j-appserver/pom.xml
+++ b/log4j-appserver/pom.xml
@@ -164,6 +164,7 @@
<additionalparam>${javadoc.opts}</additionalparam>
<detectOfflineLinks>false</detectOfflineLinks>
<linksource>true</linksource>
+ <source>8</source>
<links>
<link>http://docs.oracle.com/javaee/6/api/</link>
</links>
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 7bcce98..561cfaf 100644
--- a/log4j-appserver/src/main/java/org/apache/logging/log4j/appserver/jetty/Log4j2Logger.java
+++ b/log4j-appserver/src/main/java/org/apache/logging/log4j/appserver/jetty/Log4j2Logger.java
@@ -25,7 +25,7 @@
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.
*
* <p>
* To direct Jetty to use this class, set the system property {{org.eclipse.jetty.util.log.class}} to this class name.
diff --git a/log4j-bom/pom.xml b/log4j-bom/pom.xml
index 3a89ec0..0f21c78 100644
--- a/log4j-bom/pom.xml
+++ b/log4j-bom/pom.xml
@@ -36,6 +36,12 @@
<artifactId>log4j-api</artifactId>
<version>${project.version}</version>
</dependency>
+ <!-- Plugins -->
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-plugins</artifactId>
+ <version>${project.version}</version>
+ </dependency>
<!-- Core Log4j -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
@@ -66,6 +72,12 @@
<artifactId>log4j-layout-jackson-yaml</artifactId>
<version>${project.version}</version>
</dependency>
+ <!-- JSON template layout -->
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-layout-json-template</artifactId>
+ <version>${project.version}</version>
+ </dependency>
<!-- Legacy Log4j 1.2 API -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
@@ -126,6 +138,18 @@
<artifactId>log4j-slf4j-impl</artifactId>
<version>${project.version}</version>
</dependency>
+ <!-- SLF4J 1.8.x Compatibility API -->
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-slf4j18-impl</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <!-- SLF4J Adapter -->
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-to-slf4j</artifactId>
+ <version>${project.version}</version>
+ </dependency>
<!-- Web Application Support -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
@@ -186,7 +210,13 @@
<artifactId>log4j-jul</artifactId>
<version>${project.version}</version>
</dependency>
- <!-- java.util.logging adapter -->
+ <!-- Java System Platform Loggerr -->
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-jpl</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <!-- Liquibase adapter -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-liquibase</artifactId>
@@ -198,6 +228,30 @@
<artifactId>log4j-smtp</artifactId>
<version>${project.version}</version>
</dependency>
+ <!-- Application Service Support -->
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-appserver</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <!-- Docker support -->
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-docker</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <!-- Kubernetes support -->
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-kubernetes</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <!-- Spring Cloud Config Client -->
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-spring-cloud-config-client</artifactId>
+ <version>${project.version}</version>
+ </dependency>
</dependencies>
</dependencyManagement>
<build>
diff --git a/log4j-cassandra/pom.xml b/log4j-cassandra/pom.xml
index 3455022..d035c4b 100644
--- a/log4j-cassandra/pom.xml
+++ b/log4j-cassandra/pom.xml
@@ -152,6 +152,7 @@
project -->
<detectOfflineLinks>false</detectOfflineLinks>
<linksource>true</linksource>
+ <source>8</source>
</configuration>
<reportSets>
<reportSet>
diff --git a/log4j-cassandra/revapi.json b/log4j-cassandra/revapi.json
new file mode 100644
index 0000000..ca1fa97
--- /dev/null
+++ b/log4j-cassandra/revapi.json
@@ -0,0 +1,12 @@
+[
+ {
+ "extension": "revapi.ignore",
+ "configuration": [
+ {
+ "code": "java.method.removed",
+ "old": "method void org.apache.logging.log4j.cassandra.CassandraManager::writeInternal(org.apache.logging.log4j.core.LogEvent)",
+ "justification": "Method not needed"
+ }
+ ]
+ }
+]
diff --git a/log4j-cassandra/src/main/java/org/apache/logging/log4j/cassandra/CassandraAppender.java b/log4j-cassandra/src/main/java/org/apache/logging/log4j/cassandra/CassandraAppender.java
index 5f87d7e..c017d2e 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
@@ -22,13 +22,14 @@
import org.apache.logging.log4j.core.appender.AbstractAppender;
import org.apache.logging.log4j.core.appender.db.AbstractDatabaseAppender;
import org.apache.logging.log4j.core.appender.db.ColumnMapping;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
-import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
-import org.apache.logging.log4j.core.config.plugins.PluginElement;
-import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required;
+import org.apache.logging.log4j.core.config.Property;
import org.apache.logging.log4j.core.net.SocketAddress;
import org.apache.logging.log4j.core.time.Clock;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginBuilderAttribute;
+import org.apache.logging.log4j.plugins.PluginElement;
+import org.apache.logging.log4j.plugins.PluginFactory;
+import org.apache.logging.log4j.plugins.validation.constraints.Required;
/**
* Appender plugin that uses a Cassandra database.
@@ -40,17 +41,17 @@
public class CassandraAppender extends AbstractDatabaseAppender<CassandraManager> {
private CassandraAppender(final String name, final Filter filter, final boolean ignoreExceptions,
- final CassandraManager manager) {
- super(name, filter, ignoreExceptions, manager);
+ Property[] properties, final CassandraManager manager) {
+ super(name, filter, null, ignoreExceptions, properties, manager);
}
- @PluginBuilderFactory
+ @PluginFactory
public static <B extends Builder<B>> B newBuilder() {
return new Builder<B>().asBuilder();
}
public static class Builder<B extends Builder<B>> extends AbstractAppender.Builder<B>
- implements org.apache.logging.log4j.core.util.Builder<CassandraAppender> {
+ implements org.apache.logging.log4j.plugins.util.Builder<CassandraAppender> {
/**
* List of Cassandra node contact points. Addresses without a port (or port set to 0) will use the default
@@ -177,7 +178,7 @@
final CassandraManager manager = CassandraManager.getManager(getName(), contactPoints, columns, useTls,
clusterName, keyspace, table, username, password, useClockForTimestampGenerator, bufferSize, batched,
batchType);
- return new CassandraAppender(getName(), getFilter(), isIgnoreExceptions(), manager);
+ return new CassandraAppender(getName(), getFilter(), isIgnoreExceptions(), getPropertyArray(), manager);
}
}
diff --git a/log4j-cassandra/src/main/java/org/apache/logging/log4j/cassandra/CassandraManager.java b/log4j-cassandra/src/main/java/org/apache/logging/log4j/cassandra/CassandraManager.java
index 8b54a0b..7ae8393 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
@@ -31,7 +31,7 @@
import org.apache.logging.log4j.core.appender.ManagerFactory;
import org.apache.logging.log4j.core.appender.db.AbstractDatabaseManager;
import org.apache.logging.log4j.core.appender.db.ColumnMapping;
-import org.apache.logging.log4j.core.config.plugins.convert.TypeConverters;
+import org.apache.logging.log4j.plugins.convert.TypeConverters;
import org.apache.logging.log4j.core.net.SocketAddress;
import org.apache.logging.log4j.jdbc.convert.DateTypeConverter;
import org.apache.logging.log4j.spi.ThreadContextMap;
@@ -87,12 +87,6 @@
// 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++) {
diff --git a/log4j-cassandra/src/main/java/org/apache/logging/log4j/cassandra/package-info.java b/log4j-cassandra/src/main/java/org/apache/logging/log4j/cassandra/package-info.java
index 0336dd0..3121a56 100644
--- a/log4j-cassandra/src/main/java/org/apache/logging/log4j/cassandra/package-info.java
+++ b/log4j-cassandra/src/main/java/org/apache/logging/log4j/cassandra/package-info.java
@@ -20,4 +20,4 @@
* @see <a href="https://logging.apache.org/log4j/2.x/manual/appenders.html#CassandraAppender">Cassandra Appender manual</a>
* @since 2.8
*/
-package org.apache.logging.log4j.cassandra;
\ No newline at end of file
+package org.apache.logging.log4j.cassandra;
diff --git a/log4j-core-its/pom.xml b/log4j-core-its/pom.xml
index fc25818..505d667 100644
--- a/log4j-core-its/pom.xml
+++ b/log4j-core-its/pom.xml
@@ -31,6 +31,7 @@
<log4jParentDir>${basedir}/..</log4jParentDir>
<docLabel>Core Integration Tests Documentation</docLabel>
<projectDir>/log4j-core-its</projectDir>
+ <revapi.skip>true</revapi.skip>
</properties>
<dependencies>
<dependency>
diff --git a/log4j-core-its/src/main/resources/placeholder.txt b/log4j-core-its/src/main/resources/placeholder.txt
new file mode 100644
index 0000000..44d4eb8
--- /dev/null
+++ b/log4j-core-its/src/main/resources/placeholder.txt
@@ -0,0 +1 @@
+Revapi requires something in the target/classes directory.
\ No newline at end of file
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 1de0e97..541394e 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
@@ -211,4 +211,4 @@
}
}
-}
\ 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 a2c1007..2af0421 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
@@ -202,4 +202,4 @@
return s.getBytes();
}
-}
\ No newline at end of file
+}
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 97ed395..8498fbc 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
@@ -22,7 +22,7 @@
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Timer;
+import org.apache.logging.log4j.util.Timer;
import org.apache.logging.log4j.categories.PerformanceTests;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.DefaultConfiguration;
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 5798766..42bd307 100644
--- a/log4j-core-its/src/test/java/org/apache/logging/log4j/core/ThreadedPerfTest.java
+++ b/log4j-core-its/src/test/java/org/apache/logging/log4j/core/ThreadedPerfTest.java
@@ -21,8 +21,8 @@
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Timer;
import org.apache.logging.log4j.categories.PerformanceTests;
+import org.apache.logging.log4j.util.Timer;
import org.junit.Test;
import org.junit.experimental.categories.Category;
diff --git a/log4j-core-its/src/test/java/org/apache/logging/log4j/core/appender/HttpAppenderTest.java b/log4j-core-its/src/test/java/org/apache/logging/log4j/core/appender/HttpAppenderTest.java
index 4e9a07c..46cd3d8 100644
--- a/log4j-core-its/src/test/java/org/apache/logging/log4j/core/appender/HttpAppenderTest.java
+++ b/log4j-core-its/src/test/java/org/apache/logging/log4j/core/appender/HttpAppenderTest.java
@@ -163,8 +163,8 @@
.willReturn(SUCCESS_RESPONSE));
final Appender appender = HttpAppender.newBuilder()
- .withName("Http")
- .withLayout(JsonLayout.createDefaultLayout())
+ .setName("Http")
+ .setLayout(JsonLayout.createDefaultLayout())
.setConfiguration(ctx.getConfiguration())
.setUrl(new URL("http://localhost:" + wireMockRule.port() + "/test/log4j/"))
.build();
@@ -182,13 +182,13 @@
.willReturn(SUCCESS_RESPONSE));
final Appender appender = HttpAppender.newBuilder()
- .withName("Http")
- .withLayout(JsonLayout.createDefaultLayout())
+ .setName("Http")
+ .setLayout(JsonLayout.createDefaultLayout())
.setConfiguration(ctx.getConfiguration())
.setUrl(new URL("https://localhost:" + wireMockRule.httpsPort() + "/test/log4j/"))
.setSslConfiguration(SslConfiguration.createSSLConfiguration(null,
- KeyStoreConfiguration.createKeyStoreConfiguration(TestConstants.KEYSTORE_FILE, TestConstants.KEYSTORE_PWD(), TestConstants.KEYSTORE_TYPE, null),
- TrustStoreConfiguration.createKeyStoreConfiguration(TestConstants.TRUSTSTORE_FILE, TestConstants.TRUSTSTORE_PWD(), TestConstants.TRUSTSTORE_TYPE, null)))
+ KeyStoreConfiguration.createKeyStoreConfiguration(TestConstants.KEYSTORE_FILE, TestConstants.KEYSTORE_PWD(), null, null, TestConstants.KEYSTORE_TYPE, null),
+ TrustStoreConfiguration.createKeyStoreConfiguration(TestConstants.TRUSTSTORE_FILE, TestConstants.TRUSTSTORE_PWD(), null ,null, TestConstants.TRUSTSTORE_TYPE, null)))
.setVerifyHostname(false)
.build();
appender.append(createLogEvent());
@@ -205,8 +205,8 @@
.willReturn(SUCCESS_RESPONSE));
final Appender appender = HttpAppender.newBuilder()
- .withName("Http")
- .withLayout(JsonLayout.createDefaultLayout())
+ .setName("Http")
+ .setLayout(JsonLayout.createDefaultLayout())
.setConfiguration(ctx.getConfiguration())
.setMethod("PUT")
.setUrl(new URL("http://localhost:" + wireMockRule.port() + "/test/log4j/1234"))
@@ -225,8 +225,8 @@
.willReturn(SUCCESS_RESPONSE));
final Appender appender = HttpAppender.newBuilder()
- .withName("Http")
- .withLayout(JsonLayout.createDefaultLayout())
+ .setName("Http")
+ .setLayout(JsonLayout.createDefaultLayout())
.setConfiguration(ctx.getConfiguration())
.setUrl(new URL("http://localhost:" + wireMockRule.port() + "/test/log4j/"))
.setHeaders(new Property[] {
@@ -269,8 +269,8 @@
error = null;
final Appender appender = HttpAppender.newBuilder()
- .withName("Http")
- .withLayout(JsonLayout.createDefaultLayout())
+ .setName("Http")
+ .setLayout(JsonLayout.createDefaultLayout())
.setConfiguration(ctx.getConfiguration())
.setUrl(new URL("http://localhost:" + wireMockRule.port() + "/test/log4j/"))
.build();
@@ -292,10 +292,10 @@
.willReturn(FAILURE_RESPONSE));
final Appender appender = HttpAppender.newBuilder()
- .withName("Http")
- .withLayout(JsonLayout.createDefaultLayout())
+ .setName("Http")
+ .setLayout(JsonLayout.createDefaultLayout())
.setConfiguration(ctx.getConfiguration())
- .withIgnoreExceptions(false)
+ .setIgnoreExceptions(false)
.setUrl(new URL("http://localhost:" + wireMockRule.port() + "/test/log4j/"))
.build();
appender.append(createLogEvent());
@@ -304,13 +304,13 @@
@Test(expected = AppenderLoggingException.class)
public void testAppendConnectError() throws Exception {
final Appender appender = HttpAppender.newBuilder()
- .withName("Http")
- .withLayout(JsonLayout.createDefaultLayout())
+ .setName("Http")
+ .setLayout(JsonLayout.createDefaultLayout())
.setConfiguration(ctx.getConfiguration())
- .withIgnoreExceptions(false)
+ .setIgnoreExceptions(false)
.setUrl(new URL("http://localhost:"+(wireMockRule.port()+1)+"/test/log4j/"))
.build();
appender.append(createLogEvent());
}
-}
\ No newline at end of file
+}
diff --git a/log4j-core-its/src/test/java/org/apache/logging/log4j/core/appender/SocketAppenderTest.java b/log4j-core-its/src/test/java/org/apache/logging/log4j/core/appender/SocketAppenderTest.java
index e02590c..807e8ac 100644
--- a/log4j-core-its/src/test/java/org/apache/logging/log4j/core/appender/SocketAppenderTest.java
+++ b/log4j-core-its/src/test/java/org/apache/logging/log4j/core/appender/SocketAppenderTest.java
@@ -127,13 +127,13 @@
throws Exception {
// @formatter:off
final SocketAppender appender = SocketAppender.newBuilder()
- .withHost("localhost")
- .withPort(tcpTestServer.getLocalPort())
- .withReconnectDelayMillis(-1)
- .withName("test")
- .withImmediateFail(false)
- .withBufferSize(bufferSize)
- .withLayout(JsonLayout.newBuilder().setProperties(true).build())
+ .setHost("localhost")
+ .setPort(tcpTestServer.getLocalPort())
+ .setReconnectDelayMillis(-1)
+ .setName("test")
+ .setImmediateFail(false)
+ .setBufferSize(bufferSize)
+ .setLayout(JsonLayout.newBuilder().setProperties(true).build())
.build();
// @formatter:on
appender.start();
@@ -176,11 +176,11 @@
public void testDefaultProtocol() throws Exception {
// @formatter:off
final SocketAppender appender = SocketAppender.newBuilder()
- .withPort(tcpServer.getLocalPort())
- .withReconnectDelayMillis(-1)
- .withName("test")
- .withImmediateFail(false)
- .withLayout(JsonLayout.newBuilder().setProperties(true).build())
+ .setPort(tcpServer.getLocalPort())
+ .setReconnectDelayMillis(-1)
+ .setName("test")
+ .setImmediateFail(false)
+ .setLayout(JsonLayout.newBuilder().setProperties(true).build())
.build();
// @formatter:on
assertNotNull(appender);
@@ -197,12 +197,12 @@
// @formatter:off
final SocketAppender appender = SocketAppender.newBuilder()
- .withProtocol(Protocol.UDP)
- .withPort(tcpServer.getLocalPort())
- .withReconnectDelayMillis(-1)
- .withName("test")
- .withImmediateFail(false)
- .withLayout(JsonLayout.newBuilder().setProperties(true).build())
+ .setProtocol(Protocol.UDP)
+ .setPort(tcpServer.getLocalPort())
+ .setReconnectDelayMillis(-1)
+ .setName("test")
+ .setImmediateFail(false)
+ .setLayout(JsonLayout.newBuilder().setProperties(true).build())
.build();
// @formatter:on
appender.start();
@@ -223,12 +223,12 @@
// @formatter:off
final SocketAppender appender = SocketAppender.newBuilder()
- .withHost("localhost")
- .withPort(DYN_PORT)
- .withReconnectDelayMillis(100)
- .withName("test")
- .withImmediateFail(false)
- .withLayout(JsonLayout.newBuilder().setProperties(true).build())
+ .setHost("localhost")
+ .setPort(DYN_PORT)
+ .setReconnectDelayMillis(100)
+ .setName("test")
+ .setImmediateFail(false)
+ .setLayout(JsonLayout.newBuilder().setProperties(true).build())
.build();
// @formatter:on
appender.start();
@@ -254,13 +254,13 @@
public void testTcpAppenderNoWait() throws Exception {
// @formatter:off
final SocketAppender appender = SocketAppender.newBuilder()
- .withHost("localhost")
- .withPort(ERROR_PORT)
- .withReconnectDelayMillis(100)
- .withName("test")
- .withImmediateFail(false)
- .withIgnoreExceptions(false)
- .withLayout(JsonLayout.newBuilder().setProperties(true).build())
+ .setHost("localhost")
+ .setPort(ERROR_PORT)
+ .setReconnectDelayMillis(100)
+ .setName("test")
+ .setImmediateFail(false)
+ .setIgnoreExceptions(false)
+ .setLayout(JsonLayout.newBuilder().setProperties(true).build())
.build();
// @formatter:on
appender.start();
diff --git a/log4j-core-java9/pom.xml b/log4j-core-java9/pom.xml
index fa7d7b7..22538c0 100644
--- a/log4j-core-java9/pom.xml
+++ b/log4j-core-java9/pom.xml
@@ -65,7 +65,7 @@
<configuration>
<toolchains>
<jdk>
- <version>9</version>
+ <version>[11, )</version>
</jdk>
</toolchains>
</configuration>
diff --git a/log4j-core/pom.xml b/log4j-core/pom.xml
index cd96e1d..aa0dff0 100644
--- a/log4j-core/pom.xml
+++ b/log4j-core/pom.xml
@@ -31,6 +31,7 @@
<log4jParentDir>${basedir}/..</log4jParentDir>
<docLabel>Core Documentation</docLabel>
<projectDir>/core</projectDir>
+ <log4j2-logstash-layout.version>0.18</log4j2-logstash-layout.version>
</properties>
<dependencies>
<!-- Naturally, all implementations require the log4j-api JAR -->
@@ -38,6 +39,10 @@
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
</dependency>
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-plugins</artifactId>
+ </dependency>
<!-- Classes and resources to be shaded into the core jar -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
@@ -122,6 +127,13 @@
<type>test-jar</type>
<scope>test</scope>
</dependency>
+ <!-- And test plugins -->
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-plugins</artifactId>
+ <type>test-jar</type>
+ <scope>test</scope>
+ </dependency>
<!-- Apache Commons Compress -->
<dependency>
<groupId>org.tukaani</groupId>
@@ -257,6 +269,12 @@
<artifactId>commons-io</artifactId>
<scope>test</scope>
</dependency>
+ <!-- Used for testing HTTP Watcher -->
+ <dependency>
+ <groupId>com.github.tomakehurst</groupId>
+ <artifactId>wiremock</artifactId>
+ <scope>test</scope>
+ </dependency>
<!-- Other -->
<dependency>
<groupId>commons-codec</groupId>
@@ -294,6 +312,25 @@
<artifactId>HdrHistogram</artifactId>
<scope>test</scope>
</dependency>
+ <!-- For testing log4j 2 2.x plugins -->
+ <dependency>
+ <groupId>com.vlkan.log4j2</groupId>
+ <artifactId>log4j2-logstash-layout</artifactId>
+ <version>${log4j2-logstash-layout.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.github.ivandzf</groupId>
+ <artifactId>log4j2-custom-layout</artifactId>
+ <version>1.1.0</version>
+ <scope>test</scope>
+ <exclusions>
+ <exclusion>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-core</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
</dependencies>
<build>
<plugins>
@@ -347,35 +384,6 @@
</executions>
</plugin>
<plugin>
- <artifactId>maven-compiler-plugin</artifactId>
- <executions>
- <execution>
- <!-- disable annotation processing for first pass -->
- <id>default-compile</id>
- <configuration>
- <excludes>
- <exclude>module-info.java</exclude>
- </excludes>
- <proc>none</proc>
- </configuration>
- </execution>
- <execution>
- <!-- then do a processing-only pass to generate plugins .dat file -->
- <id>process-plugins</id>
- <goals>
- <goal>compile</goal>
- </goals>
- <phase>process-classes</phase>
- <configuration>
- <excludes>
- <exclude>module-info.java</exclude>
- </excludes>
- <proc>only</proc>
- </configuration>
- </execution>
- </executions>
- </plugin>
- <plugin>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<excludedGroups>
@@ -456,6 +464,12 @@
<Import-Package>
sun.reflect;resolution:=optional,
org.apache.logging.log4j.util,
+ org.apache.logging.log4j.plugins,
+ org.apache.logging.log4j.plugins.convert,
+ org.apache.logging.log4j.plugins.processor,
+ org.apache.logging.log4j.plugins.util,
+ org.apache.logging.log4j.plugins.validation,
+ org.apache.logging.log4j.plugins.inject,
*
</Import-Package>
<Bundle-Activator>org.apache.logging.log4j.core.osgi.Activator</Bundle-Activator>
@@ -501,6 +515,7 @@
<version>${javadoc.plugin.version}</version>
<configuration>
<failOnError>false</failOnError>
+ <source>8</source>
<bottom><![CDATA[<p align="center">Copyright © {inceptionYear}-{currentYear} {organizationName}. All Rights Reserved.<br />
Apache Logging, Apache Log4j, Log4j, Apache, the Apache feather logo, the Apache Logging project logo,
and the Apache Log4j logo are trademarks of The Apache Software Foundation.</p>]]></bottom>
diff --git a/log4j-core/revapi.json b/log4j-core/revapi.json
new file mode 100644
index 0000000..1ee8659
--- /dev/null
+++ b/log4j-core/revapi.json
@@ -0,0 +1,36 @@
+[
+ {
+ "extension": "revapi.java",
+ "configuration": {
+ "filter": {
+ "classes": {
+ "exclude": [
+ "org\\.apache\\.logging\\.log4j\\.corel\\.impl\\.ContextAnchor",
+ "org\\.apache\\.logging\\.log4j\\.core\\.async\\.AsyncLoggerDisruptor",
+ "org\\.apache\\.logging\\.log4j\\.core\\.async\\.RingBufferLogEvent\\.Factory",
+ "org\\.apache\\.logging\\.log4j\\.core\\.layout\\.AbstractJacksonLayout",
+ "org\\.apache\\.logging\\.log4j\\.core\\.layout\\.AbstractJacksonLayout\\.ResolvableKeyValuePair",
+ "org\\.apache\\.logging\\.log4j\\.core\\.net\\.SmtpManager\\.FactoryData",
+ "org\\.apache\\.logging\\.log4j\\.core\\.net\\.TcpSocketManager\\.FactoryData",
+ "org\\.apache\\.logging\\.log4j\\.core\\.util\\.CronExpression\\.ValueSet",
+ "org\\.apache\\.logging\\.log4j\\.core\\.util\\.datetime\\.FastDatePrinter\\.NumberRule",
+ "org\\.apache\\.logging\\.log4j\\.core\\.util\\.datetime\\.FastDatePrinter\\.Rule"
+ ]
+ },
+ "packages": {
+ "exclude": [
+ "org.apache.logging.log4j.core.jmx",
+ "org.apache.logging.log4j.core.jackson",
+ "org.apache.logging.log4j.core.time.internal",
+ "org.apache.logging.log4j.core.util.datetime"
+ ]
+ }
+ }
+ }
+ },
+ {
+ "extension": "revapi.ignore",
+ "configuration": [
+ ]
+ }
+]
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/AbstractLifeCycle.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/AbstractLifeCycle.java
index cf2961d..43c3c02 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/AbstractLifeCycle.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/AbstractLifeCycle.java
@@ -27,7 +27,7 @@
* Wraps a {@link LifeCycle.State}.
* </p>
*/
-public class AbstractLifeCycle implements LifeCycle2 {
+public class AbstractLifeCycle implements LifeCycle {
public static final int DEFAULT_STOP_TIMEOUT = 0;
public static final TimeUnit DEFAULT_STOP_TIMEUNIT = TimeUnit.MILLISECONDS;
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 f1e6aed..35cbc94 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/AbstractLogEvent.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/AbstractLogEvent.java
@@ -16,9 +16,6 @@
*/
package org.apache.logging.log4j.core;
-import java.util.Collections;
-import java.util.Map;
-
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.Marker;
import org.apache.logging.log4j.ThreadContext;
@@ -52,14 +49,6 @@
return null;
}
- /**
- * Returns {@link Collections#emptyMap()}.
- */
- @Override
- public Map<String, String> getContextMap() {
- return Collections.emptyMap();
- }
-
@Override
public ContextStack getContextStack() {
return ThreadContext.EMPTY_STACK;
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 91a0932..5519a56 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/Appender.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/Appender.java
@@ -23,8 +23,8 @@
* as an {@link ErrorHandler}. Typical Appender implementations coordinate with an
* implementation of {@link org.apache.logging.log4j.core.appender.AbstractManager} to handle external resources
* such as streams, connections, and other shared state. As Appenders are plugins, concrete implementations need to
- * be annotated with {@link org.apache.logging.log4j.core.config.plugins.Plugin} and need to provide a static
- * factory method annotated with {@link org.apache.logging.log4j.core.config.plugins.PluginFactory}.
+ * be annotated with {@link org.apache.logging.log4j.plugins.Plugin} and need to provide a static
+ * factory method annotated with {@link org.apache.logging.log4j.plugins.PluginFactory}.
*
* <p>Most core plugins are written using a related Manager class that handle the actual task of serializing a
* {@link LogEvent} to some output location. For instance, many Appenders can take
@@ -37,7 +37,7 @@
public interface Appender extends LifeCycle {
/**
- * Main {@linkplain org.apache.logging.log4j.core.config.plugins.Plugin#elementType() plugin element type} for
+ * Main {@linkplain org.apache.logging.log4j.plugins.Plugin#elementType() plugin element type} for
* Appender plugins.
*
* @since 2.6
@@ -89,4 +89,12 @@
* @param handler the ErrorHandler to use for handling exceptions.
*/
void setHandler(ErrorHandler handler);
+
+ /**
+ * Appenders should return true if they require location information.
+ * @return true if the Appender requires location information.
+ */
+ default boolean requiresLocation() {
+ return false;
+ }
}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/ContextDataInjector.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/ContextDataInjector.java
index 5351245..2cd36d8 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
@@ -27,6 +27,8 @@
/**
* 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.
+ * <p><b>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.</b></p>
* <p>
* The source of the context data is implementation-specific. The default source for context data is the ThreadContext.
* </p><p>
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 0286498..1baa39b 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
@@ -33,7 +33,7 @@
public interface Filter extends LifeCycle {
/**
- * Main {@linkplain org.apache.logging.log4j.core.config.plugins.Plugin#elementType() plugin element type} for
+ * Main {@linkplain org.apache.logging.log4j.plugins.Plugin#elementType() plugin element type} for
* Filter plugins.
*
* @since 2.1
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 73b5d6f..f5bc7f4 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/Layout.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/Layout.java
@@ -47,7 +47,7 @@
public interface Layout<T extends Serializable> extends Encoder<LogEvent> {
/**
- * Main {@linkplain org.apache.logging.log4j.core.config.plugins.Plugin#elementType() plugin element type} for
+ * Main {@linkplain org.apache.logging.log4j.plugins.Plugin#elementType() plugin element type} for
* Layout plugins.
*
* @since 2.1
@@ -96,4 +96,12 @@
* format descriptors are specified.
*/
Map<String, String> getContentFormat();
+
+ /**
+ * Indicates whether this Layout requires location information.
+ * @return returns true if the Layout requires location information.
+ */
+ default boolean requiresLocation() {
+ return false;
+ }
}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/LifeCycle.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/LifeCycle.java
index 4aaae4f..87467c0 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/LifeCycle.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/LifeCycle.java
@@ -17,6 +17,8 @@
package org.apache.logging.log4j.core;
+import java.util.concurrent.TimeUnit;
+
/**
* All proper Java frameworks implement some sort of object life cycle. In Log4j, the main interface for handling
* the life cycle context of an object is this one. An object first starts in the {@link State#INITIALIZED} state
@@ -66,4 +68,15 @@
boolean isStopped();
+ /**
+ * Blocks until all tasks have completed execution after a shutdown request, or the timeout occurs, or the current
+ * thread is interrupted, whichever happens first.
+ *
+ * @param timeout the maximum time to wait
+ * @param timeUnit the time unit of the timeout argument
+ * @return true if the receiver was stopped cleanly and normally, false otherwise.
+ * @since 2.7
+ */
+ boolean stop(long timeout, TimeUnit timeUnit);
+
}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/LifeCycle2.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/LifeCycle2.java
index 0046a62..fbba19b 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
@@ -26,15 +26,4 @@
* @since 2.7
*/
public interface LifeCycle2 extends LifeCycle {
-
- /**
- * Blocks until all tasks have completed execution after a shutdown request, or the timeout occurs, or the current
- * thread is interrupted, whichever happens first.
- *
- * @param timeout the maximum time to wait
- * @param timeUnit the time unit of the timeout argument
- * @return true if the receiver was stopped cleanly and normally, false otherwise.
- * @since 2.7
- */
- boolean stop(long timeout, TimeUnit timeUnit);
}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/LogEvent.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/LogEvent.java
index 017eb2e..c41606b 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
@@ -30,22 +30,15 @@
/**
* Provides contextual information about a logged message. A LogEvent must be {@link java.io.Serializable} so that it
- * may be transmitted over a network connection, output in a
- * {@link org.apache.logging.log4j.core.layout.SerializedLayout}, and many other uses. Besides containing a
+ * may be transmitted over a network connection. Besides containing a
* {@link org.apache.logging.log4j.message.Message}, a LogEvent has a corresponding
* {@link org.apache.logging.log4j.Level} that the message was logged at. If a
* {@link org.apache.logging.log4j.Marker} was used, then it is included here. The contents of the
* {@link org.apache.logging.log4j.ThreadContext} at the time of the log call are provided via
- * {@link #getContextMap()} and {@link #getContextStack()}. If a {@link java.lang.Throwable} was included in the log
+ * {@link #getContextData()} and {@link #getContextStack()}. If a {@link java.lang.Throwable} was included in the log
* call, then it is provided via {@link #getThrown()}. When this class is serialized, the attached Throwable will
* be wrapped into a {@link org.apache.logging.log4j.core.impl.ThrowableProxy} so that it may be safely serialized
* and deserialized properly without causing problems if the exception class is not available on the other end.
- * <p>
- * Since version 2.7, {@link #getContextMap()} is deprecated in favor of {@link #getContextData()}, which
- * can carry both {@code ThreadContext} data as well as other context data supplied by the
- * {@linkplain org.apache.logging.log4j.core.impl.ContextDataInjectorFactory configured}
- * {@link ContextDataInjector}.
- * </p>
*/
public interface LogEvent extends Serializable {
@@ -57,15 +50,6 @@
LogEvent toImmutable();
/**
- * Gets the context map (also know as Mapped Diagnostic Context or MDC).
- *
- * @return The context map, never {@code null}.
- * @deprecated use {@link #getContextData()} instead
- */
- @Deprecated
- Map<String, String> getContextMap();
-
- /**
* Returns the {@code ReadOnlyStringMap} object holding context data key-value pairs.
* <p>
* Context data (also known as Mapped Diagnostic Context or MDC) is data that is set by the application to be
@@ -133,14 +117,14 @@
long getTimeMillis();
/**
- * Returns the timestamp when the message was logged.
+ * Returns the Instant when the message was logged.
* <p>
* <b>Caution</b>: 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.
* </p>
*
- * @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.0
*/
Instant getInstant();
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/Logger.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/Logger.java
index ef90b40..806f4a7 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
@@ -139,6 +139,11 @@
}
@Override
+ protected boolean requiresLocation() {
+ return privateConfig.requiresLocation;
+ }
+
+ @Override
public void logMessage(final String fqcn, final Level level, final Marker marker, final Message message,
final Throwable t) {
final Message msg = message == null ? new SimpleMessage(Strings.EMPTY) : message;
@@ -147,6 +152,13 @@
}
@Override
+ protected void log(final Level level, final Marker marker, final String fqcn, final StackTraceElement location,
+ final Message message, final Throwable throwable) {
+ final ReliabilityStrategy strategy = privateConfig.loggerConfig.getReliabilityStrategy();
+ strategy.log(this, getName(), fqcn, location, marker, level, message, throwable);
+ }
+
+ @Override
public boolean isEnabled(final Level level, final Marker marker, final String message, final Throwable t) {
return privateConfig.filter(level, marker, message, t);
}
@@ -377,6 +389,7 @@
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 +397,7 @@
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 +406,7 @@
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 +415,7 @@
this.loggerConfigLevel = lc.getLevel();
this.intLevel = this.loggerConfigLevel.intLevel();
this.logger = pc.logger;
+ this.requiresLocation = this.loggerConfig.requiresLocation();
}
// LOG4J2-151: changed visibility to public
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/LoggerContext.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/LoggerContext.java
index 8234024..8b369fe 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
@@ -22,8 +22,13 @@
import java.beans.PropertyChangeListener;
import java.io.File;
import java.net.URI;
+import java.util.ArrayList;
import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
import java.util.Objects;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.TimeUnit;
@@ -39,33 +44,36 @@
import org.apache.logging.log4j.core.config.NullConfiguration;
import org.apache.logging.log4j.core.config.Reconfigurable;
import org.apache.logging.log4j.core.impl.Log4jLogEvent;
+import org.apache.logging.log4j.core.impl.ThreadContextDataInjector;
import org.apache.logging.log4j.core.jmx.Server;
import org.apache.logging.log4j.core.util.Cancellable;
import org.apache.logging.log4j.core.util.ExecutorServices;
+import org.apache.logging.log4j.core.util.Loader;
import org.apache.logging.log4j.core.util.NetUtils;
import org.apache.logging.log4j.core.util.ShutdownCallbackRegistry;
import org.apache.logging.log4j.message.MessageFactory;
import org.apache.logging.log4j.spi.AbstractLogger;
import org.apache.logging.log4j.spi.LoggerContextFactory;
+import org.apache.logging.log4j.spi.LoggerContextShutdownAware;
+import org.apache.logging.log4j.spi.LoggerContextShutdownEnabled;
import org.apache.logging.log4j.spi.LoggerRegistry;
import org.apache.logging.log4j.spi.Terminable;
import org.apache.logging.log4j.spi.ThreadContextMapFactory;
-import org.apache.logging.log4j.util.LoaderUtil;
import org.apache.logging.log4j.util.PropertiesUtil;
-
/**
* The LoggerContext is the anchor for the logging system. It maintains a list of all the loggers requested by
* applications and a reference to the Configuration. The Configuration will contain the configured loggers, appenders,
* 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 {
+ implements org.apache.logging.log4j.spi.LoggerContext, AutoCloseable, Terminable, ConfigurationListener,
+ LoggerContextShutdownEnabled {
static {
try {
// LOG4J2-1642 preload ExecutorServices as it is used in shutdown hook
- LoaderUtil.loadClass(ExecutorServices.class.getName());
+ Loader.loadClass(ExecutorServices.class.getName());
} catch (final Exception e) {
LOGGER.error("Failed to preload ExecutorServices class.", e);
}
@@ -80,13 +88,15 @@
private final LoggerRegistry<Logger> loggerRegistry = new LoggerRegistry<>();
private final CopyOnWriteArrayList<PropertyChangeListener> propertyChangeListeners = new CopyOnWriteArrayList<>();
+ private volatile List<LoggerContextShutdownAware> 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 static final String EXTERNAL_CONTEXT_KEY = "__EXTERNAL_CONTEXT_KEY__";
+ private final ConcurrentMap<String, Object> externalMap = new ConcurrentHashMap<>();
private String contextName;
private volatile URI configLocation;
private Cancellable shutdownCallback;
@@ -121,8 +131,13 @@
*/
public LoggerContext(final String name, final Object externalContext, final URI configLocn) {
this.contextName = name;
- this.externalContext = externalContext;
+ if (externalContext == null) {
+ externalMap.remove(EXTERNAL_CONTEXT_KEY);
+ } else {
+ externalMap.put(EXTERNAL_CONTEXT_KEY, externalContext);
+ }
this.configLocation = configLocn;
+ CompletableFuture.runAsync(ThreadContextDataInjector::initServiceProviders);
}
/**
@@ -135,7 +150,11 @@
*/
public LoggerContext(final String name, final Object externalContext, final String configLocn) {
this.contextName = name;
- this.externalContext = externalContext;
+ if (externalContext == null) {
+ externalMap.remove(EXTERNAL_CONTEXT_KEY);
+ } else {
+ externalMap.put(EXTERNAL_CONTEXT_KEY, externalContext);
+ }
if (configLocn != null) {
URI uri;
try {
@@ -147,6 +166,22 @@
} else {
configLocation = null;
}
+ CompletableFuture.runAsync(ThreadContextDataInjector::initServiceProviders);
+ }
+
+ public void addShutdownListener(LoggerContextShutdownAware listener) {
+ if (listeners == null) {
+ synchronized(this) {
+ if (listeners == null) {
+ listeners = Collections.synchronizedList(new ArrayList<>());
+ }
+ }
+ }
+ listeners.add(listener);
+ }
+
+ public List<LoggerContextShutdownAware> getListeners() {
+ return listeners;
}
/**
@@ -349,17 +384,22 @@
final Configuration prev = configuration;
configuration = NULL_CONFIGURATION;
updateLoggers();
- if (prev instanceof LifeCycle2) {
- ((LifeCycle2) prev).stop(timeout, timeUnit);
- } else {
- prev.stop();
- }
- externalContext = null;
+ ((LifeCycle) prev).stop(timeout, timeUnit);
+ 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;
}
@@ -392,13 +432,42 @@
contextName = Objects.requireNonNull(name);
}
+ @Override
+ public Object getObject(String key) {
+ return externalMap.get(key);
+ }
+
+ @Override
+ public Object putObject(String key, Object value) {
+ return externalMap.put(key, value);
+ }
+
+ @Override
+ public Object putObjectIfAbsent(String key, Object value) {
+ return externalMap.putIfAbsent(key, value);
+ }
+
+ @Override
+ public Object removeObject(String key) {
+ return externalMap.remove(key);
+ }
+
+ @Override
+ public boolean removeObject(String key, Object value) {
+ return externalMap.remove(key, value);
+ }
+
/**
* Sets the external context.
*
* @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 +477,7 @@
*/
@Override
public Object getExternalContext() {
- return this.externalContext;
+ return this.externalMap.get(EXTERNAL_CONTEXT_KEY);
}
/**
@@ -493,7 +562,8 @@
/**
* Returns the current Configuration. The Configuration will be replaced when a reconfigure occurs.
*
- * @return The Configuration.
+ * @return The current Configuration, never {@code null}, but may be
+ * {@link org.apache.logging.log4j.core.config.NullConfiguration}.
*/
public Configuration getConfiguration() {
return configuration;
@@ -524,7 +594,7 @@
* @param config The new Configuration.
* @return The previous Configuration.
*/
- private Configuration setConfiguration(final Configuration config) {
+ public Configuration setConfiguration(final Configuration config) {
if (config == null) {
LOGGER.error("No configuration found for context '{}'.", contextName);
// No change, return the current configuration.
@@ -609,6 +679,7 @@
* Reconfigures the context.
*/
private void reconfigure(final URI configURI) {
+ Object externalContext = externalMap.get(EXTERNAL_CONTEXT_KEY);
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);
@@ -636,6 +707,17 @@
reconfigure(configLocation);
}
+ public void reconfigure(Configuration configuration) {
+ setConfiguration(configuration);
+ ConfigurationSource source = configuration.getConfigurationSource();
+ if (source != null) {
+ URI uri = source.getURI();
+ if (uri != null) {
+ configLocation = uri;
+ }
+ }
+ }
+
/**
* Causes all Loggers to be updated against the current Configuration.
*/
@@ -663,14 +745,17 @@
*/
@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);
}
}
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 57974bd..d1c171b 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AbstractAppender.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AbstractAppender.java
@@ -26,10 +26,11 @@
import org.apache.logging.log4j.core.Layout;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.config.Configuration;
-import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
+import org.apache.logging.log4j.core.config.Property;
+import org.apache.logging.log4j.plugins.PluginBuilderAttribute;
import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
-import org.apache.logging.log4j.core.config.plugins.PluginElement;
-import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required;
+import org.apache.logging.log4j.plugins.PluginElement;
+import org.apache.logging.log4j.plugins.validation.constraints.Required;
import org.apache.logging.log4j.core.filter.AbstractFilterable;
import org.apache.logging.log4j.core.layout.PatternLayout;
import org.apache.logging.log4j.core.util.Integers;
@@ -72,17 +73,17 @@
return layout;
}
- public B withName(final String name) {
+ public B setName(final String name) {
this.name = name;
return asBuilder();
}
- public B withIgnoreExceptions(final boolean ignoreExceptions) {
+ public B setIgnoreExceptions(final boolean ignoreExceptions) {
this.ignoreExceptions = ignoreExceptions;
return asBuilder();
}
- public B withLayout(final Layout<? extends Serializable> layout) {
+ public B setLayout(final Layout<? extends Serializable> layout) {
this.layout = layout;
return asBuilder();
}
@@ -96,20 +97,11 @@
public Layout<? extends Serializable> getOrCreateLayout(final Charset charset) {
if (layout == null) {
- return PatternLayout.newBuilder().withCharset(charset).build();
+ return PatternLayout.newBuilder().setCharset(charset).build();
}
return layout;
}
- /**
- * @deprecated Use {@link #setConfiguration(Configuration)}
- */
- @Deprecated
- public B withConfiguration(final Configuration configuration) {
- this.configuration = configuration;
- return asBuilder();
- }
-
public B setConfiguration(final Configuration configuration) {
this.configuration = configuration;
return asBuilder();
@@ -127,17 +119,6 @@
private ErrorHandler handler = new DefaultErrorHandler(this);
/**
- * 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.
- */
- protected AbstractAppender(final String name, final Filter filter, final Layout<? extends Serializable> layout) {
- this(name, filter, layout, true);
- }
-
- /**
* Constructor.
*
* @param name The Appender name.
@@ -145,15 +126,45 @@
* @param layout The layout to use to format the event.
* @param ignoreExceptions If true, exceptions will be logged and suppressed. If false errors will be logged and
* then passed to the application.
+ * @param properties Optional properties
*/
protected AbstractAppender(final String name, final Filter filter, final Layout<? extends Serializable> layout,
- final boolean ignoreExceptions) {
- super(filter);
+ final boolean ignoreExceptions, final Property[] properties) {
+ super(filter, properties);
this.name = Objects.requireNonNull(name, "name");
this.layout = layout;
this.ignoreExceptions = ignoreExceptions;
}
+ /**
+ * Constructor that defaults to suppressing exceptions.
+ *
+ * @param name The Appender name.
+ * @param filter The Filter to associate with the Appender.
+ * @param layout The layout to use to format the event.
+ * @deprecated Use {@link #AbstractAppender(String, Filter, Layout, boolean, Property[])}.
+ */
+ @Deprecated
+ protected AbstractAppender(final String name, final Filter filter, final Layout<? extends Serializable> layout) {
+ this(name, filter, layout, true, Property.EMPTY_ARRAY);
+ }
+
+ /**
+ * Constructor.
+ *
+ * @param name The Appender name.
+ * @param filter The Filter to associate with the Appender.
+ * @param layout The layout to use to format the event.
+ * @param ignoreExceptions If true, exceptions will be logged and suppressed. If false errors will be logged and
+ * then passed to the application.
+ * @deprecated Use {@link #AbstractAppender(String, Filter, Layout, boolean, Property[])}
+ */
+ @Deprecated
+ protected AbstractAppender(final String name, final Filter filter, final Layout<? extends Serializable> layout,
+ final boolean ignoreExceptions) {
+ this(name, filter, layout, ignoreExceptions, Property.EMPTY_ARRAY);
+ }
+
public static int parseInt(final String s, final int defaultValue) {
try {
return Integers.parseInt(s, defaultValue);
@@ -163,6 +174,11 @@
}
}
+ @Override
+ public boolean requiresLocation() {
+ return layout != null && layout.requiresLocation();
+ }
+
/**
* Handle an error with a message using the {@link ErrorHandler} configured for this Appender.
*
@@ -244,6 +260,7 @@
public void setHandler(final ErrorHandler handler) {
if (handler == null) {
LOGGER.error("The handler cannot be set to null");
+ return;
}
if (isStarted()) {
LOGGER.error("The handler cannot be changed once the appender is started");
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 09127c3..4b88cc7 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AbstractFileAppender.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AbstractFileAppender.java
@@ -23,8 +23,9 @@
import org.apache.logging.log4j.core.Filter;
import org.apache.logging.log4j.core.Layout;
-import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
-import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required;
+import org.apache.logging.log4j.core.config.Property;
+import org.apache.logging.log4j.plugins.PluginBuilderAttribute;
+import org.apache.logging.log4j.plugins.validation.constraints.Required;
import org.apache.logging.log4j.core.net.Advertiser;
/**
@@ -104,47 +105,47 @@
return fileGroup;
}
- public B withAdvertise(final boolean advertise) {
+ public B setAdvertise(final boolean advertise) {
this.advertise = advertise;
return asBuilder();
}
- public B withAdvertiseUri(final String advertiseUri) {
+ public B setAdvertiseUri(final String advertiseUri) {
this.advertiseUri = advertiseUri;
return asBuilder();
}
- public B withAppend(final boolean append) {
+ public B setAppend(final boolean append) {
this.append = append;
return asBuilder();
}
- public B withFileName(final String fileName) {
+ public B setFileName(final String fileName) {
this.fileName = fileName;
return asBuilder();
}
- public B withCreateOnDemand(final boolean createOnDemand) {
+ public B setCreateOnDemand(final boolean createOnDemand) {
this.createOnDemand = createOnDemand;
return asBuilder();
}
- public B withLocking(final boolean locking) {
+ public B setLocking(final boolean locking) {
this.locking = locking;
return asBuilder();
}
- public B withFilePermissions(final String filePermissions) {
+ public B setFilePermissions(final String filePermissions) {
this.filePermissions = filePermissions;
return asBuilder();
}
- public B withFileOwner(final String fileOwner) {
+ public B setFileOwner(final String fileOwner) {
this.fileOwner = fileOwner;
return asBuilder();
}
- public B withFileGroup(final String fileGroup) {
+ public B setFileGroup(final String fileGroup) {
this.fileGroup = fileGroup;
return asBuilder();
}
@@ -159,9 +160,9 @@
private AbstractFileAppender(final String name, final Layout<? extends Serializable> layout, final Filter filter,
final M manager, final String filename, final boolean ignoreExceptions,
- final boolean immediateFlush, final Advertiser advertiser) {
+ final boolean immediateFlush, final Advertiser advertiser, Property[] properties) {
- super(name, layout, filter, ignoreExceptions, immediateFlush, manager);
+ super(name, layout, filter, ignoreExceptions, immediateFlush, properties, manager);
if (advertiser != null) {
final Map<String, String> 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 5b146f8..8b5f729 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
@@ -126,6 +126,11 @@
}
}
+ /**
+ * Used by Log4j to update the Manager during reconfiguration. This method should be considered private.
+ * Implementations may not be thread safe. This method may be made protected in a future release.
+ * @param data The data to update.
+ */
public void updateData(final Object data) {
// This default implementation does nothing.
}
@@ -164,6 +169,10 @@
manager.getName() + "'");
}
+ protected static StatusLogger logger() {
+ return StatusLogger.getLogger();
+ }
+
/**
* May be overridden by managers to perform processing while the manager is being released and the
* lock is held. A timeout is passed for implementors to use as they see fit.
@@ -192,15 +201,6 @@
}
/**
- * Called to signify that this Manager is no longer required by an Appender.
- * @deprecated In 2.7, use {@link #close()}.
- */
- @Deprecated
- public void release() {
- close();
- }
-
- /**
* Returns the name of the Manager.
* @return The name of the Manager.
*/
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AbstractOutputStreamAppender.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AbstractOutputStreamAppender.java
index 1e6f3e4..b498f95 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AbstractOutputStreamAppender.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AbstractOutputStreamAppender.java
@@ -22,7 +22,8 @@
import org.apache.logging.log4j.core.Filter;
import org.apache.logging.log4j.core.Layout;
import org.apache.logging.log4j.core.LogEvent;
-import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
+import org.apache.logging.log4j.core.config.Property;
+import org.apache.logging.log4j.plugins.PluginBuilderAttribute;
import org.apache.logging.log4j.core.util.Constants;
/**
@@ -60,17 +61,17 @@
return immediateFlush;
}
- public B withImmediateFlush(final boolean immediateFlush) {
+ public B setImmediateFlush(final boolean immediateFlush) {
this.immediateFlush = immediateFlush;
return asBuilder();
}
- public B withBufferedIo(final boolean bufferedIo) {
+ public B setBufferedIo(final boolean bufferedIo) {
this.bufferedIo = bufferedIo;
return asBuilder();
}
- public B withBufferSize(final int bufferSize) {
+ public B setBufferSize(final int bufferSize) {
this.bufferSize = bufferSize;
return asBuilder();
}
@@ -93,11 +94,13 @@
*
* @param name The name of the Appender.
* @param layout The layout to format the message.
+ * @param properties Optional properties.
* @param manager The OutputStreamManager.
*/
protected AbstractOutputStreamAppender(final String name, final Layout<? extends Serializable> layout,
- final Filter filter, final boolean ignoreExceptions, final boolean immediateFlush, final M manager) {
- super(name, filter, layout, ignoreExceptions);
+ final Filter filter, final boolean ignoreExceptions, final boolean immediateFlush, Property[] properties,
+ final M manager) {
+ super(name, filter, layout, ignoreExceptions, properties);
this.manager = manager;
this.immediateFlush = immediateFlush;
}
@@ -160,7 +163,7 @@
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 17a0bb4..0042a27 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AbstractWriterAppender.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AbstractWriterAppender.java
@@ -1,126 +1,128 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache license, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the license for the specific language governing permissions and
- * limitations under the license.
- */
-package org.apache.logging.log4j.core.appender;
-
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReadWriteLock;
-import java.util.concurrent.locks.ReentrantReadWriteLock;
-
-import org.apache.logging.log4j.core.Filter;
-import org.apache.logging.log4j.core.LogEvent;
-import org.apache.logging.log4j.core.StringLayout;
-
-/**
- * Appends log events as strings to a writer.
- *
- * @param <M>
- * The kind of {@link WriterManager} under management
- */
-public abstract class AbstractWriterAppender<M extends WriterManager> 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 <code>immediateFlush</code>
- * 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.
- * <p>
- * Most subclasses will need to override this method.
- * </p>
- *
- * @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 <M>
+ * The kind of {@link WriterManager} under management
+ */
+public abstract class AbstractWriterAppender<M extends WriterManager> 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 <code>immediateFlush</code>
+ * is set to {@code false}, then there is a good chance that the last few
+ * logs events are not actually written to persistent media if and when the
+ * application crashes.
+ */
+ protected final boolean immediateFlush;
+ private final M manager;
+ private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
+ private final Lock readLock = readWriteLock.readLock();
+
+ /**
+ * Instantiates.
+ *
+ * @param name
+ * The name of the Appender.
+ * @param layout
+ * The layout to format the message.
+ * @param properties Optional properties.
+ * @param manager
+ * The OutputStreamManager.
+ */
+ protected AbstractWriterAppender(final String name, final StringLayout layout, final Filter filter,
+ final boolean ignoreExceptions, final boolean immediateFlush, Property[] properties, final M manager) {
+ super(name, filter, layout, ignoreExceptions, properties);
+ this.manager = manager;
+ this.immediateFlush = immediateFlush;
+ }
+
+ /**
+ * Actual writing occurs here.
+ * <p>
+ * Most subclasses will need to override this method.
+ * </p>
+ *
+ * @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 4b65a2c..4174e4e 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AppenderLoggingException.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AppenderLoggingException.java
@@ -24,17 +24,18 @@
* using the {@link org.apache.logging.log4j.status.StatusLogger}. Appenders should only throw exceptions when an error
* prevents an event from being written. Appenders <em>must</em> throw an exception in this case so that error-handling
* features like the {@link FailoverAppender} work properly.
- *
+ * <p>
* Also note that appenders <em>must</em> 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.
+ * </p>
*/
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 @@
}
/**
- * 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, 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 @@
}
/**
- * 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, 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 7b3c07b..b592738 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AppenderSet.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AppenderSet.java
@@ -16,28 +16,28 @@
*/
package org.apache.logging.log4j.core.appender;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
import org.apache.logging.log4j.core.Appender;
import org.apache.logging.log4j.core.Core;
import org.apache.logging.log4j.core.config.Configuration;
-import org.apache.logging.log4j.core.config.Node;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
-import org.apache.logging.log4j.core.config.plugins.PluginNode;
-import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required;
+import org.apache.logging.log4j.plugins.Node;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginFactory;
+import org.apache.logging.log4j.plugins.PluginNode;
+import org.apache.logging.log4j.plugins.validation.constraints.Required;
import org.apache.logging.log4j.status.StatusLogger;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
/**
* A deferred plugin for appenders.
*/
@Plugin(name = "AppenderSet", category = Core.CATEGORY_NAME, printObject = true, deferChildren = true)
public class AppenderSet {
- public static class Builder implements org.apache.logging.log4j.core.util.Builder<AppenderSet> {
+ public static class Builder implements org.apache.logging.log4j.plugins.util.Builder<AppenderSet> {
@PluginNode
private Node node;
@@ -82,12 +82,12 @@
return configuration;
}
- public Builder withNode(@SuppressWarnings("hiding") final Node node) {
+ public Builder setNode(@SuppressWarnings("hiding") final Node node) {
this.node = node;
return this;
}
- public Builder withConfiguration(@SuppressWarnings("hiding") final Configuration configuration) {
+ public Builder setConfiguration(@SuppressWarnings("hiding") final Configuration configuration) {
this.configuration = configuration;
return this;
}
@@ -104,7 +104,7 @@
private final Configuration configuration;
private final Map<String, Node> nodeMap;
- @PluginBuilderFactory
+ @PluginFactory
public static Builder newBuilder() {
return new Builder();
}
@@ -114,13 +114,13 @@
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 +132,7 @@
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 b0ed1d9..d4a270b 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AsyncAppender.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/AsyncAppender.java
@@ -16,14 +16,6 @@
*/
package org.apache.logging.log4j.core.appender;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TransferQueue;
-import java.util.concurrent.atomic.AtomicLong;
-
import org.apache.logging.log4j.core.AbstractLogEvent;
import org.apache.logging.log4j.core.Appender;
import org.apache.logging.log4j.core.Core;
@@ -41,17 +33,27 @@
import org.apache.logging.log4j.core.config.AppenderRef;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.ConfigurationException;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginAliases;
-import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
-import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
+import org.apache.logging.log4j.core.config.Property;
import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
-import org.apache.logging.log4j.core.config.plugins.PluginElement;
-import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required;
+import org.apache.logging.log4j.core.filter.AbstractFilterable;
import org.apache.logging.log4j.core.impl.Log4jLogEvent;
import org.apache.logging.log4j.core.util.Log4jThread;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginAliases;
+import org.apache.logging.log4j.plugins.PluginBuilderAttribute;
+import org.apache.logging.log4j.plugins.PluginElement;
+import org.apache.logging.log4j.plugins.PluginFactory;
+import org.apache.logging.log4j.plugins.validation.constraints.Required;
import org.apache.logging.log4j.spi.AbstractLogger;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TransferQueue;
+import java.util.concurrent.atomic.AtomicLong;
+
/**
* Appends to one or more Appenders asynchronously. You can configure an AsyncAppender with one or more Appenders and an
* Appender to append to if the queue is full. The AsyncAppender does not allow a filter to be specified on the Appender
@@ -62,6 +64,7 @@
private static final int DEFAULT_QUEUE_SIZE = 1024;
private static final LogEvent SHUTDOWN_LOG_EVENT = new AbstractLogEvent() {
+ // empty
};
private static final AtomicLong THREAD_SEQUENCE = new AtomicLong(1);
@@ -79,10 +82,10 @@
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<LogEvent> blockingQueueFactory) {
- super(name, filter, null, ignoreExceptions);
+ final String errorRef, final int queueSize, final boolean blocking, final boolean ignoreExceptions,
+ final long shutdownTimeout, final Configuration config, final boolean includeLocation,
+ final BlockingQueueFactory<LogEvent> blockingQueueFactory, Property[] properties) {
+ super(name, filter, null, ignoreExceptions, properties);
this.queue = blockingQueueFactory.create(queueSize);
this.queueSize = queueSize;
this.blocking = blocking;
@@ -236,49 +239,13 @@
}
}
- /**
- * Create an AsyncAppender. This method is retained for backwards compatibility. New code should use the
- * {@link Builder} instead. This factory will use {@link ArrayBlockingQueueFactory} by default as was the behavior
- * pre-2.7.
- *
- * @param appenderRefs The Appenders to reference.
- * @param errorRef An optional Appender to write to if the queue is full or other errors occur.
- * @param blocking True if the Appender should wait when the queue is full. The default is true.
- * @param shutdownTimeout How many milliseconds the Appender should wait to flush outstanding log events
- * in the queue on shutdown. The default is zero which means to wait forever.
- * @param size The size of the event queue. The default is 128.
- * @param name The name of the Appender.
- * @param includeLocation whether to include location information. The default is false.
- * @param filter The Filter or null.
- * @param config The Configuration.
- * @param ignoreExceptions If {@code "true"} (default) exceptions encountered when appending events are logged;
- * otherwise they are propagated to the caller.
- * @return The AsyncAppender.
- * @deprecated use {@link Builder} instead
- */
- @Deprecated
- public static AsyncAppender createAppender(final AppenderRef[] appenderRefs, final String errorRef,
- final boolean blocking, final long shutdownTimeout, final int size,
- final String name, final boolean includeLocation, final Filter filter,
- final Configuration config, final boolean ignoreExceptions) {
- if (name == null) {
- LOGGER.error("No name provided for AsyncAppender");
- return null;
- }
- if (appenderRefs == null) {
- LOGGER.error("No appender references provided to AsyncAppender {}", name);
- }
-
- return new AsyncAppender(name, filter, appenderRefs, errorRef, size, blocking, ignoreExceptions,
- shutdownTimeout, config, includeLocation, new ArrayBlockingQueueFactory<LogEvent>());
- }
-
- @PluginBuilderFactory
+ @PluginFactory
public static Builder newBuilder() {
return new Builder();
}
- public static class Builder implements org.apache.logging.log4j.core.util.Builder<AsyncAppender> {
+ public static class Builder<B extends Builder<B>> extends AbstractFilterable.Builder<B>
+ implements org.apache.logging.log4j.plugins.util.Builder<AsyncAppender> {
@PluginElement("AppenderRef")
@Required(message = "No appender references provided to AsyncAppender")
@@ -304,9 +271,6 @@
@PluginBuilderAttribute
private boolean includeLocation = false;
- @PluginElement("Filter")
- private Filter filter;
-
@PluginConfiguration
private Configuration configuration;
@@ -351,11 +315,6 @@
return this;
}
- public Builder setFilter(final Filter filter) {
- this.filter = filter;
- return this;
- }
-
public Builder setConfiguration(final Configuration configuration) {
this.configuration = configuration;
return this;
@@ -373,8 +332,8 @@
@Override
public AsyncAppender build() {
- return new AsyncAppender(name, filter, appenderRefs, errorRef, bufferSize, blocking, ignoreExceptions,
- shutdownTimeout, configuration, includeLocation, blockingQueueFactory);
+ return new AsyncAppender(name, getFilter(), appenderRefs, errorRef, bufferSize, blocking, ignoreExceptions,
+ shutdownTimeout, configuration, includeLocation, blockingQueueFactory, getPropertyArray());
}
}
@@ -383,7 +342,7 @@
*/
private class AsyncThread extends Log4jThread {
- private volatile boolean shutdown = false;
+ private volatile boolean shutdown;
private final List<AppenderControl> appenders;
private final BlockingQueue<LogEvent> queue;
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 f6103e6..ce73da0 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
@@ -16,6 +16,21 @@
*/
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.Filter;
+import org.apache.logging.log4j.core.Layout;
+import org.apache.logging.log4j.core.config.Property;
+import org.apache.logging.log4j.core.util.CloseShieldOutputStream;
+import org.apache.logging.log4j.core.util.Loader;
+import org.apache.logging.log4j.core.util.Throwables;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginBuilderAttribute;
+import org.apache.logging.log4j.plugins.PluginFactory;
+import org.apache.logging.log4j.plugins.validation.constraints.Required;
+import org.apache.logging.log4j.util.Chars;
+import org.apache.logging.log4j.util.PropertiesUtil;
+
import java.io.FileDescriptor;
import java.io.FileOutputStream;
import java.io.IOException;
@@ -27,22 +42,6 @@
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.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.Chars;
-import org.apache.logging.log4j.util.LoaderUtil;
-import org.apache.logging.log4j.util.PropertiesUtil;
-
/**
* Appends log events to <code>System.out</code> or <code>System.err</code> using a layout specified by the user. The
* default target is <code>System.out</code>.
@@ -58,7 +57,7 @@
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();
@@ -96,92 +95,18 @@
}
private ConsoleAppender(final String name, final Layout<? extends Serializable> layout, final Filter filter,
- final OutputStreamManager manager, final boolean ignoreExceptions, final Target target) {
- super(name, layout, filter, ignoreExceptions, true, manager);
+ final OutputStreamManager manager, final boolean ignoreExceptions, final Target target, Property[] properties) {
+ super(name, layout, filter, ignoreExceptions, true, properties, manager);
this.target = target;
}
- /**
- * Creates a Console Appender.
- *
- * @param layout The layout to use (required).
- * @param filter The Filter or null.
- * @param targetStr The target ("SYSTEM_OUT" or "SYSTEM_ERR"). The default is "SYSTEM_OUT".
- * @param name The name of the Appender (required).
- * @param follow If true will follow changes to the underlying output stream.
- * @param ignore If {@code "true"} (default) exceptions encountered when appending events are logged; otherwise they
- * are propagated to the caller.
- * @return The ConsoleAppender.
- * @deprecated Deprecated in 2.7; use {@link #newBuilder()}.
- */
- @Deprecated
- public static ConsoleAppender createAppender(Layout<? extends Serializable> layout,
- final Filter filter,
- final String targetStr,
- final String name,
- final String follow,
- final String ignore) {
- if (name == null) {
- LOGGER.error("No name provided for ConsoleAppender");
- return null;
- }
- if (layout == null) {
- layout = PatternLayout.createDefaultLayout();
- }
- final boolean isFollow = Boolean.parseBoolean(follow);
- final boolean ignoreExceptions = Booleans.parseBoolean(ignore, true);
- final Target target = targetStr == null ? DEFAULT_TARGET : Target.valueOf(targetStr);
- return new ConsoleAppender(name, layout, filter, getManager(target, isFollow, false, layout), ignoreExceptions, target);
- }
-
- /**
- * Creates a Console Appender.
- *
- * @param layout The layout to use (required).
- * @param filter The Filter or null.
- * @param target The target (SYSTEM_OUT or SYSTEM_ERR). The default is SYSTEM_OUT.
- * @param name The name of the Appender (required).
- * @param follow If true will follow changes to the underlying output stream.
- * @param direct If true will write directly to {@link java.io.FileDescriptor} and bypass
- * {@link System#out}/{@link System#err}.
- * @param ignoreExceptions If {@code "true"} (default) exceptions encountered when appending events are logged; otherwise they
- * are propagated to the caller.
- * @return The ConsoleAppender.
- * @deprecated Deprecated in 2.7; use {@link #newBuilder()}.
- */
- @Deprecated
- public static ConsoleAppender createAppender(
- // @formatter:off
- Layout<? extends Serializable> layout,
- final Filter filter,
- Target target,
- final String name,
- final boolean follow,
- final boolean direct,
- final boolean ignoreExceptions) {
- // @formatter:on
- if (name == null) {
- LOGGER.error("No name provided for ConsoleAppender");
- return null;
- }
- if (layout == null) {
- layout = PatternLayout.createDefaultLayout();
- }
- target = target == null ? Target.SYSTEM_OUT : target;
- if (follow && direct) {
- LOGGER.error("Cannot use both follow and direct on ConsoleAppender");
- return null;
- }
- return new ConsoleAppender(name, layout, filter, getManager(target, follow, direct, layout), ignoreExceptions, target);
- }
-
public static ConsoleAppender createDefaultAppenderForLayout(final Layout<? extends Serializable> layout) {
// this method cannot use the builder class without introducing an infinite loop due to DefaultConfiguration
return new ConsoleAppender("DefaultConsole-" + COUNT.incrementAndGet(), layout, null,
- getDefaultManager(DEFAULT_TARGET, false, false, layout), true, DEFAULT_TARGET);
+ getDefaultManager(DEFAULT_TARGET, false, false, layout), true, DEFAULT_TARGET, null);
}
- @PluginBuilderFactory
+ @PluginFactory
public static <B extends Builder<B>> B newBuilder() {
return new Builder<B>().asBuilder();
}
@@ -191,7 +116,7 @@
* @param <B> The type to build
*/
public static class Builder<B extends Builder<B>> extends AbstractOutputStreamAppender.Builder<B>
- implements org.apache.logging.log4j.core.util.Builder<ConsoleAppender> {
+ implements org.apache.logging.log4j.plugins.util.Builder<ConsoleAppender> {
@PluginBuilderAttribute
@Required
@@ -225,7 +150,7 @@
}
final Layout<? extends Serializable> layout = getOrCreateLayout(target.getDefaultCharset());
return new ConsoleAppender(getName(), layout, getFilter(), getManager(target, follow, direct, layout),
- isIgnoreExceptions(), target);
+ isIgnoreExceptions(), target, getPropertyArray());
}
}
@@ -266,7 +191,7 @@
}
try {
// We type the parameter as a wildcard to avoid a hard reference to Jansi.
- final Class<?> clazz = LoaderUtil.loadClass(JANSI_CLASS);
+ final Class<?> clazz = Loader.loadClass(JANSI_CLASS);
final Constructor<?> constructor = clazz.getConstructor(OutputStream.class);
return new CloseShieldOutputStream((OutputStream) constructor.newInstance(outputStream));
} catch (final ClassNotFoundException cnfe) {
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 0e3ac47..e5628f4 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
@@ -23,9 +23,10 @@
import org.apache.logging.log4j.core.Core;
import org.apache.logging.log4j.core.Layout;
import org.apache.logging.log4j.core.LogEvent;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
-import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+import org.apache.logging.log4j.core.config.Property;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginAttribute;
+import org.apache.logging.log4j.plugins.PluginFactory;
/**
* No-Operation Appender that counts events.
@@ -36,7 +37,7 @@
private final AtomicLong total = new AtomicLong();
public CountingNoOpAppender(final String name, final Layout<?> layout) {
- super(name, null, layout);
+ super(name, null, layout, true, Property.EMPTY_ARRAY);
}
public long getCount() {
@@ -52,7 +53,7 @@
* Creates a CountingNoOp Appender.
*/
@PluginFactory
- public static CountingNoOpAppender createAppender(@PluginAttribute("name") final String name) {
+ public static CountingNoOpAppender createAppender(@PluginAttribute final String name) {
return new CountingNoOpAppender(Objects.requireNonNull(name), null);
}
}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/FailoverAppender.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/FailoverAppender.java
index 659caf1..72bf446 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
@@ -28,14 +28,16 @@
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.config.AppenderControl;
import org.apache.logging.log4j.core.config.Configuration;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginAliases;
-import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
+import org.apache.logging.log4j.core.config.Property;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginAliases;
+import org.apache.logging.log4j.plugins.PluginAttribute;
import org.apache.logging.log4j.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.plugins.PluginElement;
+import org.apache.logging.log4j.plugins.PluginFactory;
import org.apache.logging.log4j.core.util.Booleans;
import org.apache.logging.log4j.core.util.Constants;
+import org.apache.logging.log4j.plugins.validation.constraints.Required;
/**
* The FailoverAppender will capture exceptions in an Appender and then route the event
@@ -59,18 +61,18 @@
private final long intervalNanos;
- private volatile long nextCheckNanos = 0;
+ private volatile long nextCheckNanos;
private FailoverAppender(final String name, final Filter filter, final String primary, final String[] failovers,
- final int intervalMillis, final Configuration config, final boolean ignoreExceptions) {
- super(name, filter, null, ignoreExceptions);
+ final int intervalMillis, final Configuration config, final boolean ignoreExceptions,
+ 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<String, Appender> map = config.getAppenders();
@@ -181,38 +183,22 @@
*/
@PluginFactory
public static FailoverAppender createAppender(
- @PluginAttribute("name") final String name,
- @PluginAttribute("primary") final String primary,
- @PluginElement("Failovers") final String[] failovers,
+ @PluginAttribute @Required(message = "A name for the Appender must be specified") final String name,
+ @PluginAttribute @Required(message = "A primary Appender must be specified") final String primary,
+ @PluginElement @Required(message = "At least one failover Appender must be specified") final String[] failovers,
@PluginAliases("retryInterval") // deprecated
- @PluginAttribute("retryIntervalSeconds") final String retryIntervalSeconds,
+ @PluginAttribute(defaultInt = DEFAULT_INTERVAL_SECONDS) final int retryIntervalSeconds,
@PluginConfiguration final Configuration config,
- @PluginElement("Filter") final Filter filter,
- @PluginAttribute("ignoreExceptions") final String ignore) {
- if (name == null) {
- LOGGER.error("A name for the Appender must be specified");
- return null;
- }
- if (primary == null) {
- LOGGER.error("A primary Appender must be specified");
- return null;
- }
- if (failovers == null || failovers.length == 0) {
- LOGGER.error("At least one failover Appender must be specified");
- return null;
- }
+ @PluginElement final Filter filter,
+ @PluginAttribute(defaultBoolean = true) final boolean ignoreExceptions) {
- final int seconds = parseInt(retryIntervalSeconds, DEFAULT_INTERVAL_SECONDS);
int retryIntervalMillis;
- if (seconds >= 0) {
- retryIntervalMillis = seconds * Constants.MILLIS_IN_SECONDS;
+ if (retryIntervalSeconds >= 0) {
+ retryIntervalMillis = retryIntervalSeconds * Constants.MILLIS_IN_SECONDS;
} else {
- LOGGER.warn("Interval " + retryIntervalSeconds + " is less than zero. Using default");
+ LOGGER.warn("Interval {} is less than zero. Using default", retryIntervalSeconds);
retryIntervalMillis = DEFAULT_INTERVAL_SECONDS * Constants.MILLIS_IN_SECONDS;
}
-
- final boolean ignoreExceptions = Booleans.parseBoolean(ignore, true);
-
- return new FailoverAppender(name, filter, primary, failovers, retryIntervalMillis, config, ignoreExceptions);
+ return new FailoverAppender(name, filter, primary, failovers, retryIntervalMillis, config, ignoreExceptions, Property.EMPTY_ARRAY);
}
}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/FailoversPlugin.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/FailoversPlugin.java
index c537d91..1bf889b 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
@@ -19,9 +19,9 @@
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.core.Core;
import org.apache.logging.log4j.core.config.AppenderRef;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginElement;
-import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginElement;
+import org.apache.logging.log4j.plugins.PluginFactory;
import org.apache.logging.log4j.status.StatusLogger;
/**
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 8e5736f..6f3af1b 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/FileAppender.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/FileAppender.java
@@ -16,23 +16,21 @@
*/
package org.apache.logging.log4j.core.appender;
-import java.io.Serializable;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.TimeUnit;
-
import org.apache.logging.log4j.core.Appender;
import org.apache.logging.log4j.core.Core;
import org.apache.logging.log4j.core.Filter;
import org.apache.logging.log4j.core.Layout;
-import org.apache.logging.log4j.core.config.Configuration;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
-import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
-import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required;
+import org.apache.logging.log4j.core.config.Property;
import org.apache.logging.log4j.core.net.Advertiser;
-import org.apache.logging.log4j.core.util.Booleans;
-import org.apache.logging.log4j.core.util.Integers;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginBuilderAttribute;
+import org.apache.logging.log4j.plugins.PluginFactory;
+import org.apache.logging.log4j.plugins.validation.constraints.Required;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
/**
* File Appender.
@@ -49,7 +47,7 @@
* The type to build
*/
public static class Builder<B extends Builder<B>> extends AbstractOutputStreamAppender.Builder<B>
- implements org.apache.logging.log4j.core.util.Builder<FileAppender> {
+ implements org.apache.logging.log4j.plugins.util.Builder<FileAppender> {
@PluginBuilderAttribute
@Required
@@ -99,7 +97,7 @@
}
return new FileAppender(getName(), layout, getFilter(), manager, fileName, isIgnoreExceptions(),
- !bufferedIo || isImmediateFlush(), advertise ? getConfiguration().getAdvertiser() : null);
+ !bufferedIo || isImmediateFlush(), advertise ? getConfiguration().getAdvertiser() : null, getPropertyArray());
}
public String getAdvertiseUri() {
@@ -138,112 +136,54 @@
return fileGroup;
}
- public B withAdvertise(final boolean advertise) {
+ public B setAdvertise(final boolean advertise) {
this.advertise = advertise;
return asBuilder();
}
- public B withAdvertiseUri(final String advertiseUri) {
+ public B setAdvertiseUri(final String advertiseUri) {
this.advertiseUri = advertiseUri;
return asBuilder();
}
- public B withAppend(final boolean append) {
+ public B setAppend(final boolean append) {
this.append = append;
return asBuilder();
}
- public B withFileName(final String fileName) {
+ public B setFileName(final String fileName) {
this.fileName = fileName;
return asBuilder();
}
- public B withCreateOnDemand(final boolean createOnDemand) {
+ public B setCreateOnDemand(final boolean createOnDemand) {
this.createOnDemand = createOnDemand;
return asBuilder();
}
- public B withLocking(final boolean locking) {
+ public B setLocking(final boolean locking) {
this.locking = locking;
return asBuilder();
}
- public B withFilePermissions(final String filePermissions) {
+ public B setFilePermissions(final String filePermissions) {
this.filePermissions = filePermissions;
return asBuilder();
}
- public B withFileOwner(final String fileOwner) {
+ public B setFileOwner(final String fileOwner) {
this.fileOwner = fileOwner;
return asBuilder();
}
- public B withFileGroup(final String fileGroup) {
+ public B setFileGroup(final String fileGroup) {
this.fileGroup = fileGroup;
return asBuilder();
}
}
- private static final int DEFAULT_BUFFER_SIZE = 8192;
-
- /**
- * Create a File Appender.
- * @param fileName The name and path of the file.
- * @param append "True" if the file should be appended to, "false" if it should be overwritten.
- * The default is "true".
- * @param locking "True" if the file should be locked. The default is "false".
- * @param name The name of the Appender.
- * @param immediateFlush "true" if the contents should be flushed on every write, "false" otherwise. The default
- * is "true".
- * @param ignoreExceptions If {@code "true"} (default) exceptions encountered when appending events are logged; otherwise
- * they are propagated to the caller.
- * @param bufferedIo "true" if I/O should be buffered, "false" otherwise. The default is "true".
- * @param bufferSizeStr buffer size for buffered IO (default is 8192).
- * @param layout The layout to use to format the event. If no layout is provided the default PatternLayout
- * will be used.
- * @param filter The filter, if any, to use.
- * @param advertise "true" if the appender configuration should be advertised, "false" otherwise.
- * @param advertiseUri The advertised URI which can be used to retrieve the file contents.
- * @param config The Configuration
- * @return The FileAppender.
- * @deprecated Use {@link #newBuilder()}
- */
- @Deprecated
- public static <B extends Builder<B>> FileAppender createAppender(
- // @formatter:off
- final String fileName,
- final String append,
- final String locking,
- final String name,
- final String immediateFlush,
- final String ignoreExceptions,
- final String bufferedIo,
- final String bufferSizeStr,
- final Layout<? extends Serializable> layout,
- final Filter filter,
- final String advertise,
- final String advertiseUri,
- final Configuration config) {
- return FileAppender.<B>newBuilder()
- .withAdvertise(Boolean.parseBoolean(advertise))
- .withAdvertiseUri(advertiseUri)
- .withAppend(Booleans.parseBoolean(append, true))
- .withBufferedIo(Booleans.parseBoolean(bufferedIo, true))
- .withBufferSize(Integers.parseInt(bufferSizeStr, DEFAULT_BUFFER_SIZE))
- .setConfiguration(config)
- .withFileName(fileName)
- .withFilter(filter)
- .withIgnoreExceptions(Booleans.parseBoolean(ignoreExceptions, true))
- .withImmediateFlush(Booleans.parseBoolean(immediateFlush, true))
- .withLayout(layout)
- .withLocking(Boolean.parseBoolean(locking))
- .withName(name)
- .build();
- // @formatter:on
- }
-
- @PluginBuilderFactory
+ @PluginFactory
public static <B extends Builder<B>> B newBuilder() {
return new Builder<B>().asBuilder();
}
@@ -256,9 +196,9 @@
private FileAppender(final String name, final Layout<? extends Serializable> layout, final Filter filter,
final FileManager manager, final String filename, final boolean ignoreExceptions,
- final boolean immediateFlush, final Advertiser advertiser) {
+ final boolean immediateFlush, final Advertiser advertiser, Property[] properties) {
- super(name, layout, filter, ignoreExceptions, immediateFlush, manager);
+ super(name, layout, filter, ignoreExceptions, immediateFlush, properties, manager);
if (advertiser != null) {
final Map<String, String> 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 2898c7f..306b9a2 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/FileManager.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/FileManager.java
@@ -25,9 +25,11 @@
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.nio.file.FileSystems;
+import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.FileOwnerAttributeView;
+import java.nio.file.attribute.FileTime;
import java.nio.file.attribute.PosixFileAttributeView;
import java.nio.file.attribute.PosixFilePermission;
import java.nio.file.attribute.PosixFilePermissions;
@@ -41,6 +43,7 @@
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.util.Constants;
import org.apache.logging.log4j.core.util.FileUtils;
+import org.apache.logging.log4j.util.StackLocatorUtil;
/**
@@ -61,56 +64,6 @@
private final boolean attributeViewEnabled;
/**
- * @deprecated
- */
- @Deprecated
- protected FileManager(final String fileName, final OutputStream os, final boolean append, final boolean locking,
- final String advertiseURI, final Layout<? extends Serializable> layout, final int bufferSize,
- final boolean writeHeader) {
- this(fileName, os, append, locking, advertiseURI, layout, writeHeader, ByteBuffer.wrap(new byte[bufferSize]));
- }
-
- /**
- * @deprecated
- * @since 2.6
- */
- @Deprecated
- protected FileManager(final String fileName, final OutputStream os, final boolean append, final boolean locking,
- final String advertiseURI, final Layout<? extends Serializable> layout, final boolean writeHeader,
- final ByteBuffer buffer) {
- super(os, fileName, layout, writeHeader, buffer);
- this.isAppend = append;
- this.createOnDemand = false;
- this.isLocking = locking;
- this.advertiseURI = advertiseURI;
- this.bufferSize = buffer.capacity();
- this.filePermissions = null;
- this.fileOwner = null;
- this.fileGroup = null;
- this.attributeViewEnabled = false;
- }
-
- /**
- * @deprecated
- * @since 2.7
- */
- @Deprecated
- protected FileManager(final LoggerContext loggerContext, final String fileName, final OutputStream os, final boolean append, final boolean locking,
- final boolean createOnDemand, final String advertiseURI, final Layout<? extends Serializable> layout,
- final boolean writeHeader, final ByteBuffer buffer) {
- super(loggerContext, os, fileName, createOnDemand, layout, writeHeader, buffer);
- this.isAppend = append;
- this.createOnDemand = createOnDemand;
- this.isLocking = locking;
- this.advertiseURI = advertiseURI;
- this.bufferSize = buffer.capacity();
- this.filePermissions = null;
- this.fileOwner = null;
- this.fileGroup = null;
- this.attributeViewEnabled = false;
- }
-
- /**
* @since 2.9
*/
protected FileManager(final LoggerContext loggerContext, final String fileName, final OutputStream os, final boolean append, final boolean locking,
@@ -164,7 +117,7 @@
* @param bufferSize buffer size for buffered IO
* @param filePermissions File permissions
* @param fileOwner File owner
- * @param fileOwner File group
+ * @param fileGroup File group
* @param configuration The configuration.
* @return A FileManager for the File.
*/
@@ -185,7 +138,17 @@
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);
+ final FileOutputStream fos = new FileOutputStream(file, isAppend);
+ if (file.exists() && file.length() == 0) {
+ try {
+ FileTime now = FileTime.fromMillis(System.currentTimeMillis());
+ Files.setAttribute(file.toPath(), "creationTime", now);
+ } catch (Exception ex) {
+ LOGGER.warn("Unable to set current file tiem for {}", filename);
+ }
+ writeHeader(fos);
+ }
defineAttributeView(Paths.get(filename));
return fos;
}
@@ -416,10 +379,10 @@
final File file = new File(name);
try {
FileUtils.makeParentDirs(file);
- final boolean writeHeader = !data.append || !file.exists();
final int actualSize = data.bufferedIo ? data.bufferSize : Constants.ENCODER_BYTE_BUFFER_SIZE;
final ByteBuffer byteBuffer = ByteBuffer.wrap(new byte[actualSize]);
final FileOutputStream fos = data.createOnDemand ? null : new FileOutputStream(file, data.append);
+ final boolean writeHeader = file.exists() && file.length() == 0;
final FileManager fm = new FileManager(data.getLoggerContext(), name, fos, data.append, data.locking,
data.createOnDemand, data.advertiseURI, data.layout,
data.filePermissions, data.fileOwner, data.fileGroup, writeHeader, byteBuffer);
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/HttpAppender.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/HttpAppender.java
index 59f93c8..4c2dfe6 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/HttpAppender.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/HttpAppender.java
@@ -17,23 +17,24 @@
package org.apache.logging.log4j.core.appender;
+import org.apache.logging.log4j.core.Appender;
+import org.apache.logging.log4j.core.Filter;
+import org.apache.logging.log4j.core.Layout;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.config.Property;
+import org.apache.logging.log4j.core.net.ssl.SslConfiguration;
+import org.apache.logging.log4j.plugins.Node;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginBuilderAttribute;
+import org.apache.logging.log4j.plugins.PluginElement;
+import org.apache.logging.log4j.plugins.PluginFactory;
+import org.apache.logging.log4j.plugins.validation.constraints.Required;
+
import java.io.Serializable;
import java.net.URL;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
-import org.apache.logging.log4j.core.Appender;
-import org.apache.logging.log4j.core.Filter;
-import org.apache.logging.log4j.core.Layout;
-import org.apache.logging.log4j.core.LogEvent;
-import org.apache.logging.log4j.core.config.Node;
-import org.apache.logging.log4j.core.config.Property;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
-import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
-import org.apache.logging.log4j.core.config.plugins.PluginElement;
-import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required;
-import org.apache.logging.log4j.core.net.ssl.SslConfiguration;
/**
* Sends log events over HTTP.
@@ -46,7 +47,7 @@
* @param <B> The type to build
*/
public static class Builder<B extends Builder<B>> extends AbstractAppender.Builder<B>
- implements org.apache.logging.log4j.core.util.Builder<HttpAppender> {
+ implements org.apache.logging.log4j.plugins.util.Builder<HttpAppender> {
@PluginBuilderAttribute
@Required(message = "No URL provided for HttpAppender")
@@ -74,7 +75,7 @@
public HttpAppender build() {
final HttpManager httpManager = new HttpURLConnectionManager(getConfiguration(), getConfiguration().getLoggerContext(),
getName(), url, method, connectTimeoutMillis, readTimeoutMillis, headers, sslConfiguration, verifyHostname);
- return new HttpAppender(getName(), getLayout(), getFilter(), isIgnoreExceptions(), httpManager);
+ return new HttpAppender(getName(), getLayout(), getFilter(), isIgnoreExceptions(), httpManager, getPropertyArray());
}
public URL getUrl() {
@@ -144,7 +145,7 @@
/**
* @return a builder for a HttpAppender.
*/
- @PluginBuilderFactory
+ @PluginFactory
public static <B extends Builder<B>> B newBuilder() {
return new Builder<B>().asBuilder();
}
@@ -152,8 +153,8 @@
private final HttpManager manager;
private HttpAppender(final String name, final Layout<? extends Serializable> layout, final Filter filter,
- final boolean ignoreExceptions, final HttpManager manager) {
- super(name, filter, layout, ignoreExceptions);
+ final boolean ignoreExceptions, final HttpManager manager, final Property[] properties) {
+ super(name, filter, layout, ignoreExceptions, properties);
Objects.requireNonNull(layout, "layout");
this.manager = Objects.requireNonNull(manager, "manager");
}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/MemoryMappedFileAppender.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/MemoryMappedFileAppender.java
index 17f64ee..105d4c8 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/MemoryMappedFileAppender.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/MemoryMappedFileAppender.java
@@ -16,23 +16,22 @@
*/
package org.apache.logging.log4j.core.appender;
-import java.io.Serializable;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.TimeUnit;
-
import org.apache.logging.log4j.core.Appender;
import org.apache.logging.log4j.core.Core;
import org.apache.logging.log4j.core.Filter;
import org.apache.logging.log4j.core.Layout;
import org.apache.logging.log4j.core.LogEvent;
-import org.apache.logging.log4j.core.config.Configuration;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
-import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
+import org.apache.logging.log4j.core.config.Property;
import org.apache.logging.log4j.core.net.Advertiser;
-import org.apache.logging.log4j.core.util.Booleans;
import org.apache.logging.log4j.core.util.Integers;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginBuilderAttribute;
+import org.apache.logging.log4j.plugins.PluginFactory;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
/**
* Memory Mapped File Appender.
@@ -49,7 +48,7 @@
* The type to build
*/
public static class Builder<B extends Builder<B>> extends AbstractOutputStreamAppender.Builder<B>
- implements org.apache.logging.log4j.core.util.Builder<MemoryMappedFileAppender> {
+ implements org.apache.logging.log4j.plugins.util.Builder<MemoryMappedFileAppender> {
@PluginBuilderAttribute("fileName")
private String fileName;
@@ -88,7 +87,7 @@
}
return new MemoryMappedFileAppender(name, layout, getFilter(), manager, fileName, isIgnoreExceptions(), false,
- advertise ? getConfiguration().getAdvertiser() : null);
+ advertise ? getConfiguration().getAdvertiser() : null, getPropertyArray());
}
public B setFileName(final String fileName) {
@@ -128,8 +127,8 @@
private MemoryMappedFileAppender(final String name, final Layout<? extends Serializable> layout,
final Filter filter, final MemoryMappedFileManager manager, final String filename,
- final boolean ignoreExceptions, final boolean immediateFlush, final Advertiser advertiser) {
- super(name, layout, filter, ignoreExceptions, immediateFlush, manager);
+ final boolean ignoreExceptions, final boolean immediateFlush, final Advertiser advertiser, Property[] properties) {
+ super(name, layout, filter, ignoreExceptions, immediateFlush, properties, manager);
if (advertiser != null) {
final Map<String, String> configuration = new HashMap<>(layout.getContentFormat());
configuration.putAll(manager.getContentFormat());
@@ -188,67 +187,7 @@
return getManager().getRegionLength();
}
- /**
- * Create a Memory Mapped File Appender.
- *
- * @param fileName The name and path of the file.
- * @param append "True" if the file should be appended to, "false" if it should be overwritten. The default is
- * "true".
- * @param name The name of the Appender.
- * @param immediateFlush "true" if the contents should be flushed on every write, "false" otherwise. The default is
- * "false".
- * @param regionLengthStr The buffer size, defaults to {@value MemoryMappedFileManager#DEFAULT_REGION_LENGTH}.
- * @param ignore If {@code "true"} (default) exceptions encountered when appending events are logged; otherwise they
- * are propagated to the caller.
- * @param layout The layout to use to format the event. If no layout is provided the default PatternLayout will be
- * used.
- * @param filter The filter, if any, to use.
- * @param advertise "true" if the appender configuration should be advertised, "false" otherwise.
- * @param advertiseURI The advertised URI which can be used to retrieve the file contents.
- * @param config The Configuration.
- * @return The FileAppender.
- * @deprecated Use {@link #newBuilder()}.
- */
- @Deprecated
- public static <B extends Builder<B>> MemoryMappedFileAppender createAppender(
- // @formatter:off
- final String fileName, //
- final String append, //
- final String name, //
- final String immediateFlush, //
- final String regionLengthStr, //
- final String ignore, //
- final Layout<? extends Serializable> layout, //
- final Filter filter, //
- final String advertise, //
- final String advertiseURI, //
- final Configuration config) {
- // @formatter:on
-
- final boolean isAppend = Booleans.parseBoolean(append, true);
- final boolean isImmediateFlush = Booleans.parseBoolean(immediateFlush, false);
- final boolean ignoreExceptions = Booleans.parseBoolean(ignore, true);
- final boolean isAdvertise = Boolean.parseBoolean(advertise);
- final int regionLength = Integers.parseInt(regionLengthStr, MemoryMappedFileManager.DEFAULT_REGION_LENGTH);
-
- // @formatter:off
- return MemoryMappedFileAppender.<B>newBuilder()
- .setAdvertise(isAdvertise)
- .setAdvertiseURI(advertiseURI)
- .setAppend(isAppend)
- .setConfiguration(config)
- .setFileName(fileName)
- .withFilter(filter)
- .withIgnoreExceptions(ignoreExceptions)
- .withImmediateFlush(isImmediateFlush)
- .withLayout(layout)
- .withName(name)
- .setRegionLength(regionLength)
- .build();
- // @formatter:on
- }
-
- @PluginBuilderFactory
+ @PluginFactory
public static <B extends Builder<B>> B newBuilder() {
return new Builder<B>().asBuilder();
}
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 3978f05..f84e04c 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
@@ -19,12 +19,14 @@
import org.apache.logging.log4j.core.Appender;
import org.apache.logging.log4j.core.Core;
import org.apache.logging.log4j.core.LogEvent;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
-import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+import org.apache.logging.log4j.core.config.Property;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginAttribute;
+import org.apache.logging.log4j.plugins.PluginFactory;
/**
- * An Appender that ignores log events. Use for compatibility with version 1.2.
+ * An Appender that ignores log events. Use for compatibility with version 1.2 and handy for composing a
+ * {@link ScriptAppenderSelector}.
*/
@Plugin(name = NullAppender.PLUGIN_NAME, category = Core.CATEGORY_NAME, elementType = Appender.ELEMENT_TYPE, printObject = true)
public class NullAppender extends AbstractAppender {
@@ -32,12 +34,13 @@
public static final String PLUGIN_NAME = "Null";
@PluginFactory
- public static NullAppender createAppender(@PluginAttribute("name") final String name) {
+ public static NullAppender createAppender(
+ @PluginAttribute(defaultString = "null") final String name) {
return new NullAppender(name);
}
private NullAppender(final String name) {
- super(name, null, null);
+ super(name, null, null, true, Property.EMPTY_ARRAY);
// Do nothing
}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/OutputStreamAppender.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/OutputStreamAppender.java
index ddca626..a1763e9 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/OutputStreamAppender.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/OutputStreamAppender.java
@@ -1,191 +1,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.appender;
-
-import java.io.OutputStream;
-import java.io.Serializable;
-
-import org.apache.logging.log4j.core.Appender;
-import org.apache.logging.log4j.core.Core;
-import org.apache.logging.log4j.core.Filter;
-import org.apache.logging.log4j.core.Layout;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
-import org.apache.logging.log4j.core.config.plugins.PluginFactory;
-import org.apache.logging.log4j.core.layout.PatternLayout;
-import org.apache.logging.log4j.core.util.CloseShieldOutputStream;
-
-/**
- * Appends log events to a given output stream using a layout.
- * <p>
- * Character encoding is handled within the Layout.
- * </p>
- */
-@Plugin(name = "OutputStream", category = Core.CATEGORY_NAME, elementType = Appender.ELEMENT_TYPE, printObject = true)
-public final class OutputStreamAppender extends AbstractOutputStreamAppender<OutputStreamManager> {
-
- /**
- * Builds OutputStreamAppender instances.
- */
- public static class Builder implements org.apache.logging.log4j.core.util.Builder<OutputStreamAppender> {
-
- private Filter filter;
-
- private boolean follow = false;
-
- private boolean ignoreExceptions = true;
-
- private Layout<? extends Serializable> layout = PatternLayout.createDefaultLayout();
-
- private String name;
-
- private OutputStream target;
-
- @Override
- public OutputStreamAppender build() {
- return new OutputStreamAppender(name, layout, filter, getManager(target, follow, layout), ignoreExceptions);
- }
-
- public Builder setFilter(final Filter aFilter) {
- this.filter = aFilter;
- return this;
- }
-
- public Builder setFollow(final boolean shouldFollow) {
- this.follow = shouldFollow;
- return this;
- }
-
- public Builder setIgnoreExceptions(final boolean shouldIgnoreExceptions) {
- this.ignoreExceptions = shouldIgnoreExceptions;
- return this;
- }
-
- public Builder setLayout(final Layout<? extends Serializable> aLayout) {
- this.layout = aLayout;
- return this;
- }
-
- public Builder setName(final String aName) {
- this.name = aName;
- return this;
- }
-
- public Builder setTarget(final OutputStream aTarget) {
- this.target = aTarget;
- return this;
- }
- }
- /**
- * Holds data to pass to factory method.
- */
- private static class FactoryData {
- private final Layout<? extends Serializable> layout;
- private final String name;
- private final OutputStream os;
-
- /**
- * Builds instances.
- *
- * @param os
- * The OutputStream.
- * @param type
- * The name of the target.
- * @param layout
- * A Serializable layout
- */
- public FactoryData(final OutputStream os, final String type, final Layout<? extends Serializable> layout) {
- this.os = os;
- this.name = type;
- this.layout = layout;
- }
- }
-
- /**
- * Creates the manager.
- */
- private static class OutputStreamManagerFactory implements ManagerFactory<OutputStreamManager, FactoryData> {
-
- /**
- * Creates an OutputStreamManager.
- *
- * @param name
- * The name of the entity to manage.
- * @param data
- * The data required to create the entity.
- * @return The OutputStreamManager
- */
- @Override
- public OutputStreamManager createManager(final String name, final FactoryData data) {
- return new OutputStreamManager(data.os, data.name, data.layout, true);
- }
- }
-
- private static OutputStreamManagerFactory factory = new OutputStreamManagerFactory();
-
- /**
- * Creates an OutputStream Appender.
- *
- * @param layout
- * The layout to use or null to get the default layout.
- * @param filter
- * The Filter or null.
- * @param target
- * an output stream.
- * @param follow
- * If true will follow changes to the underlying output stream.
- * Use false as the default.
- * @param name
- * The name of the Appender (required).
- * @param ignore
- * If {@code "true"} (default) exceptions encountered when
- * appending events are logged; otherwise they are propagated to
- * the caller. Use true as the default.
- * @return The ConsoleAppender.
- */
- @PluginFactory
- public static OutputStreamAppender createAppender(Layout<? extends Serializable> layout, final Filter filter,
- final OutputStream target, final String name, final boolean follow, final boolean ignore) {
- if (name == null) {
- LOGGER.error("No name provided for OutputStreamAppender");
- return null;
- }
- if (layout == null) {
- layout = PatternLayout.createDefaultLayout();
- }
- return new OutputStreamAppender(name, layout, filter, getManager(target, follow, layout), ignore);
- }
-
- private static OutputStreamManager getManager(final OutputStream target, final boolean follow,
- final Layout<? extends Serializable> layout) {
- final OutputStream os = new CloseShieldOutputStream(target);
- final String managerName = target.getClass().getName() + "@" + Integer.toHexString(target.hashCode()) + '.'
- + follow;
- return OutputStreamManager.getManager(managerName, new FactoryData(os, managerName, layout), factory);
- }
-
- @PluginBuilderFactory
- public static Builder newBuilder() {
- return new Builder();
- }
-
- private OutputStreamAppender(final String name, final Layout<? extends Serializable> layout, final Filter filter,
- final OutputStreamManager manager, final boolean ignoreExceptions) {
- super(name, layout, filter, ignoreExceptions, true, manager);
- }
-
-}
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.logging.log4j.core.appender;
+
+import org.apache.logging.log4j.core.Appender;
+import org.apache.logging.log4j.core.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.layout.PatternLayout;
+import org.apache.logging.log4j.core.util.CloseShieldOutputStream;
+import org.apache.logging.log4j.core.util.NullOutputStream;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginFactory;
+
+import java.io.OutputStream;
+import java.io.Serializable;
+
+/**
+ * Appends log events to a given output stream using a layout.
+ * <p>
+ * Character encoding is handled within the Layout.
+ * </p>
+ */
+@Plugin(name = "OutputStream", category = Core.CATEGORY_NAME, elementType = Appender.ELEMENT_TYPE, printObject = true)
+public final class OutputStreamAppender extends AbstractOutputStreamAppender<OutputStreamManager> {
+
+ /**
+ * Builds OutputStreamAppender instances.
+ *
+ * @param <B>
+ * The type to build.
+ */
+ public static class Builder<B extends Builder<B>> extends AbstractOutputStreamAppender.Builder<B>
+ implements org.apache.logging.log4j.plugins.util.Builder<OutputStreamAppender> {
+
+ private boolean follow = false;
+
+ private final Layout<? extends Serializable> layout = PatternLayout.createDefaultLayout();
+
+ private OutputStream target;
+
+ @Override
+ public OutputStreamAppender build() {
+
+ return new OutputStreamAppender(getName(), layout, getFilter(), getManager(target, follow, layout), isIgnoreExceptions(), getPropertyArray());
+ }
+
+ public B setFollow(final boolean shouldFollow) {
+ this.follow = shouldFollow;
+ return asBuilder();
+ }
+
+ public B setTarget(final OutputStream aTarget) {
+ this.target = aTarget;
+ return asBuilder();
+ }
+ }
+ /**
+ * Holds data to pass to factory method.
+ */
+ private static class FactoryData {
+ private final Layout<? extends Serializable> layout;
+ private final String name;
+ private final OutputStream os;
+
+ /**
+ * Builds instances.
+ *
+ * @param os
+ * The OutputStream.
+ * @param type
+ * The name of the target.
+ * @param layout
+ * A Serializable layout
+ */
+ public FactoryData(final OutputStream os, final String type, final Layout<? extends Serializable> layout) {
+ this.os = os;
+ this.name = type;
+ this.layout = layout;
+ }
+ }
+
+ /**
+ * Creates the manager.
+ */
+ private static class OutputStreamManagerFactory implements ManagerFactory<OutputStreamManager, FactoryData> {
+
+ /**
+ * Creates an OutputStreamManager.
+ *
+ * @param name
+ * The name of the entity to manage.
+ * @param data
+ * The data required to create the entity.
+ * @return The OutputStreamManager
+ */
+ @Override
+ public OutputStreamManager createManager(final String name, final FactoryData data) {
+ return new OutputStreamManager(data.os, data.name, data.layout, true);
+ }
+ }
+
+ private static final OutputStreamManagerFactory factory = new OutputStreamManagerFactory();
+
+ /**
+ * Creates an OutputStream Appender.
+ *
+ * @param layout
+ * The layout to use or null to get the default layout.
+ * @param filter
+ * The Filter or null.
+ * @param target
+ * an output stream.
+ * @param follow
+ * If true will follow changes to the underlying output stream.
+ * Use false as the default.
+ * @param name
+ * The name of the Appender (required).
+ * @param ignore
+ * If {@code "true"} (default) exceptions encountered when
+ * appending events are logged; otherwise they are propagated to
+ * the caller. Use true as the default.
+ * @return The ConsoleAppender.
+ */
+ @PluginFactory
+ public static OutputStreamAppender createAppender(Layout<? extends Serializable> layout, final Filter filter,
+ final OutputStream target, final String name, final boolean follow, final boolean ignore) {
+ if (name == null) {
+ LOGGER.error("No name provided for OutputStreamAppender");
+ return null;
+ }
+ if (layout == null) {
+ layout = PatternLayout.createDefaultLayout();
+ }
+ return new OutputStreamAppender(name, layout, filter, getManager(target, follow, layout), ignore, null);
+ }
+
+ private static OutputStreamManager getManager(final OutputStream target, final boolean follow,
+ final Layout<? extends Serializable> layout) {
+ final OutputStream os = target == null ? NullOutputStream.getInstance() : new CloseShieldOutputStream(target);
+ final OutputStream targetRef = target == null ? os : target;
+ final String managerName = targetRef.getClass().getName() + "@" + Integer.toHexString(targetRef.hashCode())
+ + '.' + follow;
+ return OutputStreamManager.getManager(managerName, new FactoryData(os, managerName, layout), factory);
+ }
+
+ @PluginFactory
+ public static <B extends Builder<B>> B newBuilder() {
+ return new Builder<B>().asBuilder();
+ }
+
+ private OutputStreamAppender(final String name, final Layout<? extends Serializable> layout, final Filter filter,
+ final OutputStreamManager manager, final boolean ignoreExceptions, Property[] properties) {
+ super(name, layout, filter, ignoreExceptions, true, null, manager);
+ }
+
+}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/OutputStreamManager.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/OutputStreamManager.java
index 40f90db..8d5b43f 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
@@ -29,6 +29,7 @@
import org.apache.logging.log4j.core.layout.ByteBufferDestination;
import org.apache.logging.log4j.core.layout.ByteBufferDestinationHelper;
import org.apache.logging.log4j.core.util.Constants;
+import org.apache.logging.log4j.util.StackLocatorUtil;
/**
* Manages an OutputStream so that it can be shared by multiple Appenders and will
@@ -60,17 +61,10 @@
super(null, streamName);
this.outputStream = os;
this.layout = layout;
- if (writeHeader && layout != null) {
- final byte[] header = layout.getHeader();
- if (header != null) {
- try {
- getOutputStream().write(header, 0, header.length);
- } catch (final IOException e) {
- logError("Unable to write header", e);
- }
- }
- }
this.byteBuffer = Objects.requireNonNull(byteBuffer, "byteBuffer");
+ if (writeHeader) {
+ writeHeader(os);
+ }
}
/**
@@ -88,15 +82,8 @@
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);
}
}
@@ -136,6 +123,19 @@
return closeOutputStream();
}
+ protected void writeHeader(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.
*/
@@ -169,17 +169,7 @@
}
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;
}
/**
@@ -311,6 +301,7 @@
}
try {
stream.close();
+ LOGGER.debug("OutputStream closed");
} catch (final IOException ex) {
logError("Unable to close stream", ex);
return false;
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 d7ec920..6e63f71 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/RandomAccessFileAppender.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/RandomAccessFileAppender.java
@@ -16,23 +16,20 @@
*/
package org.apache.logging.log4j.core.appender;
-import java.io.Serializable;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.TimeUnit;
-
import org.apache.logging.log4j.core.Appender;
import org.apache.logging.log4j.core.Core;
import org.apache.logging.log4j.core.Filter;
import org.apache.logging.log4j.core.Layout;
import org.apache.logging.log4j.core.LogEvent;
-import org.apache.logging.log4j.core.config.Configuration;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
-import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
import org.apache.logging.log4j.core.net.Advertiser;
-import org.apache.logging.log4j.core.util.Booleans;
-import org.apache.logging.log4j.core.util.Integers;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginBuilderAttribute;
+import org.apache.logging.log4j.plugins.PluginFactory;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
/**
* File Appender.
@@ -47,7 +44,7 @@
* The type to build
*/
public static class Builder<B extends Builder<B>> extends AbstractOutputStreamAppender.Builder<B>
- implements org.apache.logging.log4j.core.util.Builder<RandomAccessFileAppender> {
+ implements org.apache.logging.log4j.plugins.util.Builder<RandomAccessFileAppender> {
@PluginBuilderAttribute("fileName")
private String fileName;
@@ -115,7 +112,7 @@
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);
+ super(name, layout, filter, ignoreExceptions, immediateFlush, null, manager);
if (advertiser != null) {
final Map<String, String> configuration = new HashMap<>(
layout.getContentFormat());
@@ -176,71 +173,11 @@
return getManager().getBufferSize();
}
- // difference from standard File Appender:
- // locking is not supported and buffering cannot be switched off
- /**
- * Create a File Appender.
- *
- * @param fileName The name and path of the file.
- * @param append "True" if the file should be appended to, "false" if it
- * should be overwritten. The default is "true".
- * @param name The name of the Appender.
- * @param immediateFlush "true" if the contents should be flushed on every
- * write, "false" otherwise. The default is "true".
- * @param bufferSizeStr The buffer size, defaults to {@value RandomAccessFileManager#DEFAULT_BUFFER_SIZE}.
- * @param ignore If {@code "true"} (default) exceptions encountered when appending events are logged; otherwise
- * they are propagated to the caller.
- * @param layout The layout to use to format the event. If no layout is
- * provided the default PatternLayout will be used.
- * @param filter The filter, if any, to use.
- * @param advertise "true" if the appender configuration should be
- * advertised, "false" otherwise.
- * @param advertiseURI The advertised URI which can be used to retrieve the
- * file contents.
- * @param configuration The Configuration.
- * @return The FileAppender.
- * @deprecated Use {@link #newBuilder()}.
- */
- @Deprecated
- public static <B extends Builder<B>> RandomAccessFileAppender createAppender(
- final String fileName,
- final String append,
- final String name,
- final String immediateFlush,
- final String bufferSizeStr,
- final String ignore,
- final Layout<? extends Serializable> layout,
- final Filter filter,
- final String advertise,
- final String advertiseURI,
- final Configuration configuration) {
-
- final boolean isAppend = Booleans.parseBoolean(append, true);
- final boolean isFlush = Booleans.parseBoolean(immediateFlush, true);
- final boolean ignoreExceptions = Booleans.parseBoolean(ignore, true);
- final boolean isAdvertise = Boolean.parseBoolean(advertise);
- final int bufferSize = Integers.parseInt(bufferSizeStr, RandomAccessFileManager.DEFAULT_BUFFER_SIZE);
-
- return RandomAccessFileAppender.<B>newBuilder()
- .setAdvertise(isAdvertise)
- .setAdvertiseURI(advertiseURI)
- .setAppend(isAppend)
- .withBufferSize(bufferSize)
- .setConfiguration(configuration)
- .setFileName(fileName)
- .withFilter(filter)
- .withIgnoreExceptions(ignoreExceptions)
- .withImmediateFlush(isFlush)
- .withLayout(layout)
- .withName(name)
- .build();
- }
-
/**
* Creates a builder for a RandomAccessFileAppender.
* @return a builder for a RandomAccessFileAppender.
*/
- @PluginBuilderFactory
+ @PluginFactory
public static <B extends Builder<B>> B newBuilder() {
return new Builder<B>().asBuilder();
}
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 def63a4..5849d79 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/RollingFileAppender.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/RollingFileAppender.java
@@ -16,12 +16,6 @@
*/
package org.apache.logging.log4j.core.appender;
-import java.io.Serializable;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.TimeUnit;
-import java.util.zip.Deflater;
-
import org.apache.logging.log4j.core.Appender;
import org.apache.logging.log4j.core.Core;
import org.apache.logging.log4j.core.Filter;
@@ -33,15 +27,18 @@
import org.apache.logging.log4j.core.appender.rolling.RollingFileManager;
import org.apache.logging.log4j.core.appender.rolling.RolloverStrategy;
import org.apache.logging.log4j.core.appender.rolling.TriggeringPolicy;
-import org.apache.logging.log4j.core.config.Configuration;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
-import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
-import org.apache.logging.log4j.core.config.plugins.PluginElement;
-import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required;
import org.apache.logging.log4j.core.net.Advertiser;
-import org.apache.logging.log4j.core.util.Booleans;
-import org.apache.logging.log4j.core.util.Integers;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginBuilderAttribute;
+import org.apache.logging.log4j.plugins.PluginElement;
+import org.apache.logging.log4j.plugins.PluginFactory;
+import org.apache.logging.log4j.plugins.validation.constraints.Required;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import java.util.zip.Deflater;
/**
* An appender that writes to files and can roll over at intervals.
@@ -59,7 +56,7 @@
* @since 2.7
*/
public static class Builder<B extends Builder<B>> extends AbstractOutputStreamAppender.Builder<B>
- implements org.apache.logging.log4j.core.util.Builder<RollingFileAppender> {
+ implements org.apache.logging.log4j.plugins.util.Builder<RollingFileAppender> {
@PluginBuilderAttribute
private String fileName;
@@ -127,17 +124,17 @@
if (strategy == null) {
if (fileName != null) {
strategy = DefaultRolloverStrategy.newBuilder()
- .withCompressionLevelStr(String.valueOf(Deflater.DEFAULT_COMPRESSION))
- .withConfig(getConfiguration())
+ .setCompressionLevelStr(String.valueOf(Deflater.DEFAULT_COMPRESSION))
+ .setConfig(getConfiguration())
.build();
} else {
strategy = DirectWriteRolloverStrategy.newBuilder()
- .withCompressionLevelStr(String.valueOf(Deflater.DEFAULT_COMPRESSION))
- .withConfig(getConfiguration())
+ .setCompressionLevelStr(String.valueOf(Deflater.DEFAULT_COMPRESSION))
+ .setConfig(getConfiguration())
.build();
}
} else if (fileName == null && !(strategy instanceof DirectFileRolloverStrategy)) {
- LOGGER.error("RollingFileAppender '{}': When no file name is provided a DirectFilenameRolloverStrategy must be configured");
+ LOGGER.error("RollingFileAppender '{}': When no file name is provided a {} must be configured", getName(), DirectFileRolloverStrategy.class.getSimpleName());
return null;
}
@@ -191,32 +188,32 @@
return fileGroup;
}
- public B withAdvertise(final boolean advertise) {
+ public B setAdvertise(final boolean advertise) {
this.advertise = advertise;
return asBuilder();
}
- public B withAdvertiseUri(final String advertiseUri) {
+ public B setAdvertiseUri(final String advertiseUri) {
this.advertiseUri = advertiseUri;
return asBuilder();
}
- public B withAppend(final boolean append) {
+ public B setAppend(final boolean append) {
this.append = append;
return asBuilder();
}
- public B withFileName(final String fileName) {
+ public B setFileName(final String fileName) {
this.fileName = fileName;
return asBuilder();
}
- public B withCreateOnDemand(final boolean createOnDemand) {
+ public B setCreateOnDemand(final boolean createOnDemand) {
this.createOnDemand = createOnDemand;
return asBuilder();
}
- public B withLocking(final boolean locking) {
+ public B setLocking(final boolean locking) {
this.locking = locking;
return asBuilder();
}
@@ -233,40 +230,38 @@
return strategy;
}
- public B withFilePattern(final String filePattern) {
+ public B setFilePattern(final String filePattern) {
this.filePattern = filePattern;
return asBuilder();
}
- public B withPolicy(final TriggeringPolicy policy) {
+ public B setPolicy(final TriggeringPolicy policy) {
this.policy = policy;
return asBuilder();
}
- public B withStrategy(final RolloverStrategy strategy) {
+ public B setStrategy(final RolloverStrategy strategy) {
this.strategy = strategy;
return asBuilder();
}
- public B withFilePermissions(final String filePermissions) {
+ public B setFilePermissions(final String filePermissions) {
this.filePermissions = filePermissions;
return asBuilder();
}
- public B withFileOwner(final String fileOwner) {
+ public B setFileOwner(final String fileOwner) {
this.fileOwner = fileOwner;
return asBuilder();
}
- public B withFileGroup(final String fileGroup) {
+ public B setFileGroup(final String fileGroup) {
this.fileGroup = fileGroup;
return asBuilder();
}
}
- private static final int DEFAULT_BUFFER_SIZE = 8192;
-
private final String fileName;
private final String filePattern;
private Object advertisement;
@@ -275,7 +270,7 @@
private RollingFileAppender(final String name, final Layout<? extends Serializable> layout, final Filter filter,
final RollingFileManager manager, final String fileName, final String filePattern,
final boolean ignoreExceptions, final boolean immediateFlush, final Advertiser advertiser) {
- super(name, layout, filter, ignoreExceptions, immediateFlush, manager);
+ super(name, layout, filter, ignoreExceptions, immediateFlush, null, manager);
if (advertiser != null) {
final Map<String, String> configuration = new HashMap<>(layout.getContentFormat());
configuration.put("contentType", layout.getContentType());
@@ -335,77 +330,12 @@
}
/**
- * Creates a RollingFileAppender.
- * @param fileName The name of the file that is actively written to. (required).
- * @param filePattern The pattern of the file name to use on rollover. (required).
- * @param append If true, events are appended to the file. If false, the file
- * is overwritten when opened. Defaults to "true"
- * @param name The name of the Appender (required).
- * @param bufferedIO When true, I/O will be buffered. Defaults to "true".
- * @param bufferSizeStr buffer size for buffered IO (default is 8192).
- * @param immediateFlush When true, events are immediately flushed. Defaults to "true".
- * @param policy The triggering policy. (required).
- * @param strategy The rollover strategy. Defaults to DefaultRolloverStrategy.
- * @param layout The layout to use (defaults to the default PatternLayout).
- * @param filter The Filter or null.
- * @param ignore If {@code "true"} (default) exceptions encountered when appending events are logged; otherwise
- * they are propagated to the caller.
- * @param advertise "true" if the appender configuration should be advertised, "false" otherwise.
- * @param advertiseUri The advertised URI which can be used to retrieve the file contents.
- * @param config The Configuration.
- * @return A RollingFileAppender.
- * @deprecated Use {@link #newBuilder()}.
- */
- @Deprecated
- public static <B extends Builder<B>> RollingFileAppender createAppender(
- // @formatter:off
- final String fileName,
- final String filePattern,
- final String append,
- final String name,
- final String bufferedIO,
- final String bufferSizeStr,
- final String immediateFlush,
- final TriggeringPolicy policy,
- final RolloverStrategy strategy,
- final Layout<? extends Serializable> layout,
- final Filter filter,
- final String ignore,
- final String advertise,
- final String advertiseUri,
- final Configuration config) {
- // @formatter:on
- final int bufferSize = Integers.parseInt(bufferSizeStr, DEFAULT_BUFFER_SIZE);
- // @formatter:off
- return RollingFileAppender.<B>newBuilder()
- .withAdvertise(Boolean.parseBoolean(advertise))
- .withAdvertiseUri(advertiseUri)
- .withAppend(Booleans.parseBoolean(append, true))
- .withBufferedIo(Booleans.parseBoolean(bufferedIO, true))
- .withBufferSize(bufferSize)
- .setConfiguration(config)
- .withFileName(fileName)
- .withFilePattern(filePattern)
- .withFilter(filter)
- .withIgnoreExceptions(Booleans.parseBoolean(ignore, true))
- .withImmediateFlush(Booleans.parseBoolean(immediateFlush, true))
- .withLayout(layout)
- .withCreateOnDemand(false)
- .withLocking(false)
- .withName(name)
- .withPolicy(policy)
- .withStrategy(strategy)
- .build();
- // @formatter:on
- }
-
- /**
* Creates a new Builder.
*
* @return a new Builder.
* @since 2.7
*/
- @PluginBuilderFactory
+ @PluginFactory
public static <B extends Builder<B>> B newBuilder() {
return new Builder<B>().asBuilder();
}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/RollingRandomAccessFileAppender.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/RollingRandomAccessFileAppender.java
index f43ccd0..e24bc9f 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/RollingRandomAccessFileAppender.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/RollingRandomAccessFileAppender.java
@@ -16,12 +16,6 @@
*/
package org.apache.logging.log4j.core.appender;
-import java.io.Serializable;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.TimeUnit;
-import java.util.zip.Deflater;
-
import org.apache.logging.log4j.core.Appender;
import org.apache.logging.log4j.core.Core;
import org.apache.logging.log4j.core.Filter;
@@ -33,14 +27,17 @@
import org.apache.logging.log4j.core.appender.rolling.RollingRandomAccessFileManager;
import org.apache.logging.log4j.core.appender.rolling.RolloverStrategy;
import org.apache.logging.log4j.core.appender.rolling.TriggeringPolicy;
-import org.apache.logging.log4j.core.config.Configuration;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
-import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
-import org.apache.logging.log4j.core.config.plugins.PluginElement;
import org.apache.logging.log4j.core.net.Advertiser;
-import org.apache.logging.log4j.core.util.Booleans;
-import org.apache.logging.log4j.core.util.Integers;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginBuilderAttribute;
+import org.apache.logging.log4j.plugins.PluginElement;
+import org.apache.logging.log4j.plugins.PluginFactory;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import java.util.zip.Deflater;
/**
* An appender that writes to random access files and can roll over at
@@ -50,13 +47,13 @@
public final class RollingRandomAccessFileAppender extends AbstractOutputStreamAppender<RollingRandomAccessFileManager> {
public static class Builder<B extends Builder<B>> extends AbstractOutputStreamAppender.Builder<B>
- implements org.apache.logging.log4j.core.util.Builder<RollingRandomAccessFileAppender> {
+ implements org.apache.logging.log4j.plugins.util.Builder<RollingRandomAccessFileAppender> {
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 +97,17 @@
if (strategy == null) {
if (fileName != null) {
strategy = DefaultRolloverStrategy.newBuilder()
- .withCompressionLevelStr(String.valueOf(Deflater.DEFAULT_COMPRESSION))
- .withConfig(getConfiguration())
+ .setCompressionLevelStr(String.valueOf(Deflater.DEFAULT_COMPRESSION))
+ .setConfig(getConfiguration())
.build();
} else {
strategy = DirectWriteRolloverStrategy.newBuilder()
- .withCompressionLevelStr(String.valueOf(Deflater.DEFAULT_COMPRESSION))
- .withConfig(getConfiguration())
+ .setCompressionLevelStr(String.valueOf(Deflater.DEFAULT_COMPRESSION))
+ .setConfig(getConfiguration())
.build();
}
} else if (fileName == null && !(strategy instanceof DirectFileRolloverStrategy)) {
- LOGGER.error("RollingFileAppender '{}': When no file name is provided a DirectFilenameRolloverStrategy must be configured");
+ LOGGER.error("RollingFileAppender '{}': When no file name is provided a DirectFileRolloverStrategy must be configured", name);
return null;
}
@@ -142,52 +139,52 @@
isIgnoreExceptions(), immediateFlush, bufferSize, advertise ? getConfiguration().getAdvertiser() : null);
}
- public B withFileName(final String fileName) {
+ public B setFileName(final String fileName) {
this.fileName = fileName;
return asBuilder();
}
- public B withFilePattern(final String filePattern) {
+ public B setFilePattern(final String filePattern) {
this.filePattern = filePattern;
return asBuilder();
}
- public B withAppend(final boolean append) {
+ public B setAppend(final boolean append) {
this.append = append;
return asBuilder();
}
- public B withPolicy(final TriggeringPolicy policy) {
+ public B setPolicy(final TriggeringPolicy policy) {
this.policy = policy;
return asBuilder();
}
- public B withStrategy(final RolloverStrategy strategy) {
+ public B setStrategy(final RolloverStrategy strategy) {
this.strategy = strategy;
return asBuilder();
}
- public B withAdvertise(final boolean advertise) {
+ public B setAdvertise(final boolean advertise) {
this.advertise = advertise;
return asBuilder();
}
- public B withAdvertiseURI(final String advertiseURI) {
+ public B setAdvertiseURI(final String advertiseURI) {
this.advertiseURI = advertiseURI;
return asBuilder();
}
- public B withFilePermissions(final String filePermissions) {
+ public B setFilePermissions(final String filePermissions) {
this.filePermissions = filePermissions;
return asBuilder();
}
- public B withFileOwner(final String fileOwner) {
+ public B setFileOwner(final String fileOwner) {
this.fileOwner = fileOwner;
return asBuilder();
}
- public B withFileGroup(final String fileGroup) {
+ public B setFileGroup(final String fileGroup) {
this.fileGroup = fileGroup;
return asBuilder();
}
@@ -203,7 +200,7 @@
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);
+ super(name, layout, filter, ignoreExceptions, immediateFlush, null, manager);
if (advertiser != null) {
final Map<String, String> configuration = new HashMap<>(layout.getContentFormat());
configuration.put("contentType", layout.getContentType());
@@ -276,76 +273,7 @@
return getManager().getBufferSize();
}
- /**
- * Create a RollingRandomAccessFileAppender.
- *
- * @param fileName The name of the file that is actively written to.
- * (required).
- * @param filePattern The pattern of the file name to use on rollover.
- * (required).
- * @param append If true, events are appended to the file. If false, the
- * file is overwritten when opened. Defaults to "true"
- * @param name The name of the Appender (required).
- * @param immediateFlush When true, events are immediately flushed. Defaults
- * to "true".
- * @param bufferSizeStr The buffer size, defaults to {@value RollingRandomAccessFileManager#DEFAULT_BUFFER_SIZE}.
- * @param policy The triggering policy. (required).
- * @param strategy The rollover strategy. Defaults to
- * DefaultRolloverStrategy.
- * @param layout The layout to use (defaults to the default PatternLayout).
- * @param filter The Filter or null.
- * @param ignoreExceptions If {@code "true"} (default) exceptions encountered when appending events are logged; otherwise
- * they are propagated to the caller.
- * @param advertise "true" if the appender configuration should be
- * advertised, "false" otherwise.
- * @param advertiseURI The advertised URI which can be used to retrieve the
- * file contents.
- * @param configuration The Configuration.
- * @return A RollingRandomAccessFileAppender.
- * @deprecated Use {@link #newBuilder()}.
- */
- @Deprecated
- public static <B extends Builder<B>> RollingRandomAccessFileAppender createAppender(
- final String fileName,
- final String filePattern,
- final String append,
- final String name,
- final String immediateFlush,
- final String bufferSizeStr,
- final TriggeringPolicy policy,
- final RolloverStrategy strategy,
- final Layout<? extends Serializable> layout,
- final Filter filter,
- final String ignoreExceptions,
- final String advertise,
- final String advertiseURI,
- final Configuration configuration) {
-
- final boolean isAppend = Booleans.parseBoolean(append, true);
- final boolean isIgnoreExceptions = Booleans.parseBoolean(ignoreExceptions, true);
- final boolean isImmediateFlush = Booleans.parseBoolean(immediateFlush, true);
- final boolean isAdvertise = Boolean.parseBoolean(advertise);
- final int bufferSize = Integers.parseInt(bufferSizeStr, RollingRandomAccessFileManager.DEFAULT_BUFFER_SIZE);
-
- return RollingRandomAccessFileAppender.<B>newBuilder()
- .withAdvertise(isAdvertise)
- .withAdvertiseURI(advertiseURI)
- .withAppend(isAppend)
- .withBufferSize(bufferSize)
- .setConfiguration(configuration)
- .withFileName(fileName)
- .withFilePattern(filePattern)
- .withFilter(filter)
- .withIgnoreExceptions(isIgnoreExceptions)
- .withImmediateFlush(isImmediateFlush)
- .withLayout(layout)
- .withName(name)
- .withPolicy(policy)
- .withStrategy(strategy)
- .build();
- }
-
- @PluginBuilderFactory
+ @PluginFactory
public static <B extends Builder<B>> B newBuilder() {
return new Builder<B>().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 ab9dfb4..cfcf580 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
@@ -16,25 +16,25 @@
*/
package org.apache.logging.log4j.core.appender;
-import java.io.Serializable;
-import java.util.Objects;
-
-import javax.script.Bindings;
-
import org.apache.logging.log4j.core.Appender;
import org.apache.logging.log4j.core.Core;
import org.apache.logging.log4j.core.Filter;
import org.apache.logging.log4j.core.Layout;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.config.Configuration;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
-import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
+import org.apache.logging.log4j.core.config.Property;
import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
-import org.apache.logging.log4j.core.config.plugins.PluginElement;
-import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required;
import org.apache.logging.log4j.core.script.AbstractScript;
import org.apache.logging.log4j.core.script.ScriptManager;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginBuilderAttribute;
+import org.apache.logging.log4j.plugins.PluginElement;
+import org.apache.logging.log4j.plugins.PluginFactory;
+import org.apache.logging.log4j.plugins.validation.constraints.Required;
+
+import javax.script.Bindings;
+import java.io.Serializable;
+import java.util.Objects;
@Plugin(name = "ScriptAppenderSelector", category = Core.CATEGORY_NAME, elementType = Appender.ELEMENT_TYPE, printObject = true)
public class ScriptAppenderSelector extends AbstractAppender {
@@ -42,7 +42,7 @@
/**
* Builds an appender.
*/
- public static final class Builder implements org.apache.logging.log4j.core.util.Builder<Appender> {
+ public static final class Builder implements org.apache.logging.log4j.plugins.util.Builder<Appender> {
@PluginElement("AppenderSet")
@Required
@@ -81,10 +81,12 @@
final ScriptManager scriptManager = configuration.getScriptManager();
scriptManager.addScript(script);
final Bindings bindings = scriptManager.createBindings(script);
+ LOGGER.debug("ScriptAppenderSelector '{}' executing {} '{}': {}", name, script.getLanguage(),
+ script.getName(), script.getScriptText());
final Object object = scriptManager.execute(script.getName(), bindings);
- final String appenderName = Objects.toString(object, null);
- final Appender appender = appenderSet.createAppender(appenderName, name);
- return appender;
+ final String actualAppenderName = Objects.toString(object, null);
+ LOGGER.debug("ScriptAppenderSelector '{}' selected '{}'", name, actualAppenderName);
+ return appenderSet.createAppender(actualAppenderName, name);
}
public AppenderSet getAppenderSet() {
@@ -103,35 +105,35 @@
return script;
}
- public Builder withAppenderNodeSet(@SuppressWarnings("hiding") final AppenderSet appenderSet) {
+ public Builder setAppenderNodeSet(@SuppressWarnings("hiding") final AppenderSet appenderSet) {
this.appenderSet = appenderSet;
return this;
}
- public Builder withConfiguration(@SuppressWarnings("hiding") final Configuration configuration) {
+ public Builder setConfiguration(@SuppressWarnings("hiding") final Configuration configuration) {
this.configuration = configuration;
return this;
}
- public Builder withName(@SuppressWarnings("hiding") final String name) {
+ public Builder setName(@SuppressWarnings("hiding") final String name) {
this.name = name;
return this;
}
- public Builder withScript(@SuppressWarnings("hiding") final AbstractScript script) {
+ public Builder setScript(@SuppressWarnings("hiding") final AbstractScript script) {
this.script = script;
return this;
}
}
- @PluginBuilderFactory
+ @PluginFactory
public static Builder newBuilder() {
return new Builder();
}
private ScriptAppenderSelector(final String name, final Filter filter, final Layout<? extends Serializable> layout) {
- super(name, filter, layout);
+ super(name, filter, layout, true, Property.EMPTY_ARRAY);
}
@Override
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 b1d167c..ec2041b 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/SocketAppender.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/SocketAppender.java
@@ -16,26 +16,12 @@
*/
package org.apache.logging.log4j.core.appender;
-import java.io.Serializable;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.concurrent.TimeUnit;
-
import org.apache.logging.log4j.core.AbstractLifeCycle;
import org.apache.logging.log4j.core.Appender;
import org.apache.logging.log4j.core.Core;
import org.apache.logging.log4j.core.Filter;
import org.apache.logging.log4j.core.Layout;
import org.apache.logging.log4j.core.LogEvent;
-import org.apache.logging.log4j.core.config.Configuration;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginAliases;
-import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
-import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
-import org.apache.logging.log4j.core.config.plugins.PluginElement;
-import org.apache.logging.log4j.core.config.plugins.PluginFactory;
-import org.apache.logging.log4j.core.config.plugins.validation.constraints.ValidHost;
-import org.apache.logging.log4j.core.config.plugins.validation.constraints.ValidPort;
import org.apache.logging.log4j.core.net.AbstractSocketManager;
import org.apache.logging.log4j.core.net.Advertiser;
import org.apache.logging.log4j.core.net.DatagramSocketManager;
@@ -44,7 +30,18 @@
import org.apache.logging.log4j.core.net.SslSocketManager;
import org.apache.logging.log4j.core.net.TcpSocketManager;
import org.apache.logging.log4j.core.net.ssl.SslConfiguration;
-import org.apache.logging.log4j.core.util.Booleans;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginAliases;
+import org.apache.logging.log4j.plugins.PluginBuilderAttribute;
+import org.apache.logging.log4j.plugins.PluginElement;
+import org.apache.logging.log4j.plugins.PluginFactory;
+import org.apache.logging.log4j.plugins.validation.constraints.ValidHost;
+import org.apache.logging.log4j.plugins.validation.constraints.ValidPort;
+
+import java.io.Serializable;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
/**
* An Appender that delivers events over socket connections. Supports both TCP and UDP.
@@ -129,47 +126,47 @@
return immediateFail;
}
- public B withAdvertise(final boolean advertise) {
+ public B setAdvertise(final boolean advertise) {
this.advertise = advertise;
return asBuilder();
}
- public B withConnectTimeoutMillis(final int connectTimeoutMillis) {
+ public B setConnectTimeoutMillis(final int connectTimeoutMillis) {
this.connectTimeoutMillis = connectTimeoutMillis;
return asBuilder();
}
- public B withHost(final String host) {
+ public B setHost(final String host) {
this.host = host;
return asBuilder();
}
- public B withImmediateFail(final boolean immediateFail) {
+ public B setImmediateFail(final boolean immediateFail) {
this.immediateFail = immediateFail;
return asBuilder();
}
- public B withPort(final int port) {
+ public B setPort(final int port) {
this.port = port;
return asBuilder();
}
- public B withProtocol(final Protocol protocol) {
+ public B setProtocol(final Protocol protocol) {
this.protocol = protocol;
return asBuilder();
}
- public B withReconnectDelayMillis(final int reconnectDelayMillis) {
+ public B setReconnectDelayMillis(final int reconnectDelayMillis) {
this.reconnectDelayMillis = reconnectDelayMillis;
return asBuilder();
}
- public B withSocketOptions(final SocketOptions socketOptions) {
+ public B setSocketOptions(final SocketOptions socketOptions) {
this.socketOptions = socketOptions;
return asBuilder();
}
- public B withSslConfiguration(final SslConfiguration sslConfiguration) {
+ public B setSslConfiguration(final SslConfiguration sslConfiguration) {
this.sslConfiguration = sslConfiguration;
return asBuilder();
}
@@ -192,7 +189,7 @@
* </ul>
*/
public static class Builder extends AbstractBuilder<Builder>
- implements org.apache.logging.log4j.core.util.Builder<SocketAppender> {
+ implements org.apache.logging.log4j.plugins.util.Builder<SocketAppender> {
@SuppressWarnings("resource")
@Override
@@ -225,7 +222,7 @@
}
}
- @PluginBuilderFactory
+ @PluginFactory
public static Builder newBuilder() {
return new Builder();
}
@@ -236,7 +233,7 @@
protected SocketAppender(final String name, final Layout<? extends Serializable> layout, final Filter filter,
final AbstractSocketManager manager, final boolean ignoreExceptions, final boolean immediateFlush,
final Advertiser advertiser) {
- super(name, layout, filter, ignoreExceptions, immediateFlush, manager);
+ super(name, layout, filter, ignoreExceptions, immediateFlush, null, manager);
if (advertiser != null) {
final Map<String, String> configuration = new HashMap<>(layout.getContentFormat());
configuration.putAll(manager.getContentFormat());
@@ -261,159 +258,6 @@
}
/**
- * Creates a socket appender.
- *
- * @param host
- * The name of the host to connect to.
- * @param port
- * The port to connect to on the target host.
- * @param protocol
- * The Protocol to use.
- * @param sslConfig
- * The SSL configuration file for TCP/SSL, ignored for UPD.
- * @param connectTimeoutMillis
- * the connect timeout in milliseconds.
- * @param reconnectDelayMillis
- * The interval in which failed writes should be retried.
- * @param immediateFail
- * True if the write should fail if no socket is immediately available.
- * @param name
- * The name of the Appender.
- * @param immediateFlush
- * "true" if data should be flushed on each write.
- * @param ignoreExceptions
- * If {@code "true"} (default) exceptions encountered when appending events are logged; otherwise they
- * are propagated to the caller.
- * @param layout
- * The layout to use. Required, there is no default.
- * @param filter
- * The Filter or null.
- * @param advertise
- * "true" if the appender configuration should be advertised, "false" otherwise.
- * @param configuration
- * The Configuration
- * @return A SocketAppender.
- * @deprecated Deprecated in 2.7; use {@link #newBuilder()}
- */
- @Deprecated
- @PluginFactory
- public static SocketAppender createAppender(
- // @formatter:off
- final String host,
- final int port,
- final Protocol protocol,
- final SslConfiguration sslConfig,
- final int connectTimeoutMillis,
- final int reconnectDelayMillis,
- final boolean immediateFail,
- final String name,
- final boolean immediateFlush,
- final boolean ignoreExceptions,
- final Layout<? extends Serializable> layout,
- final Filter filter,
- final boolean advertise,
- final Configuration configuration) {
- // @formatter:on
-
- // @formatter:off
- return newBuilder()
- .withAdvertise(advertise)
- .setConfiguration(configuration)
- .withConnectTimeoutMillis(connectTimeoutMillis)
- .withFilter(filter)
- .withHost(host)
- .withIgnoreExceptions(ignoreExceptions)
- .withImmediateFail(immediateFail)
- .withLayout(layout)
- .withName(name)
- .withPort(port)
- .withProtocol(protocol)
- .withReconnectDelayMillis(reconnectDelayMillis)
- .withSslConfiguration(sslConfig)
- .build();
- // @formatter:on
- }
-
- /**
- * Creates a socket appender.
- *
- * @param host
- * The name of the host to connect to.
- * @param portNum
- * The port to connect to on the target host.
- * @param protocolIn
- * The Protocol to use.
- * @param sslConfig
- * The SSL configuration file for TCP/SSL, ignored for UPD.
- * @param connectTimeoutMillis
- * the connect timeout in milliseconds.
- * @param delayMillis
- * The interval in which failed writes should be retried.
- * @param immediateFail
- * True if the write should fail if no socket is immediately available.
- * @param name
- * The name of the Appender.
- * @param immediateFlush
- * "true" if data should be flushed on each write.
- * @param ignore
- * If {@code "true"} (default) exceptions encountered when appending events are logged; otherwise they
- * are propagated to the caller.
- * @param layout
- * The layout to use. Required, there is no default.
- * @param filter
- * The Filter or null.
- * @param advertise
- * "true" if the appender configuration should be advertised, "false" otherwise.
- * @param config
- * The Configuration
- * @return A SocketAppender.
- * @deprecated Deprecated in 2.5; use {@link #newBuilder()}
- */
- @Deprecated
- public static SocketAppender createAppender(
- // @formatter:off
- final String host,
- final String portNum,
- final String protocolIn,
- final SslConfiguration sslConfig,
- final int connectTimeoutMillis,
- // deprecated
- final String delayMillis,
- final String immediateFail,
- final String name,
- final String immediateFlush,
- final String ignore,
- final Layout<? extends Serializable> layout,
- final Filter filter,
- final String advertise,
- final Configuration config) {
- // @formatter:on
- final boolean isFlush = Booleans.parseBoolean(immediateFlush, true);
- final boolean isAdvertise = Boolean.parseBoolean(advertise);
- final boolean ignoreExceptions = Booleans.parseBoolean(ignore, true);
- final boolean fail = Booleans.parseBoolean(immediateFail, true);
- final int reconnectDelayMillis = AbstractAppender.parseInt(delayMillis, 0);
- final int port = AbstractAppender.parseInt(portNum, 0);
- final Protocol p = protocolIn == null ? Protocol.UDP : Protocol.valueOf(protocolIn);
- return createAppender(host, port, p, sslConfig, connectTimeoutMillis, reconnectDelayMillis, fail, name, isFlush,
- ignoreExceptions, layout, filter, isAdvertise, config);
- }
-
- /**
- * Creates an AbstractSocketManager for TCP, UDP, and SSL.
- *
- * @throws IllegalArgumentException
- * if the protocol cannot be handled.
- * @deprecated Use {@link #createSocketManager(String, Protocol, String, int, int, SslConfiguration, int, boolean, Layout, int, SocketOptions)}.
- */
- @Deprecated
- protected static AbstractSocketManager createSocketManager(final String name, final Protocol protocol, final String host,
- final int port, final int connectTimeoutMillis, final SslConfiguration sslConfig, final int reconnectDelayMillis,
- final boolean immediateFail, final Layout<? extends Serializable> layout, final int bufferSize) {
- return createSocketManager(name, protocol, host, port, connectTimeoutMillis, sslConfig, reconnectDelayMillis, immediateFail, layout, bufferSize, null);
- }
-
- /**
* Creates an AbstractSocketManager for TCP, UDP, and SSL.
*
* @throws IllegalArgumentException
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 26b435f..dec5c5e 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
@@ -16,19 +16,11 @@
*/
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.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
-import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
-import org.apache.logging.log4j.core.config.plugins.PluginElement;
import org.apache.logging.log4j.core.layout.LoggerFields;
import org.apache.logging.log4j.core.layout.Rfc5424Layout;
import org.apache.logging.log4j.core.layout.SyslogLayout;
@@ -38,7 +30,14 @@
import org.apache.logging.log4j.core.net.Protocol;
import org.apache.logging.log4j.core.net.ssl.SslConfiguration;
import org.apache.logging.log4j.core.util.Constants;
-import org.apache.logging.log4j.util.EnglishEnums;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginBuilderAttribute;
+import org.apache.logging.log4j.plugins.PluginElement;
+import org.apache.logging.log4j.plugins.PluginFactory;
+
+import java.io.Serializable;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
/**
* The Syslog Appender.
@@ -47,7 +46,7 @@
public class SyslogAppender extends SocketAppender {
public static class Builder<B extends Builder<B>> extends AbstractBuilder<B>
- implements org.apache.logging.log4j.core.util.Builder<SocketAppender> {
+ implements org.apache.logging.log4j.plugins.util.Builder<SocketAppender> {
@PluginBuilderAttribute(value = "facility")
private Facility facility = Facility.LOCAL0;
@@ -310,119 +309,8 @@
}
- /**
- * Creates a SyslogAppender.
- * @param host The name of the host to connect to.
- * @param port The port to connect to on the target host.
- * @param protocolStr The Protocol to use.
- * @param sslConfiguration TODO
- * @param connectTimeoutMillis the connect timeout in milliseconds.
- * @param reconnectDelayMillis The interval in which failed writes should be retried.
- * @param immediateFail True if the write should fail if no socket is immediately available.
- * @param name The name of the Appender.
- * @param immediateFlush "true" if data should be flushed on each write.
- * @param ignoreExceptions If {@code "true"} (default) exceptions encountered when appending events are logged;
- * otherwise they are propagated to the caller.
- * @param facility The Facility is used to try to classify the message.
- * @param id The default structured data id to use when formatting according to RFC 5424.
- * @param enterpriseNumber The IANA enterprise number.
- * @param includeMdc Indicates whether data from the ThreadContextMap will be included in the RFC 5424 Syslog
- * record. Defaults to "true:.
- * @param mdcId The id to use for the MDC Structured Data Element.
- * @param mdcPrefix The prefix to add to MDC key names.
- * @param eventPrefix The prefix to add to event key names.
- * @param newLine If true, a newline will be appended to the end of the syslog record. The default is false.
- * @param escapeNL String that should be used to replace newlines within the message text.
- * @param appName The value to use as the APP-NAME in the RFC 5424 syslog record.
- * @param msgId The default value to be used in the MSGID field of RFC 5424 syslog records.
- * @param excludes A comma separated list of mdc keys that should be excluded from the LogEvent.
- * @param includes A comma separated list of mdc keys that should be included in the FlumeEvent.
- * @param required A comma separated list of mdc keys that must be present in the MDC.
- * @param format If set to "RFC5424" the data will be formatted in accordance with RFC 5424. Otherwise,
- * it will be formatted as a BSD Syslog record.
- * @param filter A Filter to determine if the event should be handled by this Appender.
- * @param configuration The Configuration.
- * @param charset The character set to use when converting the syslog String to a byte array.
- * @param exceptionPattern The converter pattern to use for formatting exceptions.
- * @param loggerFields The logger fields
- * @param advertise Whether to advertise
- * @return A SyslogAppender.
- * @deprecated Use {@link #newSyslogAppenderBuilder()}.
- */
- @Deprecated
- public static <B extends Builder<B>> SyslogAppender createAppender(
- // @formatter:off
- final String host,
- final int port,
- final String protocolStr,
- final SslConfiguration sslConfiguration,
- final int connectTimeoutMillis,
- final int reconnectDelayMillis,
- final boolean immediateFail,
- final String name,
- final boolean immediateFlush,
- final boolean ignoreExceptions,
- final Facility facility,
- final String id,
- final int enterpriseNumber,
- final boolean includeMdc,
- final String mdcId,
- final String mdcPrefix,
- final String eventPrefix,
- final boolean newLine,
- final String escapeNL,
- final String appName,
- final String msgId,
- final String excludes,
- final String includes,
- final String required,
- final String format,
- final Filter filter,
- final Configuration configuration,
- final Charset charset,
- final String exceptionPattern,
- final LoggerFields[] loggerFields,
- final boolean advertise) {
- // @formatter:on
-
- // @formatter:off
- return SyslogAppender.<B>newSyslogAppenderBuilder()
- .withHost(host)
- .withPort(port)
- .withProtocol(EnglishEnums.valueOf(Protocol.class, protocolStr))
- .withSslConfiguration(sslConfiguration)
- .withConnectTimeoutMillis(connectTimeoutMillis)
- .withReconnectDelayMillis(reconnectDelayMillis)
- .withImmediateFail(immediateFail)
- .withName(appName)
- .withImmediateFlush(immediateFlush)
- .withIgnoreExceptions(ignoreExceptions)
- .withFilter(filter)
- .setConfiguration(configuration)
- .withAdvertise(advertise)
- .setFacility(facility)
- .setId(id)
- .setEnterpriseNumber(enterpriseNumber)
- .setIncludeMdc(includeMdc)
- .setMdcId(mdcId)
- .setMdcPrefix(mdcPrefix)
- .setEventPrefix(eventPrefix)
- .setNewLine(newLine)
- .setAppName(appName)
- .setMsgId(msgId)
- .setExcludes(excludes)
- .setIncludeMdc(includeMdc)
- .setRequired(required)
- .setFormat(format)
- .setCharsetName(charset)
- .setExceptionPattern(exceptionPattern)
- .setLoggerFields(loggerFields)
- .build();
- // @formatter:on
- }
-
// Calling this method newBuilder() does not compile
- @PluginBuilderFactory
+ @PluginFactory
public static <B extends Builder<B>> B newSyslogAppenderBuilder() {
return new Builder<B>().asBuilder();
}
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 dc8f4d2..e2ad979 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/WriterAppender.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/WriterAppender.java
@@ -16,17 +16,17 @@
*/
package org.apache.logging.log4j.core.appender;
-import java.io.Writer;
-
import org.apache.logging.log4j.core.Appender;
import org.apache.logging.log4j.core.Core;
import org.apache.logging.log4j.core.Filter;
import org.apache.logging.log4j.core.StringLayout;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
-import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+import org.apache.logging.log4j.core.config.Property;
import org.apache.logging.log4j.core.layout.PatternLayout;
import org.apache.logging.log4j.core.util.CloseShieldWriter;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginFactory;
+
+import java.io.Writer;
/**
* Appends log events to a {@link Writer}.
@@ -37,53 +37,29 @@
/**
* Builds WriterAppender instances.
*/
- public static class Builder implements org.apache.logging.log4j.core.util.Builder<WriterAppender> {
-
- private Filter filter;
+ public static class Builder<B extends Builder<B>> extends AbstractAppender.Builder<B>
+ implements org.apache.logging.log4j.plugins.util.Builder<WriterAppender> {
private boolean follow = false;
- private boolean ignoreExceptions = true;
-
- private StringLayout layout = PatternLayout.createDefaultLayout();
-
- private String name;
-
private Writer target;
@Override
public WriterAppender build() {
- return new WriterAppender(name, layout, filter, getManager(target, follow, layout), ignoreExceptions);
+ final StringLayout layout = (StringLayout) getLayout();
+ final StringLayout actualLayout = layout != null ? layout : PatternLayout.createDefaultLayout();
+ return new WriterAppender(getName(), actualLayout, getFilter(), getManager(target, follow, actualLayout),
+ isIgnoreExceptions(), getPropertyArray());
}
- public Builder setFilter(final Filter aFilter) {
- this.filter = aFilter;
- return this;
- }
-
- public Builder setFollow(final boolean shouldFollow) {
+ public B setFollow(final boolean shouldFollow) {
this.follow = shouldFollow;
- return this;
+ 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();
}
}
/**
@@ -128,7 +104,7 @@
}
}
- private static WriterManagerFactory factory = new WriterManagerFactory();
+ private static final WriterManagerFactory factory = new WriterManagerFactory();
/**
* Creates a WriterAppender.
@@ -160,7 +136,7 @@
if (layout == null) {
layout = PatternLayout.createDefaultLayout();
}
- return new WriterAppender(name, layout, filter, getManager(target, follow, layout), ignore);
+ return new WriterAppender(name, layout, filter, getManager(target, follow, layout), ignore, Property.EMPTY_ARRAY);
}
private static WriterManager getManager(final Writer target, final boolean follow, final StringLayout layout) {
@@ -170,14 +146,14 @@
return WriterManager.getManager(managerName, new FactoryData(writer, managerName, layout), factory);
}
- @PluginBuilderFactory
- public static Builder newBuilder() {
- return new Builder();
+ @PluginFactory
+ public static <B extends Builder<B>> B newBuilder() {
+ return new Builder<B>().asBuilder();
}
private WriterAppender(final String name, final StringLayout layout, final Filter filter,
- final WriterManager manager, final boolean ignoreExceptions) {
- super(name, layout, filter, ignoreExceptions, true, manager);
+ final WriterManager manager, final boolean ignoreExceptions, Property[] properties) {
+ super(name, layout, filter, ignoreExceptions, true, properties, manager);
}
}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/AbstractDatabaseAppender.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/AbstractDatabaseAppender.java
index 09798fd..7323d97 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/AbstractDatabaseAppender.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/AbstractDatabaseAppender.java
@@ -28,6 +28,7 @@
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.appender.AbstractAppender;
import org.apache.logging.log4j.core.appender.AppenderLoggingException;
+import org.apache.logging.log4j.core.config.Property;
/**
* An abstract Appender for writing events to a database of some type, be it relational or NoSQL. All database appenders
@@ -39,10 +40,15 @@
*/
public abstract class AbstractDatabaseAppender<T extends AbstractDatabaseManager> extends AbstractAppender {
+ public static class Builder<B extends Builder<B>> extends AbstractAppender.Builder<B> {
+ // 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;
/**
@@ -50,32 +56,36 @@
*
* @param name The appender name.
* @param filter The filter, if any, to use.
- * @param ignoreExceptions If {@code true} exceptions encountered when appending events are logged; otherwise
- * they are propagated to the caller.
- * @param manager The matching {@link AbstractDatabaseManager} implementation.
- */
- protected AbstractDatabaseAppender(final String name, final Filter filter, final boolean ignoreExceptions,
- final T manager) {
- super(name, filter, null, ignoreExceptions);
- this.manager = manager;
- }
-
- /**
- * Instantiates the base appender.
- *
- * @param name The appender name.
- * @param filter The filter, if any, to use.
* @param layout The layout to use to format the event.
* @param ignoreExceptions If {@code true} exceptions encountered when appending events are logged; otherwise
* they are propagated to the caller.
+ * @param properties TODO
* @param manager The matching {@link AbstractDatabaseManager} implementation.
*/
protected AbstractDatabaseAppender(final String name, final Filter filter,
- final Layout<? extends Serializable> layout, final boolean ignoreExceptions, final T manager) {
- super(name, filter, layout, ignoreExceptions);
+ final Layout<? extends Serializable> layout, final boolean ignoreExceptions, Property[] properties, final T manager) {
+ super(name, filter, layout, ignoreExceptions, properties);
this.manager = manager;
}
+ @Override
+ public final void append(final LogEvent event) {
+ this.readLock.lock();
+ try {
+ this.getManager().write(event, toSerializable(event));
+ } catch (final LoggingException e) {
+ LOGGER.error("Unable to write to database [{}] for appender [{}].", this.getManager().getName(),
+ this.getName(), e);
+ throw e;
+ } catch (final Exception e) {
+ LOGGER.error("Unable to write to database [{}] for appender [{}].", this.getManager().getName(),
+ this.getName(), e);
+ throw new AppenderLoggingException("Unable to write to database in appender: " + e.getMessage(), e);
+ } finally {
+ this.readLock.unlock();
+ }
+ }
+
/**
* This always returns {@code null}, as database appenders do not use a single layout. The JPA and NoSQL appenders
* do not use a layout at all. The JDBC appender has a layout-per-column pattern.
@@ -96,6 +106,27 @@
return this.manager;
}
+ /**
+ * Replaces the underlying manager in use within this appender. This can be useful for manually changing the way log
+ * events are written to the database without losing buffered or in-progress events. The existing manager is
+ * released only after the new manager has been installed. This method is thread-safe.
+ *
+ * @param manager The new manager to install.
+ */
+ protected final void replaceManager(final T manager) {
+ this.writeLock.lock();
+ try {
+ final T old = this.getManager();
+ if (!manager.isRunning()) {
+ manager.startup();
+ }
+ this.manager = manager;
+ old.close();
+ } finally {
+ this.writeLock.unlock();
+ }
+ }
+
@Override
public final void start() {
if (this.getManager() == null) {
@@ -117,43 +148,4 @@
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 0350543..fd61dfb 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/AbstractDatabaseManager.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/AbstractDatabaseManager.java
@@ -31,12 +31,68 @@
* Manager that allows database appenders to have their configuration reloaded without losing events.
*/
public abstract class AbstractDatabaseManager extends AbstractManager implements Flushable {
+
+ /**
+ * Implementations should extend this class for passing data between the getManager method and the manager factory
+ * class.
+ */
+ protected abstract static class AbstractFactoryData {
+ private final int bufferSize;
+ private final Layout<? extends Serializable> layout;
+
+ /**
+ * Constructs the base factory data.
+ *
+ * @param bufferSize The size of the buffer.
+ * @param layout The appender-level layout
+ */
+ protected AbstractFactoryData(final int bufferSize, final Layout<? extends Serializable> layout) {
+ this.bufferSize = bufferSize;
+ this.layout = layout;
+ }
+
+ /**
+ * Gets the buffer size.
+ *
+ * @return the buffer size.
+ */
+ public int getBufferSize() {
+ return bufferSize;
+ }
+
+ /**
+ * Gets the layout.
+ *
+ * @return the layout.
+ */
+ public Layout<? extends Serializable> 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 <M> The concrete manager type.
+ * @param <T> The concrete {@link AbstractFactoryData} type.
+ * @return a new or existing manager of the specified type and name.
+ */
+ protected static <M extends AbstractDatabaseManager, T extends AbstractFactoryData> M getManager(
+ final String name, final T data, final ManagerFactory<M, T> factory
+ ) {
+ return AbstractManager.getManager(name, factory, data);
+ }
+
private final ArrayList<LogEvent> buffer;
+
private final int bufferSize;
private final Layout<? extends Serializable> layout;
-
- private boolean running = false;
-
+ private boolean running;
/**
* Instantiates the base manager.
*
@@ -63,37 +119,67 @@
this.layout = layout;
}
- /**
- * 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 void startupInternal() throws Exception;
-
- /**
- * 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.
- */
- 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);
- }
+ 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 disconnection / shutdown operations. This
- * method will never be called twice on the same instance, and it will only be called <em>after</em>
- * {@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()}.
+ * 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 boolean shutdownInternal() throws Exception;
+ protected abstract boolean commitAndClose();
+
+ /**
+ * 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();
+
+ /**
+ * 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 {
+ 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;
+ }
+
+ /**
+ * 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;
+ }
+
+ @Override
+ public final boolean releaseSub(final long timeout, final TimeUnit timeUnit) {
+ return this.shutdown();
+ }
/**
* This method is called from the {@link #close()} method when the appender is stopped or the appender's manager
@@ -118,78 +204,40 @@
}
/**
- * Indicates whether the manager is currently connected {@link #startup()} has been called and {@link #shutdown()}
- * has not been called).
- *
- * @return {@code true} if the manager is connected.
- */
- public final boolean isRunning() {
- return this.running;
- }
-
- /**
- * Connects to the database and starts a transaction (if applicable). With buffering enabled, this is called when
- * flushing the buffer begins, before the first call to {@link #writeInternal}. With buffering disabled, this is
- * called immediately before every invocation of {@link #writeInternal}.
- */
- protected abstract void connectAndStart();
-
- /**
- * Performs the actual writing of the event in an implementation-specific way. This method is called immediately
- * from {@link #write(LogEvent, Serializable)} if buffering is off, or from {@link #flush()} if the buffer has reached its limit.
- *
- * @param event The event to write to the database.
- * @deprecated Use {@link #writeInternal(LogEvent, Serializable)}.
- */
- @Deprecated
- protected abstract void writeInternal(LogEvent event);
-
- /**
- * Performs the actual writing of the event in an implementation-specific way. This method is called immediately
- * from {@link #write(LogEvent, Serializable)} if buffering is off, or from {@link #flush()} if the buffer has reached its limit.
- *
- * @param event The event to write to the database.
- */
- protected abstract void writeInternal(LogEvent event, Serializable serializable);
-
- /**
- * Commits any active transaction (if applicable) and disconnects from the database (returns the connection to the
- * connection pool). With buffering enabled, this is called when flushing the buffer completes, after the last call
- * to {@link #writeInternal}. With buffering disabled, this is called immediately after every invocation of
- * {@link #writeInternal}.
+ * Implementations should implement this method to perform any proprietary disconnection / shutdown operations. This
+ * method will never be called twice on the same instance, and it will only be called <em>after</em>
+ * {@link #startupInternal()}. It is safe to throw any exceptions from this method. This method does not
+ * necessarily disconnect from the database for the same reasons outlined in {@link #startupInternal()}.
* @return true if all resources were closed normally, false otherwise.
*/
- protected abstract boolean commitAndClose();
+ protected abstract boolean shutdownInternal() throws Exception;
/**
- * This method is called automatically when the buffer size reaches its maximum or at the beginning of a call to
- * {@link #shutdown()}. It can also be called manually to flush events to the database.
+ * This method is called within the appender when the appender is started. If it has not already been called, it
+ * calls {@link #startupInternal()} and catches any exceptions it might throw.
*/
- @Override
- public final synchronized void flush() {
- if (this.isRunning() && this.buffer.size() > 0) {
- this.connectAndStart();
+ public final synchronized void startup() {
+ if (!this.isRunning()) {
try {
- for (final LogEvent event : this.buffer) {
- this.writeInternal(event, layout != null ? layout.toSerializable(event) : null);
- }
- } finally {
- this.commitAndClose();
- // not sure if this should be done when writing the events failed
- this.buffer.clear();
+ this.startupInternal();
+ this.running = true;
+ } catch (final Exception e) {
+ logError("Could not perform database startup operations", e);
}
}
}
/**
- * This method manages buffering and writing of events.
- *
- * @param event The event to write to the database.
- * @deprecated since 2.11.0 Use {@link #write(LogEvent, Serializable)}.
+ * Implementations should implement this method to perform any proprietary startup operations. This method will
+ * never be called twice on the same instance. It is safe to throw any exceptions from this method. This method
+ * does not necessarily connect to the database, as it is generally unreliable to connect once and use the same
+ * connection for hours.
*/
- @Deprecated
- public final synchronized void write(final LogEvent event) {
- write(event, null);
+ protected abstract void startupInternal() throws Exception;
+
+ @Override
+ public final String toString() {
+ return this.getName();
}
/**
@@ -199,84 +247,27 @@
* @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 <M> The concrete manager type.
- * @param <T> 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.
*/
- protected static <M extends AbstractDatabaseManager, T extends AbstractFactoryData> M getManager(
- final String name, final T data, final ManagerFactory<M, T> factory
- ) {
- return AbstractManager.getManager(name, factory, data);
- }
+ protected abstract void writeInternal(LogEvent event, Serializable serializable);
- /**
- * Implementations should extend this class for passing data between the getManager method and the manager factory
- * class.
- */
- protected abstract static class AbstractFactoryData {
- private final int bufferSize;
- private final Layout<? extends Serializable> 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<? extends Serializable> layout) {
- this.bufferSize = bufferSize;
- this.layout = layout;
- }
-
- /**
- * Gets the buffer size.
- *
- * @return the buffer size.
- */
- public int getBufferSize() {
- return bufferSize;
- }
-
- /**
- * Gets the layout.
- *
- * @return the layout.
- */
- public Layout<? extends Serializable> 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 470c6a7..740ebc8 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/ColumnMapping.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/ColumnMapping.java
@@ -16,24 +16,25 @@
*/
package org.apache.logging.log4j.core.appender.db;
-import java.util.Date;
-
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.core.Core;
import org.apache.logging.log4j.core.StringLayout;
import org.apache.logging.log4j.core.config.Configuration;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
-import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
-import org.apache.logging.log4j.core.config.plugins.PluginElement;
-import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required;
import org.apache.logging.log4j.core.layout.PatternLayout;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginBuilderAttribute;
+import org.apache.logging.log4j.plugins.PluginElement;
+import org.apache.logging.log4j.plugins.PluginFactory;
+import org.apache.logging.log4j.plugins.validation.constraints.Required;
import org.apache.logging.log4j.spi.ThreadContextMap;
import org.apache.logging.log4j.spi.ThreadContextStack;
import org.apache.logging.log4j.status.StatusLogger;
import org.apache.logging.log4j.util.ReadOnlyStringMap;
+import java.util.Date;
+import java.util.Locale;
+
/**
* A configuration element for specifying a database column name mapping.
*
@@ -45,7 +46,7 @@
/**
* Builder for {@link ColumnMapping}.
*/
- public static class Builder implements org.apache.logging.log4j.core.util.Builder<ColumnMapping> {
+ public static class Builder implements org.apache.logging.log4j.plugins.util.Builder<ColumnMapping> {
@PluginConfiguration
private Configuration configuration;
@@ -77,8 +78,9 @@
public ColumnMapping build() {
if (pattern != null) {
layout = PatternLayout.newBuilder()
- .withPattern(pattern)
- .withConfiguration(configuration)
+ .setPattern(pattern)
+ .setConfiguration(configuration)
+ .setAlwaysWriteExceptions(false)
.build();
}
if (!(layout == null
@@ -105,8 +107,8 @@
/**
* 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 +118,8 @@
/**
* 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 +128,8 @@
/**
* Column name.
- *
- * @return this.
+ *
+ * @return this.
*/
public Builder setName(final String name) {
this.name = name;
@@ -137,8 +139,8 @@
/**
* Parameter value to use for populating a column, MUST contain a single parameter marker '?'. This is generally useful for functions, stored procedures,
* etc. No escaping will be done on this value.
- *
- * @return this.
+ *
+ * @return this.
*/
public Builder setParameter(final String parameter) {
this.parameter= parameter;
@@ -148,8 +150,8 @@
/**
* 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 +161,7 @@
/**
* 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,8 +173,8 @@
* Class to convert value to before storing in database. If the type is compatible with {@link ThreadContextMap} or
* {@link ReadOnlyStringMap}, then the MDC will be used. If the type is compatible with {@link ThreadContextStack},
* then the NDC will be used. If the type is compatible with {@link Date}, then the event timestamp will be used.
- *
- * @return this.
+ *
+ * @return this.
*/
public Builder setType(final Class<?> type) {
this.type = type;
@@ -187,20 +189,27 @@
}
private static final Logger LOGGER = StatusLogger.getLogger();
- @PluginBuilderFactory
+
+ @PluginFactory
public static Builder newBuilder() {
return new Builder();
}
-
+
+ public static String toKey(final String name) {
+ return name.toUpperCase(Locale.ROOT);
+ }
+
private final StringLayout layout;
private final String literalValue;
private final String name;
+ private final String nameKey;
private final String parameter;
private final String source;
private final Class<?> type;
private ColumnMapping(final String name, final String source, final StringLayout layout, final String literalValue, final String parameter, final Class<?> type) {
this.name = name;
+ this.nameKey = toKey(name);
this.source = source;
this.layout = layout;
this.literalValue = literalValue;
@@ -220,6 +229,10 @@
return name;
}
+ public String getNameKey() {
+ return nameKey;
+ }
+
public String getParameter() {
return parameter;
}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/DbAppenderLoggingException.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/DbAppenderLoggingException.java
new file mode 100644
index 0000000..5545984
--- /dev/null
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/db/DbAppenderLoggingException.java
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.logging.log4j.core.appender.db;
+
+import org.apache.logging.log4j.core.appender.AppenderLoggingException;
+
+/**
+ * Wraps a database exception like a JDBC SQLException. Use this class to distinguish exceptions specifically caught
+ * from database layers like JDBC.
+ */
+public class DbAppenderLoggingException extends AppenderLoggingException {
+
+ private static final long serialVersionUID = 1L;
+
+ /**
+ * Constructs an exception with a message.
+ *
+ * @param format The reason format for the exception, see {@link String#format(String, Object...)}.
+ * @param args The reason arguments for the exception, see {@link String#format(String, Object...)}.
+ * @since 2.12.1
+ */
+ public DbAppenderLoggingException(String format, 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(Throwable cause, String format, Object... args) {
+ super(cause, format, args);
+ }
+
+}
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 0d23a2a..8819040 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/nosql/NoSqlAppender.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/nosql/NoSqlAppender.java
@@ -16,18 +16,18 @@
*/
package org.apache.logging.log4j.core.appender.nosql;
-import java.io.Serializable;
-
import org.apache.logging.log4j.core.Appender;
import org.apache.logging.log4j.core.Filter;
import org.apache.logging.log4j.core.Layout;
import org.apache.logging.log4j.core.appender.AbstractAppender;
import org.apache.logging.log4j.core.appender.db.AbstractDatabaseAppender;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
-import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
-import org.apache.logging.log4j.core.config.plugins.PluginElement;
-import org.apache.logging.log4j.core.util.Booleans;
+import org.apache.logging.log4j.core.config.Property;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginBuilderAttribute;
+import org.apache.logging.log4j.plugins.PluginElement;
+import org.apache.logging.log4j.plugins.PluginFactory;
+
+import java.io.Serializable;
/**
* This Appender writes logging events to a NoSQL database using a configured NoSQL provider. It requires
@@ -52,7 +52,7 @@
* The type to build
*/
public static class Builder<B extends Builder<B>> extends AbstractAppender.Builder<B>
- implements org.apache.logging.log4j.core.util.Builder<NoSqlAppender> {
+ implements org.apache.logging.log4j.plugins.util.Builder<NoSqlAppender> {
@PluginBuilderAttribute("bufferSize")
private int bufferSize;
@@ -78,7 +78,7 @@
return null;
}
- return new NoSqlAppender(name, getFilter(), getLayout(), isIgnoreExceptions(), manager);
+ return new NoSqlAppender(name, getFilter(), getLayout(), isIgnoreExceptions(), getPropertyArray(), manager);
}
/**
@@ -107,55 +107,7 @@
}
}
- /**
- * Factory method for creating a NoSQL appender within the plugin manager.
- *
- * @param name
- * The name of the appender.
- * @param ignore
- * If {@code "true"} (default) exceptions encountered when appending events are logged; otherwise they
- * are propagated to the caller.
- * @param filter
- * The filter, if any, to use.
- * @param bufferSize
- * If an integer greater than 0, this causes the appender to buffer log events and flush whenever the
- * buffer reaches this size.
- * @param provider
- * The NoSQL provider that provides connections to the chosen NoSQL database.
- * @return a new NoSQL appender.
- * @deprecated since 2.11.0; use {@link Builder}.
- */
- @SuppressWarnings("resource")
- @Deprecated
- public static NoSqlAppender createAppender(
- // @formatter:off
- final String name,
- final String ignore,
- final Filter filter,
- final String bufferSize,
- final NoSqlProvider<?> provider) {
- // @formatter:on
- if (provider == null) {
- LOGGER.error("NoSQL provider not specified for appender [{}].", name);
- return null;
- }
-
- final int bufferSizeInt = AbstractAppender.parseInt(bufferSize, 0);
- final boolean ignoreExceptions = Booleans.parseBoolean(ignore, true);
-
- final String managerName = "noSqlManager{ description=" + name + ", bufferSize=" + bufferSizeInt + ", provider="
- + provider + " }";
-
- final NoSqlDatabaseManager<?> manager = NoSqlDatabaseManager.getNoSqlDatabaseManager(managerName, bufferSizeInt,
- provider);
- if (manager == null) {
- return null;
- }
-
- return new NoSqlAppender(name, filter, null, ignoreExceptions, manager);
- }
-
- @PluginBuilderFactory
+ @PluginFactory
public static <B extends Builder<B>> B newBuilder() {
return new Builder<B>().asBuilder();
}
@@ -163,8 +115,8 @@
private final String description;
private NoSqlAppender(final String name, final Filter filter, Layout<? extends Serializable> layout,
- final boolean ignoreExceptions, final NoSqlDatabaseManager<?> manager) {
- super(name, filter, layout, ignoreExceptions, manager);
+ final boolean ignoreExceptions, Property[] properties, final NoSqlDatabaseManager<?> manager) {
+ super(name, filter, layout, ignoreExceptions, properties, manager);
this.description = this.getName() + "{ manager=" + this.getManager() + " }";
}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/nosql/NoSqlDatabaseManager.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/nosql/NoSqlDatabaseManager.java
index 7e2cad8..14322e3 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
@@ -67,12 +67,6 @@
}
}
- @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()) {
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 a1ef2ef..abc9339 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
@@ -23,10 +23,10 @@
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.core.Core;
import org.apache.logging.log4j.core.LogEvent;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
-import org.apache.logging.log4j.core.config.plugins.PluginElement;
-import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginAttribute;
+import org.apache.logging.log4j.plugins.PluginElement;
+import org.apache.logging.log4j.plugins.PluginFactory;
import org.apache.logging.log4j.core.impl.Log4jLogEvent;
import org.apache.logging.log4j.core.util.KeyValuePair;
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 5302a07..0072d29 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
@@ -22,10 +22,10 @@
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.core.Core;
import org.apache.logging.log4j.core.LogEvent;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
-import org.apache.logging.log4j.core.config.plugins.PluginElement;
-import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginAttribute;
+import org.apache.logging.log4j.plugins.PluginElement;
+import org.apache.logging.log4j.plugins.PluginFactory;
import org.apache.logging.log4j.core.impl.Log4jLogEvent;
import org.apache.logging.log4j.core.util.KeyValuePair;
import org.apache.logging.log4j.message.MapMessage;
@@ -127,7 +127,7 @@
*/
@PluginFactory
public static MapRewritePolicy createPolicy(
- @PluginAttribute("mode") final String mode,
+ @PluginAttribute final String mode,
@PluginElement("KeyValuePair") final KeyValuePair[] pairs) {
Mode op = mode == null ? op = Mode.Add : Mode.valueOf(mode);
if (pairs == null || pairs.length == 0) {
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rewrite/PropertiesRewritePolicy.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rewrite/PropertiesRewritePolicy.java
index 88a574d..ccd5571 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
@@ -26,10 +26,10 @@
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.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.plugins.PluginElement;
+import org.apache.logging.log4j.plugins.PluginFactory;
import org.apache.logging.log4j.core.impl.ContextDataFactory;
import org.apache.logging.log4j.core.impl.Log4jLogEvent;
import org.apache.logging.log4j.status.StatusLogger;
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 64a0abd..3d8aa26 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
@@ -27,12 +27,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.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
+import org.apache.logging.log4j.core.config.Property;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginAttribute;
import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
-import org.apache.logging.log4j.core.config.plugins.PluginElement;
-import org.apache.logging.log4j.core.config.plugins.PluginFactory;
-import org.apache.logging.log4j.core.util.Booleans;
+import org.apache.logging.log4j.plugins.PluginElement;
+import org.apache.logging.log4j.plugins.PluginFactory;
+import org.apache.logging.log4j.plugins.validation.constraints.Required;
/**
* This Appender allows the logging event to be manipulated before it is processed by other Appenders.
@@ -47,8 +48,8 @@
private RewriteAppender(final String name, final Filter filter, final boolean ignoreExceptions,
final AppenderRef[] appenderRefs, final RewritePolicy rewritePolicy,
- final Configuration config) {
- super(name, filter, null, ignoreExceptions);
+ final Configuration config, Property[] properties) {
+ super(name, filter, null, ignoreExceptions, properties);
this.config = config;
this.rewritePolicy = rewritePolicy;
this.appenderRefs = appenderRefs;
@@ -97,22 +98,12 @@
*/
@PluginFactory
public static RewriteAppender createAppender(
- @PluginAttribute("name") final String name,
- @PluginAttribute("ignoreExceptions") final String ignore,
- @PluginElement("AppenderRef") final AppenderRef[] appenderRefs,
+ @PluginAttribute @Required(message = "No name provided for RewriteAppender") final String name,
+ @PluginAttribute(defaultBoolean = true) final boolean ignoreExceptions,
+ @PluginElement @Required(message = "No appender references defined for RewriteAppender") final AppenderRef[] appenderRefs,
@PluginConfiguration final Configuration config,
- @PluginElement("RewritePolicy") final RewritePolicy rewritePolicy,
- @PluginElement("Filter") final Filter filter) {
-
- final boolean ignoreExceptions = Booleans.parseBoolean(ignore, true);
- if (name == null) {
- LOGGER.error("No name provided for RewriteAppender");
- return null;
- }
- if (appenderRefs == null) {
- LOGGER.error("No appender references defined for RewriteAppender");
- return null;
- }
- return new RewriteAppender(name, filter, ignoreExceptions, appenderRefs, rewritePolicy, config);
+ @PluginElement final RewritePolicy rewritePolicy,
+ @PluginElement final Filter filter) {
+ return new RewriteAppender(name, filter, ignoreExceptions, appenderRefs, rewritePolicy, config, Property.EMPTY_ARRAY);
}
}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/AbstractRolloverStrategy.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/AbstractRolloverStrategy.java
index 741afb8..375dc09 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/AbstractRolloverStrategy.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/AbstractRolloverStrategy.java
@@ -46,6 +46,8 @@
*/
protected static final Logger LOGGER = StatusLogger.getLogger();
+ public static final Pattern PATTERN_COUNTER= Pattern.compile(".*%((?<ZEROPAD>0)?(?<PADDING>\\d+))?i.*");
+
protected final StrSubstitutor strSubstitutor;
protected AbstractRolloverStrategy(final StrSubstitutor strSubstitutor) {
@@ -89,14 +91,16 @@
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<Integer, Path> getEligibleFiles(final String path, final String pattern) {
- return getEligibleFiles(path, pattern, true);
+ return getEligibleFiles("", path, pattern, true);
}
- protected SortedMap<Integer, Path> getEligibleFiles(final String path, final String logfilePattern, final boolean isAscending) {
+ protected SortedMap<Integer, Path> getEligibleFiles(final String currentFile, final String path,
+ final String logfilePattern, final boolean isAscending) {
final TreeMap<Integer, Path> eligibleFiles = new TreeMap<>();
final File file = new File(path);
File parent = file.getParentFile();
@@ -105,7 +109,7 @@
} else {
parent.mkdirs();
}
- if (!logfilePattern.contains("%i")) {
+ if (!PATTERN_COUNTER.matcher(logfilePattern).matches()) {
return eligibleFiles;
}
final Path dir = parent.toPath();
@@ -114,15 +118,22 @@
if (suffixLength > 0) {
fileName = fileName.substring(0, fileName.length() - suffixLength) + ".*";
}
- final String filePattern = fileName.replace(NotANumber.VALUE, "(\\d+)");
+ final String filePattern = fileName.replaceFirst("0?\\u0000", "(0?\\\\d+)");
final Pattern pattern = Pattern.compile(filePattern);
+ final Path current = currentFile.length() > 0 ? new File(currentFile).toPath() : null;
+ LOGGER.debug("Current file: {}", currentFile);
try (DirectoryStream<Path> stream = Files.newDirectoryStream(dir)) {
for (final Path entry: stream) {
final Matcher matcher = pattern.matcher(entry.toFile().getName());
- if (matcher.matches()) {
- final Integer index = Integer.parseInt(matcher.group(1));
- eligibleFiles.put(index, entry);
+ if (matcher.matches() && !entry.equals(current)) {
+ try {
+ final Integer index = Integer.parseInt(matcher.group(1));
+ eligibleFiles.put(index, entry);
+ } catch (NumberFormatException ex) {
+ LOGGER.debug("Ignoring file {} which matches pattern but the index is invalid.",
+ entry.toFile().getName());
+ }
}
}
} catch (final IOException ioe) {
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/CompositeTriggeringPolicy.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/CompositeTriggeringPolicy.java
index ecb48cc..c5c024f 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
@@ -23,9 +23,9 @@
import org.apache.logging.log4j.core.LifeCycle;
import org.apache.logging.log4j.core.LifeCycle2;
import org.apache.logging.log4j.core.LogEvent;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginElement;
-import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginElement;
+import org.apache.logging.log4j.plugins.PluginFactory;
/**
* Triggering policy that wraps other triggering policies.
@@ -50,6 +50,7 @@
@Override
public void initialize(final RollingFileManager manager) {
for (final TriggeringPolicy triggeringPolicy : triggeringPolicies) {
+ LOGGER.debug("Initializing triggering policy {}", triggeringPolicy.toString());
triggeringPolicy.initialize(manager);
}
}
@@ -85,12 +86,7 @@
setStopping();
boolean stopped = true;
for (final TriggeringPolicy triggeringPolicy : triggeringPolicies) {
- if (triggeringPolicy instanceof LifeCycle2) {
- stopped &= ((LifeCycle2) triggeringPolicy).stop(timeout, timeUnit);
- } else if (triggeringPolicy instanceof LifeCycle) {
- ((LifeCycle) triggeringPolicy).stop();
- stopped &= true;
- }
+ stopped &= ((LifeCycle) triggeringPolicy).stop(timeout, timeUnit);
}
setStopped();
return stopped;
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/CronTriggeringPolicy.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/CronTriggeringPolicy.java
index 6eb3e50..1dbc119 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
@@ -17,6 +17,7 @@
package org.apache.logging.log4j.core.appender.rolling;
import java.text.ParseException;
+import java.time.LocalDateTime;
import java.util.Date;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
@@ -27,10 +28,10 @@
import org.apache.logging.log4j.core.config.ConfigurationScheduler;
import org.apache.logging.log4j.core.config.CronScheduledFuture;
import org.apache.logging.log4j.core.config.Scheduled;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginAttribute;
import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
-import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+import org.apache.logging.log4j.plugins.PluginFactory;
import org.apache.logging.log4j.core.util.CronExpression;
/**
@@ -70,6 +71,7 @@
aManager.getPatternProcessor().setCurrentFileTime(lastRegularRoll.getTime());
LOGGER.debug("LastRollForFile {}, LastRegularRole {}", lastRollForFile, lastRegularRoll);
aManager.getPatternProcessor().setPrevFileTime(lastRegularRoll.getTime());
+ aManager.getPatternProcessor().setTimeBased(true);
if (checkOnStartup && lastRollForFile != null && lastRegularRoll != null &&
lastRollForFile.before(lastRegularRoll)) {
lastRollDate = lastRollForFile;
@@ -118,8 +120,8 @@
*/
@PluginFactory
public static CronTriggeringPolicy createPolicy(@PluginConfiguration final Configuration configuration,
- @PluginAttribute("evaluateOnStartup") final String evaluateOnStartup,
- @PluginAttribute("schedule") final String schedule) {
+ @PluginAttribute final String evaluateOnStartup,
+ @PluginAttribute final String schedule) {
CronExpression cronExpression;
final boolean checkOnStartup = Boolean.parseBoolean(evaluateOnStartup);
if (schedule == null) {
@@ -145,10 +147,7 @@
}
private void rollover() {
- manager.getPatternProcessor().setPrevFileTime(lastRollDate.getTime());
- final Date thisRoll = cronExpression.getPrevFireTime(new Date());
- manager.getPatternProcessor().setCurrentFileTime(thisRoll.getTime());
- manager.rollover();
+ manager.rollover(cronExpression.getPrevFireTime(new Date()).getTime(), lastRollDate.getTime());
if (future != null) {
lastRollDate = future.getFireTime();
}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/DefaultRolloverStrategy.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/DefaultRolloverStrategy.java
index e5298bc..182afcb 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/DefaultRolloverStrategy.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/DefaultRolloverStrategy.java
@@ -16,6 +16,21 @@
*/
package org.apache.logging.log4j.core.appender.rolling;
+import org.apache.logging.log4j.core.Core;
+import org.apache.logging.log4j.core.appender.rolling.action.Action;
+import org.apache.logging.log4j.core.appender.rolling.action.CompositeAction;
+import org.apache.logging.log4j.core.appender.rolling.action.FileRenameAction;
+import org.apache.logging.log4j.core.appender.rolling.action.PathCondition;
+import org.apache.logging.log4j.core.appender.rolling.action.PosixViewAttributeAction;
+import org.apache.logging.log4j.core.config.Configuration;
+import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
+import org.apache.logging.log4j.core.lookup.StrSubstitutor;
+import org.apache.logging.log4j.core.util.Integers;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginBuilderAttribute;
+import org.apache.logging.log4j.plugins.PluginElement;
+import org.apache.logging.log4j.plugins.PluginFactory;
+
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
@@ -28,23 +43,6 @@
import java.util.concurrent.TimeUnit;
import java.util.zip.Deflater;
-import org.apache.logging.log4j.core.Core;
-import org.apache.logging.log4j.core.appender.rolling.action.Action;
-import org.apache.logging.log4j.core.appender.rolling.action.CompositeAction;
-import org.apache.logging.log4j.core.appender.rolling.action.FileRenameAction;
-import org.apache.logging.log4j.core.appender.rolling.action.PathCondition;
-import org.apache.logging.log4j.core.appender.rolling.action.PosixViewAttributeAction;
-import org.apache.logging.log4j.core.config.Configuration;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
-import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
-import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
-import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
-import org.apache.logging.log4j.core.config.plugins.PluginElement;
-import org.apache.logging.log4j.core.config.plugins.PluginFactory;
-import org.apache.logging.log4j.core.lookup.StrSubstitutor;
-import org.apache.logging.log4j.core.util.Integers;
-
/**
* When rolling over, <code>DefaultRolloverStrategy</code> renames files according to an algorithm as described below.
*
@@ -87,7 +85,7 @@
/**
* Builds DefaultRolloverStrategy instances.
*/
- public static class Builder implements org.apache.logging.log4j.core.util.Builder<DefaultRolloverStrategy> {
+ public static class Builder implements org.apache.logging.log4j.plugins.util.Builder<DefaultRolloverStrategy> {
@PluginBuilderAttribute("max")
private String max;
@@ -158,7 +156,7 @@
* @param max The maximum number of files to keep.
* @return This builder for chaining convenience
*/
- public Builder withMax(final String max) {
+ public Builder setMax(final String max) {
this.max = max;
return this;
}
@@ -173,7 +171,7 @@
* @param min The minimum number of files to keep.
* @return This builder for chaining convenience
*/
- public Builder withMin(final String min) {
+ public Builder setMin(final String min) {
this.min = min;
return this;
}
@@ -189,7 +187,7 @@
* index. If set to "min", file renaming and the counter will follow the Fixed Window strategy.
* @return This builder for chaining convenience
*/
- public Builder withFileIndex(final String fileIndex) {
+ public Builder setFileIndex(final String fileIndex) {
this.fileIndex = fileIndex;
return this;
}
@@ -204,7 +202,7 @@
* @param compressionLevelStr The compression level, 0 (less) through 9 (more); applies only to ZIP files.
* @return This builder for chaining convenience
*/
- public Builder withCompressionLevelStr(final String compressionLevelStr) {
+ public Builder setCompressionLevelStr(final String compressionLevelStr) {
this.compressionLevelStr = compressionLevelStr;
return this;
}
@@ -219,7 +217,7 @@
* @param customActions custom actions to perform asynchronously after rollover
* @return This builder for chaining convenience
*/
- public Builder withCustomActions(final Action[] customActions) {
+ public Builder setCustomActions(final Action... customActions) {
this.customActions = customActions;
return this;
}
@@ -234,7 +232,7 @@
* @param stopCustomActionsOnError whether to stop executing asynchronous actions if an error occurs
* @return This builder for chaining convenience
*/
- public Builder withStopCustomActionsOnError(final boolean stopCustomActionsOnError) {
+ public Builder setStopCustomActionsOnError(final boolean stopCustomActionsOnError) {
this.stopCustomActionsOnError = stopCustomActionsOnError;
return this;
}
@@ -249,7 +247,7 @@
* @param tempCompressedFilePattern File pattern of the working file pattern used during compression, if null no temporary file are used
* @return This builder for chaining convenience
*/
- public Builder withTempCompressedFilePattern(final String tempCompressedFilePattern) {
+ public Builder setTempCompressedFilePattern(final String tempCompressedFilePattern) {
this.tempCompressedFilePattern = tempCompressedFilePattern;
return this;
}
@@ -264,56 +262,18 @@
* @param config The Configuration.
* @return This builder for chaining convenience
*/
- public Builder withConfig(final Configuration config) {
+ public Builder setConfig(final Configuration config) {
this.config = config;
return this;
}
}
- @PluginBuilderFactory
+ @PluginFactory
public static Builder newBuilder() {
return new Builder();
}
/**
- * Creates the DefaultRolloverStrategy.
- *
- * @param max The maximum number of files to keep.
- * @param min The minimum number of files to keep.
- * @param fileIndex If set to "max" (the default), files with a higher index will be newer than files with a smaller
- * index. If set to "min", file renaming and the counter will follow the Fixed Window strategy.
- * @param compressionLevelStr The compression level, 0 (less) through 9 (more); applies only to ZIP files.
- * @param customActions custom actions to perform asynchronously after rollover
- * @param stopCustomActionsOnError whether to stop executing asynchronous actions if an error occurs
- * @param config The Configuration.
- * @return A DefaultRolloverStrategy.
- * @deprecated Since 2.9 Usage of Builder API is preferable
- */
- @PluginFactory
- @Deprecated
- public static DefaultRolloverStrategy createStrategy(
- // @formatter:off
- @PluginAttribute("max") final String max,
- @PluginAttribute("min") final String min,
- @PluginAttribute("fileIndex") final String fileIndex,
- @PluginAttribute("compressionLevel") final String compressionLevelStr,
- @PluginElement("Actions") final Action[] customActions,
- @PluginAttribute(value = "stopCustomActionsOnError", defaultBoolean = true)
- final boolean stopCustomActionsOnError,
- @PluginConfiguration final Configuration config) {
- return DefaultRolloverStrategy.newBuilder()
- .withMin(min)
- .withMax(max)
- .withFileIndex(fileIndex)
- .withCompressionLevelStr(compressionLevelStr)
- .withCustomActions(customActions)
- .withStopCustomActionsOnError(stopCustomActionsOnError)
- .withConfig(config)
- .build();
- // @formatter:on
- }
-
- /**
* Index for oldest retained log file.
*/
private final int maxIndex;
@@ -335,23 +295,6 @@
* @param maxIndex The maximum index.
* @param customActions custom actions to perform asynchronously after rollover
* @param stopCustomActionsOnError whether to stop executing asynchronous actions if an error occurs
- * @deprecated Since 2.9 Added tempCompressedFilePatternString parameter
- */
- @Deprecated
- protected DefaultRolloverStrategy(final int minIndex, final int maxIndex, final boolean useMax,
- final int compressionLevel, final StrSubstitutor strSubstitutor, final Action[] customActions,
- final boolean stopCustomActionsOnError) {
- this(minIndex, maxIndex, useMax, compressionLevel,
- strSubstitutor, customActions, stopCustomActionsOnError, null);
- }
-
- /**
- * Constructs a new instance.
- *
- * @param minIndex The minimum index.
- * @param maxIndex The maximum index.
- * @param customActions custom actions to perform asynchronously after rollover
- * @param stopCustomActionsOnError whether to stop executing asynchronous actions if an error occurs
* @param tempCompressedFilePatternString File pattern of the working file
* used during compression, if null no temporary file are used
*/
@@ -414,7 +357,7 @@
final SortedMap<Integer, Path> eligibleFiles = getEligibleFiles(manager);
final int maxFiles = highIndex - lowIndex + 1;
- boolean renameFiles = false;
+ boolean renameFiles = !eligibleFiles.isEmpty() && eligibleFiles.lastKey() >= maxIndex;
while (eligibleFiles.size() >= maxFiles) {
try {
LOGGER.debug("Eligible files: {}", eligibleFiles);
@@ -470,6 +413,7 @@
// Retrieve the files in descending order, so the highest key will be first.
final SortedMap<Integer, Path> eligibleFiles = getEligibleFiles(manager, false);
final int maxFiles = highIndex - lowIndex + 1;
+ LOGGER.debug("Eligible files: {}", eligibleFiles);
while (eligibleFiles.size() >= maxFiles) {
try {
@@ -517,9 +461,11 @@
@Override
public RolloverDescription rollover(final RollingFileManager manager) throws SecurityException {
int fileIndex;
+ final StringBuilder buf = new StringBuilder(255);
if (minIndex == Integer.MIN_VALUE) {
final SortedMap<Integer, Path> eligibleFiles = getEligibleFiles(manager);
fileIndex = eligibleFiles.size() > 0 ? eligibleFiles.lastKey() + 1 : 1;
+ manager.getPatternProcessor().formatFileName(strSubstitutor, buf, fileIndex);
} else {
if (maxIndex < 0) {
return null;
@@ -529,13 +475,13 @@
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();
@@ -576,14 +522,14 @@
// Propagate posix attribute view to compressed file
// @formatter:off
final Action posixAttributeViewAction = PosixViewAttributeAction.newBuilder()
- .withBasePath(compressedName)
- .withFollowLinks(false)
- .withMaxDepth(1)
- .withPathConditions(new PathCondition[0])
- .withSubst(getStrSubstitutor())
- .withFilePermissions(manager.getFilePermissions())
- .withFileOwner(manager.getFileOwner())
- .withFileGroup(manager.getFileGroup())
+ .setBasePath(compressedName)
+ .setFollowLinks(false)
+ .setMaxDepth(1)
+ .setPathConditions(new PathCondition[0])
+ .setSubst(getStrSubstitutor())
+ .setFilePermissions(manager.getFilePermissions())
+ .setFileOwner(manager.getFileOwner())
+ .setFileGroup(manager.getFileGroup())
.build();
// @formatter:on
compressAction = new CompositeAction(Arrays.asList(compressAction, posixAttributeViewAction), false);
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/DirectFileRolloverStrategy.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/DirectFileRolloverStrategy.java
index 4d27f8d..3daeee3 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/DirectFileRolloverStrategy.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/DirectFileRolloverStrategy.java
@@ -22,4 +22,6 @@
public interface DirectFileRolloverStrategy {
String getCurrentFileName(final RollingFileManager manager);
+
+ void clearCurrentFileName();
}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/DirectWriteRolloverStrategy.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/DirectWriteRolloverStrategy.java
index b1ee506..fe4159e 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/DirectWriteRolloverStrategy.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/DirectWriteRolloverStrategy.java
@@ -16,6 +16,21 @@
*/
package org.apache.logging.log4j.core.appender.rolling;
+import org.apache.logging.log4j.core.Core;
+import org.apache.logging.log4j.core.appender.rolling.action.Action;
+import org.apache.logging.log4j.core.appender.rolling.action.CompositeAction;
+import org.apache.logging.log4j.core.appender.rolling.action.FileRenameAction;
+import org.apache.logging.log4j.core.appender.rolling.action.PathCondition;
+import org.apache.logging.log4j.core.appender.rolling.action.PosixViewAttributeAction;
+import org.apache.logging.log4j.core.config.Configuration;
+import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
+import org.apache.logging.log4j.core.lookup.StrSubstitutor;
+import org.apache.logging.log4j.core.util.Integers;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginBuilderAttribute;
+import org.apache.logging.log4j.plugins.PluginElement;
+import org.apache.logging.log4j.plugins.PluginFactory;
+
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
@@ -27,23 +42,6 @@
import java.util.concurrent.TimeUnit;
import java.util.zip.Deflater;
-import org.apache.logging.log4j.core.Core;
-import org.apache.logging.log4j.core.appender.rolling.action.Action;
-import org.apache.logging.log4j.core.appender.rolling.action.CompositeAction;
-import org.apache.logging.log4j.core.appender.rolling.action.FileRenameAction;
-import org.apache.logging.log4j.core.appender.rolling.action.PathCondition;
-import org.apache.logging.log4j.core.appender.rolling.action.PosixViewAttributeAction;
-import org.apache.logging.log4j.core.config.Configuration;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
-import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
-import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
-import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
-import org.apache.logging.log4j.core.config.plugins.PluginElement;
-import org.apache.logging.log4j.core.config.plugins.PluginFactory;
-import org.apache.logging.log4j.core.lookup.StrSubstitutor;
-import org.apache.logging.log4j.core.util.Integers;
-
/**
* When rolling over, <code>DirectWriteRolloverStrategy</code> writes directly to the file as resolved by the file
* pattern. Files will be renamed files according to an algorithm as described below.
@@ -64,7 +62,7 @@
/**
* Builds DirectWriteRolloverStrategy instances.
*/
- public static class Builder implements org.apache.logging.log4j.core.util.Builder<DirectWriteRolloverStrategy> {
+ public static class Builder implements org.apache.logging.log4j.plugins.util.Builder<DirectWriteRolloverStrategy> {
@PluginBuilderAttribute("maxFiles")
private String maxFiles;
@@ -110,7 +108,7 @@
* @param maxFiles The maximum number of files that match the date portion of the pattern to keep.
* @return This builder for chaining convenience
*/
- public Builder withMaxFiles(final String maxFiles) {
+ public Builder setMaxFiles(final String maxFiles) {
this.maxFiles = maxFiles;
return this;
}
@@ -125,7 +123,7 @@
* @param compressionLevelStr The compression level, 0 (less) through 9 (more); applies only to ZIP files.
* @return This builder for chaining convenience
*/
- public Builder withCompressionLevelStr(final String compressionLevelStr) {
+ public Builder setCompressionLevelStr(final String compressionLevelStr) {
this.compressionLevelStr = compressionLevelStr;
return this;
}
@@ -140,7 +138,7 @@
* @param customActions custom actions to perform asynchronously after rollover
* @return This builder for chaining convenience
*/
- public Builder withCustomActions(final Action[] customActions) {
+ public Builder setCustomActions(final Action... customActions) {
this.customActions = customActions;
return this;
}
@@ -155,7 +153,7 @@
* @param stopCustomActionsOnError whether to stop executing asynchronous actions if an error occurs
* @return This builder for chaining convenience
*/
- public Builder withStopCustomActionsOnError(final boolean stopCustomActionsOnError) {
+ public Builder setStopCustomActionsOnError(final boolean stopCustomActionsOnError) {
this.stopCustomActionsOnError = stopCustomActionsOnError;
return this;
}
@@ -170,7 +168,7 @@
* @param tempCompressedFilePattern File pattern of the working file pattern used during compression, if null no temporary file are used
* @return This builder for chaining convenience
*/
- public Builder withTempCompressedFilePattern(final String tempCompressedFilePattern) {
+ public Builder setTempCompressedFilePattern(final String tempCompressedFilePattern) {
this.tempCompressedFilePattern = tempCompressedFilePattern;
return this;
}
@@ -185,48 +183,18 @@
* @param config The Configuration.
* @return This builder for chaining convenience
*/
- public Builder withConfig(final Configuration config) {
+ public Builder setConfig(final Configuration config) {
this.config = config;
return this;
}
}
- @PluginBuilderFactory
+ @PluginFactory
public static Builder newBuilder() {
return new Builder();
}
/**
- * Creates the DirectWriteRolloverStrategy.
- *
- * @param maxFiles The maximum number of files that match the date portion of the pattern to keep.
- * @param compressionLevelStr The compression level, 0 (less) through 9 (more); applies only to ZIP files.
- * @param customActions custom actions to perform asynchronously after rollover
- * @param stopCustomActionsOnError whether to stop executing asynchronous actions if an error occurs
- * @param config The Configuration.
- * @return A DirectWriteRolloverStrategy.
- * @deprecated Since 2.9 Usage of Builder API is preferable
- */
- @Deprecated
- @PluginFactory
- public static DirectWriteRolloverStrategy createStrategy(
- // @formatter:off
- @PluginAttribute("maxFiles") final String maxFiles,
- @PluginAttribute("compressionLevel") final String compressionLevelStr,
- @PluginElement("Actions") final Action[] customActions,
- @PluginAttribute(value = "stopCustomActionsOnError", defaultBoolean = true)
- final boolean stopCustomActionsOnError,
- @PluginConfiguration final Configuration config) {
- return newBuilder().withMaxFiles(maxFiles)
- .withCompressionLevelStr(compressionLevelStr)
- .withCustomActions(customActions)
- .withStopCustomActionsOnError(stopCustomActionsOnError)
- .withConfig(config)
- .build();
- // @formatter:on
- }
-
- /**
* Index for most recent log file.
*/
private final int maxFiles;
@@ -243,21 +211,6 @@
* @param maxFiles The maximum number of files that match the date portion of the pattern to keep.
* @param customActions custom actions to perform asynchronously after rollover
* @param stopCustomActionsOnError whether to stop executing asynchronous actions if an error occurs
- * @deprecated Since 2.9 Added tempCompressedFilePatternString parameter
- */
- @Deprecated
- protected DirectWriteRolloverStrategy(final int maxFiles, final int compressionLevel,
- final StrSubstitutor strSubstitutor, final Action[] customActions,
- final boolean stopCustomActionsOnError) {
- this(maxFiles, compressionLevel, strSubstitutor, customActions, stopCustomActionsOnError, null);
- }
-
- /**
- * Constructs a new instance.
- *
- * @param maxFiles The maximum number of files that match the date portion of the pattern to keep.
- * @param customActions custom actions to perform asynchronously after rollover
- * @param stopCustomActionsOnError whether to stop executing asynchronous actions if an error occurs
* @param tempCompressedFilePatternString File pattern of the working file
* used during compression, if null no temporary file are used
*/
@@ -323,6 +276,11 @@
return currentFileName;
}
+ @Override
+ public void clearCurrentFileName() {
+ currentFileName = null;
+ }
+
/**
* Performs the rollover.
*
@@ -375,14 +333,14 @@
// Propagate posix attribute view to compressed file
// @formatter:off
final Action posixAttributeViewAction = PosixViewAttributeAction.newBuilder()
- .withBasePath(compressedName)
- .withFollowLinks(false)
- .withMaxDepth(1)
- .withPathConditions(new PathCondition[0])
- .withSubst(getStrSubstitutor())
- .withFilePermissions(manager.getFilePermissions())
- .withFileOwner(manager.getFileOwner())
- .withFileGroup(manager.getFileGroup())
+ .setBasePath(compressedName)
+ .setFollowLinks(false)
+ .setMaxDepth(1)
+ .setPathConditions(new PathCondition[0])
+ .setSubst(getStrSubstitutor())
+ .setFilePermissions(manager.getFilePermissions())
+ .setFileOwner(manager.getFileOwner())
+ .setFileGroup(manager.getFileGroup())
.build();
// @formatter:on
compressAction = new CompositeAction(Arrays.asList(compressAction, posixAttributeViewAction), false);
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/FileExtension.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/FileExtension.java
index 9d0016f..d12d26a 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/FileExtension.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/FileExtension.java
@@ -39,7 +39,7 @@
@Override
Action createCompressAction(final String renameTo, final String compressedName, final boolean deleteSource,
final int compressionLevel) {
- return new GzCompressAction(source(renameTo), target(compressedName), deleteSource);
+ return new GzCompressAction(source(renameTo), target(compressedName), deleteSource, compressionLevel);
}
},
BZIP2(".bz2") {
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 bd5dce8..f95dc8e 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
@@ -61,7 +61,7 @@
if (matcher.matches()) {
try {
// Get double precision value
- final long value = NumberFormat.getNumberInstance(Locale.getDefault()).parse(
+ final long value = NumberFormat.getNumberInstance(Locale.ROOT).parse(
matcher.group(1)).longValue();
// Get units specified
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
index 149ae4f..6ae08eb 100644
--- 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
@@ -19,8 +19,8 @@
import org.apache.logging.log4j.core.Core;
import org.apache.logging.log4j.core.LogEvent;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginFactory;
/*
* Never triggers and is handy for edge-cases in tests for example.
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 4c8ae1c..d7a2c74 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
@@ -20,9 +20,9 @@
import org.apache.logging.log4j.core.Core;
import org.apache.logging.log4j.core.LogEvent;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
-import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginAttribute;
+import org.apache.logging.log4j.plugins.PluginFactory;
import org.apache.logging.log4j.core.util.Loader;
import org.apache.logging.log4j.status.StatusLogger;
@@ -76,6 +76,7 @@
@Override
public void initialize(final RollingFileManager manager) {
if (manager.getFileTime() < JVM_START_TIME && manager.getFileSize() >= minSize) {
+ StatusLogger.getLogger().debug("Initiating rollover at startup");
if (minSize == 0) {
manager.setRenameEmptyFiles(true);
}
@@ -102,7 +103,7 @@
@PluginFactory
public static OnStartupTriggeringPolicy createPolicy(
- @PluginAttribute(value = "minSize", defaultLong = 1) final long minSize) {
+ @PluginAttribute(defaultLong = 1) final long minSize) {
return new OnStartupTriggeringPolicy(minSize);
}
}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/PatternProcessor.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/PatternProcessor.java
index b5a5aa3..e56fd59 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
@@ -28,6 +28,7 @@
import org.apache.logging.log4j.core.lookup.StrSubstitutor;
import org.apache.logging.log4j.core.pattern.ArrayPatternConverter;
import org.apache.logging.log4j.core.pattern.DatePatternConverter;
+import org.apache.logging.log4j.core.pattern.FileDatePatternConverter;
import org.apache.logging.log4j.core.pattern.FormattingInfo;
import org.apache.logging.log4j.core.pattern.PatternConverter;
import org.apache.logging.log4j.core.pattern.PatternParser;
@@ -52,11 +53,14 @@
private final ArrayPatternConverter[] patternConverters;
private final FormattingInfo[] patternFields;
+ private final FileExtension fileExtension;
private long prevFileTime = 0;
private long nextFileTime = 0;
private long currentFileTime = 0;
+ private boolean isTimeBased = false;
+
private RolloverFrequency frequency = null;
private final String pattern;
@@ -77,6 +81,7 @@
public PatternProcessor(final String pattern) {
this.pattern = pattern;
final PatternParser parser = createPatternParser();
+ // FIXME: this seems to expect List<ArrayPatternConverter> in practice; types need to be fixed around this
final List<PatternConverter> converters = new ArrayList<>();
final List<FormattingInfo> fields = new ArrayList<>();
parser.parse(pattern, converters, fields, false, false, false);
@@ -84,11 +89,15 @@
patternFields = fields.toArray(infoArray);
final ArrayPatternConverter[] converterArray = new ArrayPatternConverter[converters.size()];
patternConverters = converters.toArray(converterArray);
+ this.fileExtension = FileExtension.lookupForFile(pattern);
for (final ArrayPatternConverter converter : patternConverters) {
+ // TODO: extract common interface
if (converter instanceof DatePatternConverter) {
final DatePatternConverter dateConverter = (DatePatternConverter) converter;
frequency = calculateFrequency(dateConverter.getPattern());
+ } else if (converter instanceof FileDatePatternConverter) {
+ frequency = calculateFrequency(((FileDatePatternConverter) converter).getPattern());
}
}
}
@@ -106,6 +115,10 @@
this.currentFileTime = copy.currentFileTime;
}
+ public void setTimeBased(boolean isTimeBased) {
+ this.isTimeBased = isTimeBased;
+ }
+
public long getCurrentFileTime() {
return currentFileTime;
}
@@ -123,6 +136,10 @@
this.prevFileTime = prevFileTime;
}
+ public FileExtension getFileExtension() {
+ return fileExtension;
+ }
+
/**
* Returns the next potential rollover time.
* @param currentMillis The current time.
@@ -213,7 +230,9 @@
}
public void updateTime() {
- prevFileTime = nextFileTime;
+ if (nextFileTime != 0 || !isTimeBased) {
+ prevFileTime = nextFileTime;
+ }
}
private long debugGetNextTime(final long nextTime) {
@@ -266,7 +285,9 @@
final Object obj) {
// LOG4J2-628: we deliberately use System time, not the log4j.Clock time
// for creating the file name of rolled-over files.
- final long time = useCurrentTime && currentFileTime != 0 ? currentFileTime :
+ LOGGER.debug("Formatting file name. useCurrentTime={}, currentFileTime={}, prevFileTime={}",
+ 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();
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 87d2cb5..e95dc85 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/RollingFileManager.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/RollingFileManager.java
@@ -22,7 +22,12 @@
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.ExecutorService;
import java.util.concurrent.Semaphore;
@@ -32,7 +37,6 @@
import org.apache.logging.log4j.core.Layout;
import org.apache.logging.log4j.core.LifeCycle;
-import org.apache.logging.log4j.core.LifeCycle2;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.appender.ConfigurationFactoryData;
@@ -50,9 +54,10 @@
*/
public class RollingFileManager extends FileManager {
- private static RollingFileManagerFactory factory = new RollingFileManagerFactory();
+ private static final RollingFileManagerFactory factory = new RollingFileManagerFactory();
private static final int MAX_TRIES = 3;
private static final int MIN_DURATION = 100;
+ private static final FileTime EPOCH = FileTime.fromMillis(0);
protected long size;
private long initialTime;
@@ -61,10 +66,10 @@
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;
/* This executor pool will create a new Thread for every work async action to be performed. Using it allows
us to make sure all the Threads are completed when the Manager is stopped. */
@@ -80,66 +85,25 @@
private static final AtomicReferenceFieldUpdater<RollingFileManager, PatternProcessor> patternProcessorUpdater =
AtomicReferenceFieldUpdater.newUpdater(RollingFileManager.class, PatternProcessor.class, "patternProcessor");
- @Deprecated
- protected RollingFileManager(final String fileName, final String pattern, final OutputStream os,
- final boolean append, final long size, final long time, final TriggeringPolicy triggeringPolicy,
- final RolloverStrategy rolloverStrategy, final String advertiseURI,
- final Layout<? extends Serializable> layout, final int bufferSize, final boolean writeHeader) {
- this(fileName, pattern, os, append, size, time, triggeringPolicy, rolloverStrategy, advertiseURI, layout,
- writeHeader, ByteBuffer.wrap(new byte[Constants.ENCODER_BYTE_BUFFER_SIZE]));
- }
-
- @Deprecated
- protected RollingFileManager(final String fileName, final String pattern, final OutputStream os,
- final boolean append, final long size, final long time, final TriggeringPolicy triggeringPolicy,
- final RolloverStrategy rolloverStrategy, final String advertiseURI,
- final Layout<? extends Serializable> layout, final boolean writeHeader, final ByteBuffer buffer) {
- super(fileName, os, append, false, advertiseURI, layout, writeHeader, buffer);
- this.size = size;
- this.initialTime = time;
- this.triggeringPolicy = triggeringPolicy;
- this.rolloverStrategy = rolloverStrategy;
- this.patternProcessor = new PatternProcessor(pattern);
- this.patternProcessor.setPrevFileTime(time);
- this.fileName = fileName;
- this.fileExtension = FileExtension.lookupForFile(pattern);
- }
-
- @Deprecated
- protected RollingFileManager(final LoggerContext loggerContext, final String fileName, final String pattern, final OutputStream os,
- final boolean append, final boolean createOnDemand, final long size, final long time,
- final TriggeringPolicy triggeringPolicy, final RolloverStrategy rolloverStrategy,
- final String advertiseURI, final Layout<? extends Serializable> layout, final boolean writeHeader, final ByteBuffer buffer) {
- super(loggerContext, fileName, os, append, false, createOnDemand, advertiseURI, layout, writeHeader, buffer);
- this.size = size;
- this.initialTime = time;
- this.triggeringPolicy = triggeringPolicy;
- this.rolloverStrategy = rolloverStrategy;
- this.patternProcessor = new PatternProcessor(pattern);
- this.patternProcessor.setPrevFileTime(time);
- this.fileName = fileName;
- this.fileExtension = FileExtension.lookupForFile(pattern);
- }
-
/**
* @since 2.9
*/
protected RollingFileManager(final LoggerContext loggerContext, final String fileName, final String pattern, final OutputStream os,
- final boolean append, final boolean createOnDemand, final long size, final long time,
+ final boolean append, final boolean createOnDemand, final long size, final long initialTime,
final TriggeringPolicy triggeringPolicy, final RolloverStrategy rolloverStrategy,
final String advertiseURI, final Layout<? extends Serializable> layout,
final String filePermissions, final String fileOwner, final String fileGroup,
final boolean writeHeader, final ByteBuffer buffer) {
- super(loggerContext, fileName, os, append, false, createOnDemand, advertiseURI, layout,
- filePermissions, fileOwner, fileGroup, writeHeader, buffer);
+ super(loggerContext, fileName != null ? fileName : pattern, os, append, false, createOnDemand,
+ advertiseURI, layout, filePermissions, fileOwner, fileGroup, writeHeader, buffer);
this.size = size;
- this.initialTime = time;
+ this.initialTime = initialTime;
this.triggeringPolicy = triggeringPolicy;
this.rolloverStrategy = rolloverStrategy;
this.patternProcessor = new PatternProcessor(pattern);
- this.patternProcessor.setPrevFileTime(time);
+ this.patternProcessor.setPrevFileTime(initialTime);
this.fileName = fileName;
- this.fileExtension = FileExtension.lookupForFile(pattern);
+ this.directWrite = rolloverStrategy instanceof DirectFileRolloverStrategy;
}
public void initialize() {
@@ -151,6 +115,15 @@
if (triggeringPolicy instanceof LifeCycle) {
((LifeCycle) triggeringPolicy).start();
}
+ if (directWrite) {
+ // LOG4J2-2485: Initialize size from the most recently written file.
+ File file = new File(getFileName());
+ if (file.exists()) {
+ size = file.length();
+ } else {
+ ((DirectFileRolloverStrategy) rolloverStrategy).clearCurrentFileName();
+ }
+ }
}
}
@@ -196,14 +169,18 @@
*/
@Override
public String getFileName() {
- if (rolloverStrategy instanceof DirectFileRolloverStrategy) {
+ if (directWrite) {
fileName = ((DirectFileRolloverStrategy) rolloverStrategy).getCurrentFileName(this);
}
return fileName;
}
+ public boolean isDirectWrite() {
+ return directWrite;
+ }
+
public FileExtension getFileExtension() {
- return fileExtension;
+ return patternProcessor.getFileExtension();
}
// override to make visible for unit tests
@@ -257,12 +234,7 @@
public boolean releaseSub(final long timeout, final TimeUnit timeUnit) {
LOGGER.debug("Shutting down RollingFileManager {}", getName());
boolean stopped = true;
- if (triggeringPolicy instanceof LifeCycle2) {
- stopped &= ((LifeCycle2) triggeringPolicy).stop(timeout, timeUnit);
- } else if (triggeringPolicy instanceof LifeCycle) {
- ((LifeCycle) triggeringPolicy).stop();
- stopped &= true;
- }
+ stopped &= ((LifeCycle) triggeringPolicy).stop(timeout, timeUnit);
final boolean status = super.releaseSub(timeout, timeUnit) && stopped;
asyncExecutor.shutdown();
try {
@@ -305,8 +277,14 @@
return status;
}
+ public synchronized void rollover(long prevFileTime, long prevRollTime) {
+ getPatternProcessor().setPrevFileTime(prevFileTime);
+ getPatternProcessor().setCurrentFileTime(prevRollTime);
+ rollover();
+ }
+
public synchronized void rollover() {
- if (!hasOutputStream()) {
+ if (!hasOutputStream() && !isCreateOnDemand()) {
return;
}
if (rollover(rolloverStrategy)) {
@@ -594,12 +572,18 @@
}
}
+ /**
+ * Updates the RollingFileManager's data during a reconfiguration. This method should be considered private.
+ * It is not thread safe and calling it outside of a reconfiguration may lead to errors. This method may be
+ * made protected in a future release.
+ * @param data The data to update.
+ */
@Override
public void updateData(final Object data) {
final FactoryData factoryData = (FactoryData) data;
setRolloverStrategy(factoryData.getRolloverStrategy());
- setTriggeringPolicy(factoryData.getTriggeringPolicy());
setPatternProcessor(new PatternProcessor(factoryData.getPattern(), getPatternProcessor()));
+ setTriggeringPolicy(factoryData.getTriggeringPolicy());
}
/**
@@ -616,12 +600,9 @@
@Override
public RollingFileManager createManager(final String name, final FactoryData data) {
long size = 0;
- boolean writeHeader = !data.append;
File file = null;
if (data.fileName != null) {
file = new File(data.fileName);
- // LOG4J2-1140: check writeHeader before creating the file
- writeHeader = !data.append || !file.exists();
try {
FileUtils.makeParentDirs(file);
@@ -639,11 +620,12 @@
final ByteBuffer buffer = ByteBuffer.wrap(new byte[actualSize]);
final OutputStream os = data.createOnDemand || data.fileName == null ? null :
new FileOutputStream(data.fileName, data.append);
- final long time = data.createOnDemand || file == null ?
- System.currentTimeMillis() : file.lastModified(); // LOG4J2-531 create file first so time has valid value
+ // LOG4J2-531 create file first so time has valid value.
+ final long initialTime = file == null || !file.exists() ? 0 : initialFileTime(file);
+ final boolean writeHeader = file != null && file.exists() && file.length() == 0;
final RollingFileManager rm = new RollingFileManager(data.getLoggerContext(), data.fileName, data.pattern, os,
- data.append, data.createOnDemand, size, time, data.policy, data.strategy, data.advertiseURI,
+ data.append, data.createOnDemand, size, initialTime, data.policy, data.strategy, data.advertiseURI,
data.layout, data.filePermissions, data.fileOwner, data.fileGroup, writeHeader, buffer);
if (os != null && rm.isAttributeViewEnabled()) {
rm.defineAttributeView(file.toPath());
@@ -657,6 +639,23 @@
}
}
+ private static long initialFileTime(File file) {
+ Path path = file.toPath();
+ if (Files.exists(path)) {
+ try {
+ BasicFileAttributes attrs = Files.readAttributes(path, BasicFileAttributes.class);
+ FileTime fileTime = attrs.creationTime();
+ if (fileTime.compareTo(EPOCH) > 0) {
+ return fileTime.toMillis();
+ }
+ LOGGER.info("Unable to obtain file creation time for " + file.getAbsolutePath());
+ } catch (Exception ex) {
+ LOGGER.info("Unable to calculate file creation time for " + file.getAbsolutePath() + ": " + ex.getMessage());
+ }
+ }
+ return file.lastModified();
+ }
+
private static class EmptyQueue extends ArrayBlockingQueue<Runnable> {
/**
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 e8b6cb9..7f7bf85 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/RollingRandomAccessFileManager.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/RollingRandomAccessFileManager.java
@@ -22,6 +22,7 @@
import java.io.RandomAccessFile;
import java.io.Serializable;
import java.nio.ByteBuffer;
+import java.nio.file.Paths;
import org.apache.logging.log4j.core.Layout;
import org.apache.logging.log4j.core.LoggerContext;
@@ -47,27 +48,17 @@
private RandomAccessFile randomAccessFile;
private final ThreadLocal<Boolean> 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<? extends Serializable> layout, final boolean writeHeader) {
- this(loggerContext, raf, fileName, pattern, os, append, immediateFlush, bufferSize, size, time, policy, strategy, advertiseURI,
- layout, null, null, null, writeHeader);
- }
-
/**
* @since 2.8.3
*/
public RollingRandomAccessFileManager(final LoggerContext loggerContext, final RandomAccessFile raf,
final String fileName, final String pattern, final OutputStream os, final boolean append,
- final boolean immediateFlush, final int bufferSize, final long size, final long time,
+ final boolean immediateFlush, final int bufferSize, final long initialTime, final long time,
final TriggeringPolicy policy, final RolloverStrategy strategy, final String advertiseURI,
final Layout<? extends Serializable> layout,
final String filePermissions, final String fileOwner, final String fileGroup,
final boolean writeHeader) {
- super(loggerContext, fileName, pattern, os, append, false, size, time, policy, strategy, advertiseURI, layout,
+ super(loggerContext, fileName, pattern, os, append, false, initialTime, time, policy, strategy, advertiseURI, layout,
filePermissions, fileOwner, fileGroup,
writeHeader, ByteBuffer.wrap(new byte[bufferSize]));
this.randomAccessFile = raf;
@@ -78,7 +69,7 @@
/**
* Writes the layout's header to the file if it exists.
*/
- private void writeHeader() {
+ protected void writeHeader() {
if (layout == null) {
return;
}
@@ -150,6 +141,9 @@
private void createFileAfterRollover(String fileName) throws IOException {
this.randomAccessFile = new RandomAccessFile(fileName, "rw");
+ if (isAttributeViewEnabled()) {
+ defineAttributeView(Paths.get(fileName));
+ }
if (isAppend()) {
randomAccessFile.seek(randomAccessFile.length());
}
@@ -164,13 +158,16 @@
@Override
public synchronized boolean closeOutputStream() {
flush();
- try {
- randomAccessFile.close();
- return true;
- } catch (final IOException e) {
- logError("Unable to close RandomAccessFile", e);
- return false;
+ if (randomAccessFile != null) {
+ try {
+ randomAccessFile.close();
+ return true;
+ } catch (final IOException e) {
+ logError("Unable to close RandomAccessFile", e);
+ return false;
+ }
}
+ return true;
}
/**
@@ -200,7 +197,7 @@
public RollingRandomAccessFileManager createManager(final String name, final FactoryData data) {
File file = null;
long size = 0;
- long time = System.currentTimeMillis();
+ long initialTime = System.currentTimeMillis();
RandomAccessFile raf = null;
if (data.fileName != null) {
file = new File(name);
@@ -210,7 +207,7 @@
}
size = data.append ? file.length() : 0;
if (file.exists()) {
- time = file.lastModified();
+ initialTime = file.lastModified();
}
try {
FileUtils.makeParentDirs(file);
@@ -238,7 +235,7 @@
final boolean writeHeader = !data.append || file == null || !file.exists();
final RollingRandomAccessFileManager rrm = new RollingRandomAccessFileManager(data.getLoggerContext(), raf, name, data.pattern,
- NullOutputStream.getInstance(), data.append, data.immediateFlush, data.bufferSize, size, time, data.policy,
+ NullOutputStream.getInstance(), data.append, data.immediateFlush, data.bufferSize, size, initialTime, data.policy,
data.strategy, data.advertiseURI, data.layout, data.filePermissions, data.fileOwner, data.fileGroup, writeHeader);
if (rrm.isAttributeViewEnabled()) {
rrm.defineAttributeView(file.toPath());
@@ -301,21 +298,31 @@
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/SizeBasedTriggeringPolicy.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/SizeBasedTriggeringPolicy.java
index f77d571..73245d1 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
@@ -18,9 +18,9 @@
import org.apache.logging.log4j.core.Core;
import org.apache.logging.log4j.core.LogEvent;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
-import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginAttribute;
+import org.apache.logging.log4j.plugins.PluginFactory;
/**
*
@@ -92,7 +92,7 @@
* @return A SizeBasedTriggeringPolicy.
*/
@PluginFactory
- public static SizeBasedTriggeringPolicy createPolicy(@PluginAttribute("size") final String size) {
+ public static SizeBasedTriggeringPolicy createPolicy(@PluginAttribute final String size) {
final long maxSize = size == null ? MAX_FILE_SIZE : FileSize.parse(size, MAX_FILE_SIZE);
return new SizeBasedTriggeringPolicy(maxSize);
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/TimeBasedTriggeringPolicy.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/TimeBasedTriggeringPolicy.java
index 7f6ac79..e2bcb09 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/TimeBasedTriggeringPolicy.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/TimeBasedTriggeringPolicy.java
@@ -16,16 +16,14 @@
*/
package org.apache.logging.log4j.core.appender.rolling;
-import java.util.concurrent.ThreadLocalRandom;
-import java.util.concurrent.TimeUnit;
-
import org.apache.logging.log4j.core.Core;
import org.apache.logging.log4j.core.LogEvent;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
-import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
-import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
-import org.apache.logging.log4j.core.util.Integers;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginBuilderAttribute;
+import org.apache.logging.log4j.plugins.PluginFactory;
+
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.concurrent.TimeUnit;
/**
* Rolls a file over based on time.
@@ -34,7 +32,7 @@
public final class TimeBasedTriggeringPolicy extends AbstractTriggeringPolicy {
- public static class Builder implements org.apache.logging.log4j.core.util.Builder<TimeBasedTriggeringPolicy> {
+ public static class Builder implements org.apache.logging.log4j.plugins.util.Builder<TimeBasedTriggeringPolicy> {
@PluginBuilderAttribute
private int interval = 1;
@@ -63,17 +61,17 @@
return maxRandomDelay;
}
- public Builder withInterval(final int interval){
+ public Builder setInterval(final int interval){
this.interval = interval;
return this;
}
- public Builder withModulate(final boolean modulate){
+ public Builder setModulate(final boolean modulate){
this.modulate = modulate;
return this;
}
- public Builder withMaxRandomDelay(final int maxRandomDelay){
+ public Builder setMaxRandomDelay(final int maxRandomDelay){
this.maxRandomDelay = maxRandomDelay;
return this;
}
@@ -108,12 +106,17 @@
@Override
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);
}
/**
@@ -123,36 +126,17 @@
*/
@Override
public boolean isTriggeringEvent(final LogEvent event) {
- if (manager.getFileSize() == 0) {
- return false;
- }
final long nowMillis = event.getTimeMillis();
if (nowMillis >= nextRolloverMillis) {
nextRolloverMillis = ThreadLocalRandom.current().nextLong(0, 1 + maxRandomDelayMillis)
+ manager.getPatternProcessor().getNextTime(nowMillis, interval, modulate);
+ manager.getPatternProcessor().setCurrentFileTime(System.currentTimeMillis());
return true;
}
return false;
}
- /**
- * Creates a TimeBasedTriggeringPolicy.
- * @param interval The interval between rollovers.
- * @param modulate If true the time will be rounded to occur on a boundary aligned with the increment.
- * @return a TimeBasedTriggeringPolicy.
- * @deprecated Use {@link #newBuilder()}.
- */
- @Deprecated
- public static TimeBasedTriggeringPolicy createPolicy(
- @PluginAttribute("interval") final String interval,
- @PluginAttribute("modulate") final String modulate) {
- return newBuilder()
- .withInterval(Integers.parseInt(interval, 1))
- .withModulate(Boolean.parseBoolean(modulate))
- .build();
- }
-
- @PluginBuilderFactory
+ @PluginFactory
public static TimeBasedTriggeringPolicy.Builder newBuilder() {
return new Builder();
}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/AbstractAction.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/AbstractAction.java
index 633d4bc..4eea0ea 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
@@ -64,8 +64,12 @@
if (!interrupted) {
try {
execute();
- } catch (final IOException ex) {
+ } catch (final RuntimeException | IOException ex) {
reportException(ex);
+ } catch (final Error e) {
+ // reportException takes Exception, widening to Throwable would break custom implementations
+ // so we wrap Errors in RuntimeException for handling.
+ reportException(new RuntimeException(e));
}
complete = true;
@@ -101,6 +105,7 @@
* @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/DeleteAction.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/DeleteAction.java
index 76e9b00..995d8d1 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
@@ -26,11 +26,11 @@
import org.apache.logging.log4j.core.Core;
import org.apache.logging.log4j.core.config.Configuration;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.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.plugins.PluginElement;
+import org.apache.logging.log4j.plugins.PluginFactory;
import org.apache.logging.log4j.core.lookup.StrSubstitutor;
/**
@@ -199,13 +199,13 @@
@PluginFactory
public static DeleteAction createDeleteAction(
// @formatter:off
- @PluginAttribute("basePath") final String basePath,
- @PluginAttribute(value = "followLinks") final boolean followLinks,
- @PluginAttribute(value = "maxDepth", defaultInt = 1) final int maxDepth,
- @PluginAttribute(value = "testMode") final boolean testMode,
- @PluginElement("PathSorter") final PathSorter sorterParameter,
- @PluginElement("PathConditions") final PathCondition[] pathConditions,
- @PluginElement("ScriptCondition") final ScriptCondition scriptCondition,
+ @PluginAttribute final String basePath,
+ @PluginAttribute final boolean followLinks,
+ @PluginAttribute(defaultInt = 1) final int maxDepth,
+ @PluginAttribute final boolean testMode,
+ @PluginElement final PathSorter sorterParameter,
+ @PluginElement final PathCondition[] pathConditions,
+ @PluginElement final ScriptCondition scriptCondition,
@PluginConfiguration final Configuration config) {
// @formatter:on
final PathSorter sorter = sorterParameter == null ? new PathSortByModificationTime(true) : sorterParameter;
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/DeletingVisitor.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/DeletingVisitor.java
index 825e774..78ae1cf 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/DeletingVisitor.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/DeletingVisitor.java
@@ -20,6 +20,7 @@
import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
+import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
@@ -75,6 +76,18 @@
return FileVisitResult.CONTINUE;
}
+ @Override
+ public FileVisitResult visitFileFailed(Path file, 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.
*
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 2538306..3ddca2c 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/FileRenameAction.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/FileRenameAction.java
@@ -19,7 +19,9 @@
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
+import java.nio.file.AtomicMoveNotSupportedException;
import java.nio.file.Files;
+import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
@@ -116,14 +118,11 @@
}
try {
try {
- Files.move(Paths.get(source.getAbsolutePath()), Paths.get(destination.getAbsolutePath()),
- StandardCopyOption.ATOMIC_MOVE, StandardCopyOption.REPLACE_EXISTING);
- LOGGER.trace("Renamed file {} to {} with Files.move", source.getAbsolutePath(),
- destination.getAbsolutePath());
- return true;
+ return moveFile(Paths.get(source.getAbsolutePath()), Paths.get(destination.getAbsolutePath()));
} catch (final IOException exMove) {
- LOGGER.error("Unable to move file {} to {}: {} {}", source.getAbsolutePath(),
- destination.getAbsolutePath(), exMove.getClass().getName(), exMove.getMessage());
+ LOGGER.debug("Unable to move file {} to {}: {} {} - will try to copy and delete",
+ source.getAbsolutePath(), destination.getAbsolutePath(), exMove.getClass().getName(),
+ exMove.getMessage());
boolean result = source.renameTo(destination);
if (!result) {
try {
@@ -139,6 +138,7 @@
exDelete.getClass().getName(), exDelete.getMessage());
try {
new PrintWriter(source.getAbsolutePath()).close();
+ result = true;
LOGGER.trace("Renamed file {} to {} with copy and truncation",
source.getAbsolutePath(), destination.getAbsolutePath());
} catch (final IOException exOwerwrite) {
@@ -173,6 +173,21 @@
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
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 1c9b082..5ee0ff3 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/GzCompressAction.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/GzCompressAction.java
@@ -21,7 +21,9 @@
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.io.OutputStream;
import java.util.Objects;
+import java.util.zip.Deflater;
import java.util.zip.GZIPOutputStream;
/**
@@ -47,20 +49,41 @@
private final boolean deleteSource;
/**
+ * GZIP compression level to use.
+ *
+ * @see Deflater#setLevel(int)
+ */
+ private final int compressionLevel;
+
+ /**
* Create new instance of GzCompressAction.
*
* @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.
*/
- 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 @@
*/
@Override
public boolean execute() throws IOException {
- return execute(source, destination, deleteSource);
+ return execute(source, destination, deleteSource, compressionLevel);
}
/**
@@ -83,13 +106,38 @@
* does not cause an exception to be thrown or affect return value.
* @return true if source file compressed.
* @throws IOException on IO exception.
+ * @deprecated In favor of {@link #execute(File, File, boolean, int)}.
*/
+ @Deprecated
public static boolean execute(final File source, final File destination, final boolean deleteSource)
throws IOException {
+ return execute(source, destination, deleteSource, Deflater.DEFAULT_COMPRESSION);
+ }
+
+ /**
+ * Compress a file.
+ *
+ * @param source file to compress, may not be null.
+ * @param destination compressed file, may not be null.
+ * @param deleteSource if true, attempt to delete file on completion. Failure to delete
+ * does not cause an exception to be thrown or affect return value.
+ * @param compressionLevel
+ * Gzip deflater compression level.
+ * @return true if source file compressed.
+ * @throws IOException on IO exception.
+ */
+ public static boolean execute(
+ final File source,
+ final File destination,
+ final boolean deleteSource,
+ final int compressionLevel) throws IOException {
if (source.exists()) {
try (final FileInputStream fis = new FileInputStream(source);
- final BufferedOutputStream os = new BufferedOutputStream(new GZIPOutputStream(new FileOutputStream(
- destination)))) {
+ final OutputStream fos = new FileOutputStream(destination);
+ final OutputStream gzipOut = new ConfigurableLevelGZIPOutputStream(
+ fos, BUF_SIZE, compressionLevel);
+ // Reduce native invocations by buffering data into GZIPOutputStream
+ final OutputStream os = new BufferedOutputStream(gzipOut, BUF_SIZE)) {
final byte[] inbuf = new byte[BUF_SIZE];
int n;
@@ -99,7 +147,7 @@
}
if (deleteSource && !source.delete()) {
- LOGGER.warn("Unable to delete " + source.toString() + '.');
+ LOGGER.warn("Unable to delete {}.", source);
}
return true;
@@ -108,6 +156,13 @@
return false;
}
+ private static final class ConfigurableLevelGZIPOutputStream extends GZIPOutputStream {
+
+ ConfigurableLevelGZIPOutputStream(OutputStream out, int bufSize, int level) throws IOException {
+ super(out, bufSize);
+ def.setLevel(level);
+ }
+ }
/**
* Capture exception.
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 b47954f..6c83a76 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
@@ -24,10 +24,10 @@
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.core.Core;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
-import org.apache.logging.log4j.core.config.plugins.PluginElement;
-import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginAttribute;
+import org.apache.logging.log4j.plugins.PluginElement;
+import org.apache.logging.log4j.plugins.PluginFactory;
import org.apache.logging.log4j.status.StatusLogger;
/**
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 7c1d908..7bad7d7 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
@@ -25,10 +25,10 @@
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.core.Core;
import org.apache.logging.log4j.core.appender.rolling.FileSize;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
-import org.apache.logging.log4j.core.config.plugins.PluginElement;
-import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginAttribute;
+import org.apache.logging.log4j.plugins.PluginElement;
+import org.apache.logging.log4j.plugins.PluginFactory;
import org.apache.logging.log4j.status.StatusLogger;
/**
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 2eaea8b..69a38bf 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
@@ -22,9 +22,9 @@
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.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginElement;
+import org.apache.logging.log4j.plugins.PluginFactory;
/**
* Composite {@code PathCondition} that only accepts objects that are accepted by <em>all</em> component conditions.
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 6d5841f..c12a252 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
@@ -22,9 +22,9 @@
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.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginElement;
+import org.apache.logging.log4j.plugins.PluginFactory;
/**
* Composite {@code PathCondition} that accepts objects that are accepted by <em>any</em> component conditions.
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 1084a77..bdf14dd 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
@@ -28,10 +28,10 @@
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.core.Core;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
-import org.apache.logging.log4j.core.config.plugins.PluginElement;
-import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginAttribute;
+import org.apache.logging.log4j.plugins.PluginElement;
+import org.apache.logging.log4j.plugins.PluginFactory;
import org.apache.logging.log4j.status.StatusLogger;
/**
@@ -135,8 +135,8 @@
@PluginFactory
public static IfFileName createNameCondition(
// @formatter:off
- @PluginAttribute("glob") final String glob,
- @PluginAttribute("regex") final String regex,
+ @PluginAttribute final String glob,
+ @PluginAttribute final String regex,
@PluginElement("PathConditions") final PathCondition... nestedConditions) {
// @formatter:on
return new IfFileName(glob, regex, nestedConditions);
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/IfLastModified.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/IfLastModified.java
index ffd740e..7aa3e73 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
@@ -26,10 +26,10 @@
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.core.Core;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
-import org.apache.logging.log4j.core.config.plugins.PluginElement;
-import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginAttribute;
+import org.apache.logging.log4j.plugins.PluginElement;
+import org.apache.logging.log4j.plugins.PluginFactory;
import org.apache.logging.log4j.core.time.Clock;
import org.apache.logging.log4j.core.time.ClockFactory;
import org.apache.logging.log4j.status.StatusLogger;
@@ -100,7 +100,7 @@
@PluginFactory
public static IfLastModified createAgeCondition(
// @formatter:off
- @PluginAttribute("age") final Duration age,
+ @PluginAttribute final Duration age,
@PluginElement("PathConditions") final PathCondition... nestedConditions) {
// @formatter:on
return new IfLastModified(age, nestedConditions);
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 1baf187..9e8e7a5 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
@@ -21,9 +21,9 @@
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.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginElement;
+import org.apache.logging.log4j.plugins.PluginFactory;
/**
* Wrapper {@code PathCondition} that accepts objects that are rejected by the wrapped component filter.
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 e0a04b5..68c0086 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
@@ -20,9 +20,9 @@
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;
-import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginAttribute;
+import org.apache.logging.log4j.plugins.PluginFactory;
/**
* {@link PathSorter} that sorts path by their LastModified attribute.
@@ -53,7 +53,7 @@
*/
@PluginFactory
public static PathSorter createSorter(
- @PluginAttribute(value = "recentFirst", defaultBoolean = true) final boolean recentFirst) {
+ @PluginAttribute(defaultBoolean = true) final boolean recentFirst) {
return new PathSortByModificationTime(recentFirst);
}
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 bb5bc74..c9a0c8b 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/PosixViewAttributeAction.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/PosixViewAttributeAction.java
@@ -16,6 +16,18 @@
*/
package org.apache.logging.log4j.core.appender.rolling.action;
+import org.apache.logging.log4j.core.Core;
+import org.apache.logging.log4j.core.config.Configuration;
+import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
+import org.apache.logging.log4j.core.lookup.StrSubstitutor;
+import org.apache.logging.log4j.core.util.FileUtils;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginBuilderAttribute;
+import org.apache.logging.log4j.plugins.PluginElement;
+import org.apache.logging.log4j.plugins.PluginFactory;
+import org.apache.logging.log4j.plugins.validation.constraints.Required;
+import org.apache.logging.log4j.util.Strings;
+
import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.FileVisitor;
@@ -29,18 +41,6 @@
import java.util.List;
import java.util.Set;
-import org.apache.logging.log4j.core.Core;
-import org.apache.logging.log4j.core.config.Configuration;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
-import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
-import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
-import org.apache.logging.log4j.core.config.plugins.PluginElement;
-import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required;
-import org.apache.logging.log4j.core.lookup.StrSubstitutor;
-import org.apache.logging.log4j.core.util.FileUtils;
-import org.apache.logging.log4j.util.Strings;
-
/**
* File posix attribute view action.
*
@@ -74,7 +74,7 @@
this.fileGroup = fileGroup;
}
- @PluginBuilderFactory
+ @PluginFactory
public static Builder newBuilder() {
return new Builder();
}
@@ -82,7 +82,7 @@
/**
* Builder for the posix view attribute action.
*/
- public static class Builder implements org.apache.logging.log4j.core.util.Builder<PosixViewAttributeAction> {
+ public static class Builder implements org.apache.logging.log4j.plugins.util.Builder<PosixViewAttributeAction> {
@PluginConfiguration
private Configuration configuration;
@@ -145,7 +145,7 @@
* @param configuration {@link AbstractPathAction#getStrSubstitutor()}
* @return This builder
*/
- public Builder withConfiguration(final Configuration configuration) {
+ public Builder setConfiguration(final Configuration configuration) {
this.configuration = configuration;
return this;
}
@@ -156,7 +156,7 @@
* @param subst {@link AbstractPathAction#getStrSubstitutor()}
* @return This builder
*/
- public Builder withSubst(final StrSubstitutor subst) {
+ public Builder setSubst(final StrSubstitutor subst) {
this.subst = subst;
return this;
}
@@ -166,7 +166,7 @@
* @param basePath {@link AbstractPathAction#getBasePath()}
* @return This builder
*/
- public Builder withBasePath(final String basePath) {
+ public Builder setBasePath(final String basePath) {
this.basePath = basePath;
return this;
}
@@ -176,7 +176,7 @@
* @param followLinks Follow synonyms links
* @return This builder
*/
- public Builder withFollowLinks(final boolean followLinks) {
+ public Builder setFollowLinks(final boolean followLinks) {
this.followLinks = followLinks;
return this;
}
@@ -186,7 +186,7 @@
* @param maxDepth Max search depth
* @return This builder
*/
- public Builder withMaxDepth(final int maxDepth) {
+ public Builder setMaxDepth(final int maxDepth) {
this.maxDepth = maxDepth;
return this;
}
@@ -197,7 +197,7 @@
* @param pathConditions {@link AbstractPathAction#getPathConditions()}
* @return This builder
*/
- public Builder withPathConditions(final PathCondition[] pathConditions) {
+ public Builder setPathConditions(final PathCondition[] pathConditions) {
this.pathConditions = pathConditions;
return this;
}
@@ -211,7 +211,7 @@
* @param filePermissionsString Permissions to apply
* @return This builder
*/
- public Builder withFilePermissionsString(final String filePermissionsString) {
+ public Builder setFilePermissionsString(final String filePermissionsString) {
this.filePermissionsString = filePermissionsString;
return this;
}
@@ -221,7 +221,7 @@
* @param filePermissions Permissions to apply
* @return This builder
*/
- public Builder withFilePermissions(final Set<PosixFilePermission> filePermissions) {
+ public Builder setFilePermissions(final Set<PosixFilePermission> filePermissions) {
this.filePermissions = filePermissions;
return this;
}
@@ -231,7 +231,7 @@
* @param fileOwner File owner
* @return This builder
*/
- public Builder withFileOwner(final String fileOwner) {
+ public Builder setFileOwner(final String fileOwner) {
this.fileOwner = fileOwner;
return this;
}
@@ -241,7 +241,7 @@
* @param fileGroup File group
* @return This builder
*/
- public Builder withFileGroup(final String fileGroup) {
+ public Builder setFileGroup(final String fileGroup) {
this.fileGroup = fileGroup;
return this;
}
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 9d728c0..8052458 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
@@ -26,10 +26,10 @@
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.core.Core;
import org.apache.logging.log4j.core.config.Configuration;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.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.plugins.PluginElement;
+import org.apache.logging.log4j.plugins.PluginFactory;
import org.apache.logging.log4j.core.script.AbstractScript;
import org.apache.logging.log4j.core.script.ScriptFile;
import org.apache.logging.log4j.core.script.ScriptRef;
@@ -43,7 +43,7 @@
*/
@Plugin(name = "ScriptCondition", category = Core.CATEGORY_NAME, printObject = true)
public class ScriptCondition {
- private static Logger LOGGER = StatusLogger.getLogger();
+ private static final Logger LOGGER = StatusLogger.getLogger();
private final AbstractScript script;
private final Configuration configuration;
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 7bf76b6..0b9f1ca 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/SortingVisitor.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/rolling/action/SortingVisitor.java
@@ -17,8 +17,12 @@
package org.apache.logging.log4j.core.appender.rolling.action;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.status.StatusLogger;
+
import java.io.IOException;
import java.nio.file.FileVisitResult;
+import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
@@ -32,6 +36,7 @@
*/
public class SortingVisitor extends SimpleFileVisitor<Path> {
+ private static final Logger LOGGER = StatusLogger.getLogger();
private final PathSorter sorter;
private final List<PathWithAttributes> collected = new ArrayList<>();
@@ -50,6 +55,18 @@
collected.add(new PathWithAttributes(path, attrs));
return FileVisitResult.CONTINUE;
}
+
+ @Override
+ public FileVisitResult visitFileFailed(Path file, 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<PathWithAttributes> getSortedPaths() {
Collections.sort(collected, sorter);
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 e892f66..950cdd9 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
@@ -28,10 +28,10 @@
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.ConfigurationScheduler;
import org.apache.logging.log4j.core.config.Scheduled;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginAttribute;
import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
-import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+import org.apache.logging.log4j.plugins.PluginFactory;
/**
* Policy is purging appenders that were not in use specified time in minutes
@@ -73,9 +73,10 @@
public void purge() {
final long createTime = System.currentTimeMillis() - timeToLive;
for (final Entry<String, Long> entry : appendersUsage.entrySet()) {
- if (entry.getValue() < createTime) {
- LOGGER.debug("Removing appender " + entry.getKey());
- if (appendersUsage.remove(entry.getKey(), entry.getValue())) {
+ long entryValue = entry.getValue();
+ if (entryValue < createTime) {
+ if (appendersUsage.remove(entry.getKey(), entryValue)) {
+ LOGGER.debug("Removing appender {}", entry.getKey());
routingAppender.deleteAppender(entry.getKey());
}
}
@@ -129,9 +130,9 @@
*/
@PluginFactory
public static PurgePolicy createPurgePolicy(
- @PluginAttribute("timeToLive") final String timeToLive,
- @PluginAttribute("checkInterval") final String checkInterval,
- @PluginAttribute("timeUnit") final String timeUnit,
+ @PluginAttribute final String timeToLive,
+ @PluginAttribute final String checkInterval,
+ @PluginAttribute final String timeUnit,
@PluginConfiguration final Configuration configuration) {
if (timeToLive == null) {
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 b0c8c61..af45a5d 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/PurgePolicy.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/PurgePolicy.java
@@ -24,13 +24,14 @@
public interface PurgePolicy {
/**
- * Activates purging appenders
+ * Activates purging appenders. Note that {@link PurgePolicy} implementations are responsible for invoking
+ * this method themselves.
*/
void purge();
/**
*
- * @param routed appender key
+ * @param key routed appender key
* @param event
*/
void update(String key, LogEvent event);
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 1a92b66..a5a6290 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
@@ -18,11 +18,11 @@
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.core.Core;
-import org.apache.logging.log4j.core.config.Node;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
-import org.apache.logging.log4j.core.config.plugins.PluginFactory;
-import org.apache.logging.log4j.core.config.plugins.PluginNode;
+import org.apache.logging.log4j.plugins.Node;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginAttribute;
+import org.apache.logging.log4j.plugins.PluginFactory;
+import org.apache.logging.log4j.plugins.PluginNode;
import org.apache.logging.log4j.status.StatusLogger;
/**
@@ -96,7 +96,7 @@
@PluginFactory
public static Route createRoute(
@PluginAttribute("ref") final String appenderRef,
- @PluginAttribute("key") final String key,
+ @PluginAttribute final String key,
@PluginNode final Node node) {
if (node != null && node.hasChildren()) {
if (appenderRef != null) {
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/Routes.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/Routes.java
index e179ad7..e2532d8 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/Routes.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/Routes.java
@@ -16,27 +16,26 @@
*/
package org.apache.logging.log4j.core.appender.routing;
-import static org.apache.logging.log4j.core.appender.routing.RoutingAppender.STATIC_VARIABLES_KEY;
-
-import java.util.Objects;
-import java.util.concurrent.ConcurrentMap;
-
-import javax.script.Bindings;
-
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.core.Core;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.config.Configuration;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
-import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
-import org.apache.logging.log4j.core.config.plugins.PluginElement;
-import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required;
import org.apache.logging.log4j.core.script.AbstractScript;
import org.apache.logging.log4j.core.script.ScriptManager;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginAttribute;
+import org.apache.logging.log4j.plugins.PluginElement;
+import org.apache.logging.log4j.plugins.PluginFactory;
+import org.apache.logging.log4j.plugins.validation.constraints.Required;
import org.apache.logging.log4j.status.StatusLogger;
+import javax.script.Bindings;
+import java.util.Objects;
+import java.util.concurrent.ConcurrentMap;
+
+import static org.apache.logging.log4j.core.appender.routing.RoutingAppender.STATIC_VARIABLES_KEY;
+
/**
* Contains the individual Route elements.
*/
@@ -45,18 +44,18 @@
private static final String LOG_EVENT_KEY = "logEvent";
- public static class Builder implements org.apache.logging.log4j.core.util.Builder<Routes> {
+ public static class Builder implements org.apache.logging.log4j.plugins.util.Builder<Routes> {
@PluginConfiguration
private Configuration configuration;
- @PluginAttribute("pattern")
+ @PluginAttribute
private String pattern;
@PluginElement("Script")
private AbstractScript patternScript;
- @PluginElement("Routes")
+ @PluginElement
@Required
private Route[] routes;
@@ -66,7 +65,7 @@
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) {
@@ -95,22 +94,22 @@
return routes;
}
- public Builder withConfiguration(@SuppressWarnings("hiding") final Configuration configuration) {
+ public Builder setConfiguration(@SuppressWarnings("hiding") final Configuration configuration) {
this.configuration = configuration;
return this;
}
- public Builder withPattern(@SuppressWarnings("hiding") final String pattern) {
+ public Builder setPattern(@SuppressWarnings("hiding") final String pattern) {
this.pattern = pattern;
return this;
}
- public Builder withPatternScript(@SuppressWarnings("hiding") final AbstractScript patternScript) {
+ public Builder setPatternScript(@SuppressWarnings("hiding") final AbstractScript patternScript) {
this.patternScript = patternScript;
return this;
}
- public Builder withRoutes(@SuppressWarnings("hiding") final Route[] routes) {
+ public Builder setRoutes(@SuppressWarnings("hiding") final Route... routes) {
this.routes = routes;
return this;
}
@@ -119,25 +118,7 @@
private static final Logger LOGGER = StatusLogger.getLogger();
- /**
- * Creates the Routes.
- * @param pattern The pattern.
- * @param routes An array of Route elements.
- * @return The Routes container.
- * @deprecated since 2.7; use {@link #newBuilder()}.
- */
- @Deprecated
- public static Routes createRoutes(
- final String pattern,
- final Route... routes) {
- if (routes == null || routes.length == 0) {
- LOGGER.error("No routes configured");
- return null;
- }
- return new Routes(null, null, pattern, routes);
- }
-
- @PluginBuilderFactory
+ @PluginFactory
public static Builder newBuilder() {
return new Builder();
}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/RoutingAppender.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/RoutingAppender.java
index d55504b..2d2e3d0 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/RoutingAppender.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/appender/routing/RoutingAppender.java
@@ -16,31 +16,30 @@
*/
package org.apache.logging.log4j.core.appender.routing;
+import org.apache.logging.log4j.core.Appender;
+import org.apache.logging.log4j.core.Core;
+import org.apache.logging.log4j.core.Filter;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.appender.AbstractAppender;
+import org.apache.logging.log4j.core.appender.rewrite.RewritePolicy;
+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.script.AbstractScript;
+import org.apache.logging.log4j.core.script.ScriptManager;
+import org.apache.logging.log4j.plugins.Node;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginElement;
+import org.apache.logging.log4j.plugins.PluginFactory;
+
+import javax.script.Bindings;
import java.util.Collections;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
-
-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.LifeCycle2;
-import org.apache.logging.log4j.core.LogEvent;
-import org.apache.logging.log4j.core.appender.AbstractAppender;
-import org.apache.logging.log4j.core.appender.rewrite.RewritePolicy;
-import org.apache.logging.log4j.core.config.AppenderControl;
-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.PluginElement;
-import org.apache.logging.log4j.core.script.AbstractScript;
-import org.apache.logging.log4j.core.script.ScriptManager;
-import org.apache.logging.log4j.core.util.Booleans;
+import java.util.concurrent.atomic.AtomicInteger;
/**
* This Appender "routes" between various Appenders, some of which can be references to
@@ -56,7 +55,7 @@
public static final String STATIC_VARIABLES_KEY = "staticVariables";
public static class Builder<B extends Builder<B>> extends AbstractAppender.Builder<B>
- implements org.apache.logging.log4j.core.util.Builder<RoutingAppender> {
+ implements org.apache.logging.log4j.plugins.util.Builder<RoutingAppender> {
// Does not work unless the element is called "Script", I wanted "DefaultRounteScript"...
@PluginElement("Script")
@@ -71,6 +70,9 @@
@PluginElement("PurgePolicy")
private PurgePolicy purgePolicy;
+ @PluginElement("RequiresLocation")
+ private Boolean requiresLocation;
+
@Override
public RoutingAppender build() {
final String name = getName();
@@ -83,7 +85,7 @@
return null;
}
return new RoutingAppender(name, getFilter(), isIgnoreExceptions(), routes, rewritePolicy,
- getConfiguration(), purgePolicy, defaultRouteScript);
+ getConfiguration(), purgePolicy, defaultRouteScript, getPropertyArray(), requiresLocation);
}
public Routes getRoutes() {
@@ -102,28 +104,38 @@
return purgePolicy;
}
- public B withRoutes(@SuppressWarnings("hiding") final Routes routes) {
+ public Boolean requiresLocation() {
+ return requiresLocation;
+ }
+
+ public B setRoutes(@SuppressWarnings("hiding") final Routes routes) {
this.routes = routes;
return asBuilder();
}
- public B withDefaultRouteScript(@SuppressWarnings("hiding") final AbstractScript defaultRouteScript) {
+ public B setDefaultRouteScript(@SuppressWarnings("hiding") final AbstractScript defaultRouteScript) {
this.defaultRouteScript = defaultRouteScript;
return asBuilder();
}
- public B withRewritePolicy(@SuppressWarnings("hiding") final RewritePolicy rewritePolicy) {
+ public B setRewritePolicy(@SuppressWarnings("hiding") final RewritePolicy rewritePolicy) {
this.rewritePolicy = rewritePolicy;
return asBuilder();
}
- public void withPurgePolicy(@SuppressWarnings("hiding") final PurgePolicy purgePolicy) {
+ public B setPurgePolicy(@SuppressWarnings("hiding") final PurgePolicy purgePolicy) {
this.purgePolicy = purgePolicy;
+ return asBuilder();
+ }
+
+ public B setRequestLocation(@SuppressWarnings("hiding") final boolean requiresLocation) {
+ this.requiresLocation = requiresLocation;
+ return asBuilder();
}
}
- @PluginBuilderFactory
+ @PluginFactory
public static <B extends Builder<B>> B newBuilder() {
return new Builder<B>().asBuilder();
}
@@ -133,20 +145,25 @@
private final Routes routes;
private Route defaultRoute;
private final Configuration configuration;
- private final ConcurrentMap<String, AppenderControl> appenders = new ConcurrentHashMap<>();
+ private final ConcurrentMap<String, CreatedRouteAppenderControl> createdAppenders = new ConcurrentHashMap<>();
+ private final Map<String, AppenderControl> createdAppendersUnmodifiableView
+ = Collections.unmodifiableMap(createdAppenders);
+ private final ConcurrentMap<String, RouteAppenderControl> referencedAppenders = new ConcurrentHashMap<>();
private final RewritePolicy rewritePolicy;
private final PurgePolicy purgePolicy;
private final AbstractScript defaultRouteScript;
private final ConcurrentMap<Object, Object> scriptStaticVariables = new ConcurrentHashMap<>();
+ private final Boolean requiresLocation;
private RoutingAppender(final String name, final Filter filter, final boolean ignoreExceptions, final Routes routes,
final RewritePolicy rewritePolicy, final Configuration configuration, final PurgePolicy purgePolicy,
- final AbstractScript defaultRouteScript) {
- super(name, filter, null, ignoreExceptions);
+ final AbstractScript defaultRouteScript, Property[] properties, final Boolean requiresLocation) {
+ super(name, filter, null, ignoreExceptions, properties);
this.routes = routes;
this.configuration = configuration;
this.rewritePolicy = rewritePolicy;
this.purgePolicy = purgePolicy;
+ this.requiresLocation = requiresLocation;
if (this.purgePolicy != null) {
this.purgePolicy.initialize(this);
}
@@ -187,7 +204,7 @@
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");
}
@@ -197,19 +214,19 @@
}
@Override
+ public boolean requiresLocation() {
+ return requiresLocation != null ? requiresLocation : false;
+ }
+
+
+ @Override
public boolean stop(final long timeout, final TimeUnit timeUnit) {
setStopping();
super.stop(timeout, timeUnit, false);
- final Map<String, Appender> map = configuration.getAppenders();
- for (final Map.Entry<String, AppenderControl> entry : appenders.entrySet()) {
+ // Only stop appenders that were created by this RoutingAppender
+ for (final Map.Entry<String, CreatedRouteAppenderControl> entry : createdAppenders.entrySet()) {
final Appender appender = entry.getValue().getAppender();
- if (!map.containsKey(appender.getName())) {
- if (appender instanceof LifeCycle2) {
- ((LifeCycle2) appender).stop(timeout, timeUnit);
- } else {
- appender.stop();
- }
- }
+ appender.stop(timeout, timeUnit);
}
setStopped();
return true;
@@ -221,20 +238,32 @@
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 +275,9 @@
}
if (route == null) {
route = defaultRoute;
- control = appenders.get(DEFAULT_KEY);
+ control = getAppender(DEFAULT_KEY);
if (control != null) {
+ control.checkout();
return control;
}
}
@@ -256,13 +286,25 @@
if (app == null) {
return null;
}
- control = new AppenderControl(app, null, null);
- appenders.put(key, control);
+ 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 +324,12 @@
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<String, AppenderControl> getAppenders() {
- return Collections.unmodifiableMap(appenders);
+ return createdAppendersUnmodifiableView;
}
/**
@@ -292,50 +338,29 @@
* @param key The appender's key
*/
public void deleteAppender(final String key) {
- LOGGER.debug("Deleting route with " + key + " key ");
- final AppenderControl control = appenders.remove(key);
+ LOGGER.debug("Deleting route with {} key ", key);
+ // LOG4J2-2631: Only appenders created by this RoutingAppender are eligible for deletion.
+ final CreatedRouteAppenderControl control = createdAppenders.remove(key);
if (null != control) {
- LOGGER.debug("Stopping route with " + key + " key");
- control.getAppender().stop();
+ LOGGER.debug("Stopping route with {} key", key);
+ // Synchronize with getControl to avoid triggering stopAppender before RouteAppenderControl.checkout
+ // can be invoked.
+ synchronized (this) {
+ control.pendingDeletion = true;
+ }
+ // Don't attempt to stop the appender in a synchronized block, since it may block flushing events
+ // to disk.
+ control.tryStopAppender();
} else {
- LOGGER.debug("Route with " + key + " key already deleted");
+ if (referencedAppenders.containsKey(key)) {
+ LOGGER.debug("Route {} using an appender reference may not be removed because " +
+ "the appender may be used outside of the RoutingAppender", key);
+ } else {
+ LOGGER.debug("Route with {} key already deleted", key);
+ }
}
}
- /**
- * Creates a RoutingAppender.
- * @param name The name of the Appender.
- * @param ignore If {@code "true"} (default) exceptions encountered when appending events are logged; otherwise
- * they are propagated to the caller.
- * @param routes The routing definitions.
- * @param config The Configuration (automatically added by the Configuration).
- * @param rewritePolicy A RewritePolicy, if any.
- * @param filter A Filter to restrict events processed by the Appender or null.
- * @return The RoutingAppender
- * @deprecated Since 2.7; use {@link #newBuilder()}
- */
- @Deprecated
- public static RoutingAppender createAppender(
- final String name,
- final String ignore,
- final Routes routes,
- final Configuration config,
- final RewritePolicy rewritePolicy,
- final PurgePolicy purgePolicy,
- final Filter filter) {
-
- final boolean ignoreExceptions = Booleans.parseBoolean(ignore, true);
- if (name == null) {
- LOGGER.error("No name provided for RoutingAppender");
- return null;
- }
- if (routes == null) {
- LOGGER.error("No routes defined for RoutingAppender");
- return null;
- }
- return new RoutingAppender(name, filter, ignoreExceptions, routes, rewritePolicy, config, purgePolicy, null);
- }
-
public Route getDefaultRoute() {
return defaultRoute;
}
@@ -363,4 +388,82 @@
public ConcurrentMap<Object, Object> 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 to mark usage of an appender, allowing deferral of
+ * {@link Appender#stop()} until events have successfully been recorded.
+ * Alternative approaches considered:
+ * - More aggressive synchronization: Appenders may do expensive I/O that shouldn't block routing.
+ * - Move the 'updatePurgePolicy' invocation before appenders are called: Unfortunately this approach doesn't work
+ * if we consider an ImmediatePurgePolicy (or IdlePurgePolicy with a very small timeout) because it may attempt
+ * to remove an appender that doesn't exist yet. It's counterintuitive to get an event that a route has been
+ * used at a point when we expect the route doesn't exist in {@link #getAppenders()}.
+ */
+ private static abstract class RouteAppenderControl extends AppenderControl {
+
+ RouteAppenderControl(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(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)) {
+ Appender appender = getAppender();
+ LOGGER.debug("Stopping appender {}", appender);
+ appender.stop();
+ }
+ }
+ }
+
+ private static final class ReferencedRouteAppenderControl extends RouteAppenderControl {
+
+ ReferencedRouteAppenderControl(Appender appender) {
+ super(appender);
+ }
+
+ @Override
+ void checkout() {
+ // nop
+ }
+
+ @Override
+ void release() {
+ // nop
+ }
+ }
}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/ArrayBlockingQueueFactory.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/ArrayBlockingQueueFactory.java
index be4bf22..ad94b73 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
@@ -19,9 +19,9 @@
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;
+import org.apache.logging.log4j.plugins.Node;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginFactory;
/**
* Factory for creating instances of {@link ArrayBlockingQueue}.
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 c278316..22f3d4c 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLogger.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLogger.java
@@ -26,6 +26,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;
@@ -126,13 +127,54 @@
@Override
public void logMessage(final String fqcn, final Level level, final Marker marker, final Message message,
final Throwable thrown) {
+ getTranslatorType().log(fqcn, level, marker, message, thrown);
+ }
- if (loggerDisruptor.isUseThreadLocals()) {
+ @Override
+ public void log(final Level level, final Marker marker, final String fqcn, final StackTraceElement location,
+ final Message message, final Throwable throwable) {
+ getTranslatorType().log(fqcn, location, level, marker, message, throwable);
+ }
+
+ abstract class TranslatorType {
+
+ abstract void log(final String fqcn, final StackTraceElement location, final Level level, final Marker marker,
+ final Message message, final Throwable thrown);
+
+ abstract void log(final String fqcn, final Level level, final Marker marker,
+ final Message message, final Throwable thrown);
+ }
+
+ private final TranslatorType threadLocalTranslatorType = new TranslatorType() {
+
+ @Override
+ void log(String fqcn, StackTraceElement location, Level level, Marker marker, Message message,
+ Throwable thrown) {
+ logWithThreadLocalTranslator(fqcn, location, level, marker, message, thrown);
+ }
+
+ @Override
+ void log(String fqcn, Level level, Marker marker, Message message, Throwable thrown) {
logWithThreadLocalTranslator(fqcn, level, marker, message, thrown);
- } else {
+ }
+ };
+
+ private final TranslatorType varargTranslatorType = new TranslatorType() {
+
+ @Override
+ void log(String fqcn, StackTraceElement location, Level level, Marker marker, Message message,
+ Throwable thrown) {
+ logWithVarargTranslator(fqcn, location, level, marker, message, thrown);
+ }
+ @Override
+ void log(String fqcn, Level level, Marker marker, Message message, Throwable thrown) {
// LOG4J2-1172: avoid storing non-JDK classes in ThreadLocals to avoid memory leaks in web apps
logWithVarargTranslator(fqcn, level, marker, message, thrown);
}
+ };
+
+ private TranslatorType getTranslatorType() {
+ return loggerDisruptor.isUseThreadLocals() ? threadLocalTranslatorType : varargTranslatorType;
}
private boolean isReused(final Message message) {
@@ -152,7 +194,7 @@
* @param thrown a {@code Throwable} or {@code null}
*/
private void logWithThreadLocalTranslator(final String fqcn, final Level level, final Marker marker,
- final Message message, final Throwable thrown) {
+ final Message message, final Throwable thrown) {
// Implementation note: this method is tuned for performance. MODIFY WITH CARE!
final RingBufferLogEventTranslator translator = getCachedTranslator();
@@ -161,6 +203,29 @@
publish(translator);
}
+ /**
+ * Enqueues the specified log event data for logging in a background thread.
+ * <p>
+ * This re-uses a {@code RingBufferLogEventTranslator} instance cached in a {@code ThreadLocal} to avoid creating
+ * unnecessary objects with each event.
+ *
+ * @param fqcn fully qualified name of the caller
+ * @param location the Location of the caller.
+ * @param level level at which the caller wants to log the message
+ * @param marker message marker
+ * @param message the log message
+ * @param thrown a {@code Throwable} or {@code null}
+ */
+ private void logWithThreadLocalTranslator(final String fqcn, final StackTraceElement location, final Level level,
+ final Marker marker, final Message message, final Throwable thrown) {
+ // Implementation note: this method is tuned for performance. MODIFY WITH CARE!
+
+ final RingBufferLogEventTranslator translator = getCachedTranslator();
+ initTranslator(translator, fqcn, location, level, marker, message, thrown);
+ initTranslatorThreadValues(translator);
+ publish(translator);
+ }
+
private void publish(final RingBufferLogEventTranslator translator) {
if (!loggerDisruptor.tryPublish(translator)) {
handleRingBufferFull(translator);
@@ -173,18 +238,21 @@
AsyncQueueFullMessageUtil.logWarningToStatusLogger();
logMessageInCurrentThread(translator.fqcn, translator.level, translator.marker, translator.message,
translator.thrown);
+ translator.clear();
return;
}
final EventRoute eventRoute = loggerDisruptor.getEventRoute(translator.level);
switch (eventRoute) {
case ENQUEUE:
- loggerDisruptor.enqueueLogMessageInfo(translator);
+ loggerDisruptor.enqueueLogMessageWhenQueueFull(translator);
break;
case SYNCHRONOUS:
logMessageInCurrentThread(translator.fqcn, translator.level, translator.marker, translator.message,
translator.thrown);
+ translator.clear();
break;
case DISCARD:
+ translator.clear();
break;
default:
throw new IllegalStateException("Unknown EventRoute " + eventRoute);
@@ -192,6 +260,23 @@
}
private void initTranslator(final RingBufferLogEventTranslator translator, final String fqcn,
+ final StackTraceElement location, final Level level, final Marker marker,
+ final Message message, final Throwable thrown) {
+
+ translator.setBasicValues(this, name, marker, fqcn, level, message, //
+ // don't construct ThrowableProxy until required
+ thrown,
+
+ // needs shallow copy to be fast (LOG4J2-154)
+ ThreadContext.getImmutableStack(), //
+
+ location,
+ CLOCK, //
+ nanoClock //
+ );
+ }
+
+ 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, //
@@ -267,6 +352,45 @@
}
}
+ /**
+ * Enqueues the specified log event data for logging in a background thread.
+ * <p>
+ * 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<RingBufferLogEvent> disruptor = loggerDisruptor.getDisruptor();
+ if (disruptor == null) {
+ LOGGER.error("Ignoring log event after Log4j has been shut down.");
+ return;
+ }
+ // if the Message instance is reused, there is no point in freezing its message here
+ if (!isReused(message)) {
+ InternalAsyncUtil.makeMessageImmutable(message);
+ }
+ // calls the translateTo method on this AsyncLogger
+ if (!disruptor.getRingBuffer().tryPublishEvent(this,
+ this, // asyncLogger: 0
+ location, // location: 1
+ fqcn, // 2
+ level, // 3
+ marker, // 4
+ message, // 5
+ thrown)) { // 6
+ handleRingBufferFull(location, fqcn, level, marker, message, thrown);
+ }
+ }
+
/*
* (non-Javadoc)
*
@@ -328,7 +452,7 @@
final EventRoute eventRoute = loggerDisruptor.getEventRoute(level);
switch (eventRoute) {
case ENQUEUE:
- loggerDisruptor.getDisruptor().getRingBuffer().publishEvent(this,
+ loggerDisruptor.enqueueLogMessageWhenQueueFull(this,
this, // asyncLogger: 0
location, // location: 1
fqcn, // 2
@@ -355,29 +479,39 @@
* @param event the event to log
*/
public void actualAsyncLog(final RingBufferLogEvent event) {
- final List<Property> properties = privateConfig.loggerConfig.getPropertyList();
+ final LoggerConfig privateConfigLoggerConfig = privateConfig.loggerConfig;
+ final List<Property> 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);
- }
- event.setContextData(contextData);
+ onPropertiesPresent(event, properties);
}
- final ReliabilityStrategy strategy = privateConfig.loggerConfig.getReliabilityStrategy();
- strategy.log(this, event);
+ privateConfigLoggerConfig.getReliabilityStrategy().log(this, event);
+ }
+
+ @SuppressWarnings("ForLoopReplaceableByForEach") // Avoid iterator allocation
+ private void onPropertiesPresent(final RingBufferLogEvent event, final List<Property> properties) {
+ 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
+ }
+ final String value = prop.isValueNeedsLookup() //
+ ? privateConfig.config.getStrSubstitutor().replace(event, prop.getValue()) //
+ : prop.getValue();
+ contextData.putValue(prop.getName(), value);
+ }
+ event.setContextData(contextData);
+ }
+
+ private static StringMap getContextData(final RingBufferLogEvent event) {
+ StringMap contextData = (StringMap) event.getContextData();
+ if (contextData.isFrozen()) {
+ final StringMap temp = ContextDataFactory.createContextData();
+ temp.putAll(contextData);
+ return temp;
+ }
+ return contextData;
}
}
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 06d219a..3a6613f 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
@@ -28,16 +28,16 @@
import org.apache.logging.log4j.core.config.AppenderRef;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.LoggerConfig;
-import org.apache.logging.log4j.core.config.Node;
import org.apache.logging.log4j.core.config.Property;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
-import org.apache.logging.log4j.core.config.plugins.PluginElement;
-import org.apache.logging.log4j.core.config.plugins.PluginFactory;
-import org.apache.logging.log4j.core.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.plugins.Node;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginAttribute;
+import org.apache.logging.log4j.plugins.PluginElement;
+import org.apache.logging.log4j.plugins.PluginFactory;
+import org.apache.logging.log4j.plugins.validation.constraints.Required;
import org.apache.logging.log4j.spi.AbstractLogger;
import org.apache.logging.log4j.util.Strings;
@@ -72,7 +72,7 @@
@Plugin(name = "asyncLogger", category = Node.CATEGORY, printObject = true)
public class AsyncLoggerConfig extends LoggerConfig {
- private static final ThreadLocal<Boolean> ASYNC_LOGGER_ENTERED = new ThreadLocal<>();
+ private static final ThreadLocal<Boolean> ASYNC_LOGGER_ENTERED = ThreadLocal.withInitial(() -> Boolean.FALSE);
private final AsyncLoggerConfigDelegate delegate;
protected AsyncLoggerConfig(final String name,
@@ -89,7 +89,7 @@
protected void log(final LogEvent event, final LoggerConfigPredicate predicate) {
// See LOG4J2-2301
if (predicate == LoggerConfigPredicate.ALL &&
- ASYNC_LOGGER_ENTERED.get() == null &&
+ 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
@@ -108,7 +108,7 @@
// from reusable messages.
logToAsyncDelegate(event);
} finally {
- ASYNC_LOGGER_ENTERED.remove();
+ ASYNC_LOGGER_ENTERED.set(Boolean.FALSE);
}
} else {
super.log(event, predicate);
@@ -196,52 +196,6 @@
* Factory method to create a LoggerConfig.
*
* @param additivity True if additive, false otherwise.
- * @param levelName 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.
- * @deprecated use {@link #createLogger(boolean, Level, String, String, AppenderRef[], Property[], Configuration, Filter)}
- */
- @Deprecated
- public static LoggerConfig createLogger(
- 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;
- }
-
- final List<AppenderRef> appenderRefs = Arrays.asList(refs);
- Level level;
- try {
- level = Level.toLevel(levelName, Level.ERROR);
- } catch (final Exception ex) {
- LOGGER.error(
- "Invalid Log level specified: {}. Defaulting to Error",
- levelName);
- level = Level.ERROR;
- }
- 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));
- }
-
- /**
- * 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
@@ -254,14 +208,14 @@
*/
@PluginFactory
public static LoggerConfig createLogger(
- @PluginAttribute(value = "additivity", defaultBoolean = true) final boolean additivity,
- @PluginAttribute("level") final Level level,
+ @PluginAttribute(defaultBoolean = true) final boolean additivity,
+ @PluginAttribute 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,
+ @PluginAttribute final String includeLocation,
+ @PluginElement final AppenderRef[] refs,
+ @PluginElement final Property[] properties,
@PluginConfiguration final Configuration config,
- @PluginElement("Filter") final Filter filter) {
+ @PluginElement final Filter filter) {
final String name = loggerName.equals(ROOT) ? Strings.EMPTY : loggerName;
return new AsyncLoggerConfig(name, Arrays.asList(refs), filter, level, additivity, properties, config,
includeLocation(includeLocation));
@@ -279,43 +233,17 @@
public static class RootLogger extends LoggerConfig {
/**
- * @deprecated use {@link #createLogger(String, Level, String, AppenderRef[], Property[], Configuration, Filter)}
- */
- @Deprecated
- public static LoggerConfig createLogger(
- final String additivity,
- final String levelName,
- final String includeLocation,
- final AppenderRef[] refs,
- final Property[] properties,
- final Configuration config,
- final Filter filter) {
- final List<AppenderRef> appenderRefs = Arrays.asList(refs);
- Level level = null;
- try {
- level = Level.toLevel(levelName, Level.ERROR);
- } catch (final Exception ex) {
- 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,
- AsyncLoggerConfig.includeLocation(includeLocation));
- }
-
- /**
* @since 3.0
*/
@PluginFactory
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,
+ @PluginAttribute final String additivity,
+ @PluginAttribute final Level level,
+ @PluginAttribute final String includeLocation,
+ @PluginElement final AppenderRef[] refs,
+ @PluginElement final Property[] properties,
@PluginConfiguration final Configuration config,
- @PluginElement("Filter") final Filter filter) {
+ @PluginElement final Filter filter) {
final List<AppenderRef> appenderRefs = Arrays.asList(refs);
final Level actualLevel = level == null ? Level.ERROR : level;
final boolean additive = Booleans.parseBoolean(additivity, true);
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 0e230bb..b791f0a 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLoggerConfigDelegate.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLoggerConfigDelegate.java
@@ -46,13 +46,18 @@
*/
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 <code>false</code>
+ * indicating that the ringbuffer is full, otherwise it may incur unnecessary synchronization.
+ */
void enqueueEvent(LogEvent event, AsyncLoggerConfig asyncLoggerConfig);
boolean tryEnqueue(LogEvent event, AsyncLoggerConfig asyncLoggerConfig);
/**
* Notifies the delegate what LogEventFactory an AsyncLoggerConfig is using, so the delegate can determine
- * whether to populate the ring buffer with mutable log events or not. This method may be invoced multiple times
+ * whether to populate the ring buffer with mutable log events or not. This method may be invoked multiple times
* for all AsyncLoggerConfigs that use this delegate.
*
* @param logEventFactory the factory used
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLoggerConfigDisruptor.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLoggerConfigDisruptor.java
index b29dda3..252cd28 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
@@ -27,6 +27,7 @@
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;
@@ -184,7 +185,9 @@
private long backgroundThreadId; // LOG4J2-471
private EventFactory<Log4jEventWrapper> factory;
private EventTranslatorTwoArg<Log4jEventWrapper, LogEvent, AsyncLoggerConfig> translator;
- private volatile boolean alreadyLoggedWarning = false;
+ private volatile boolean alreadyLoggedWarning;
+
+ private final Object queueFullEnqueueLock = new Object();
public AsyncLoggerConfigDisruptor() {
}
@@ -369,7 +372,24 @@
}
private void enqueue(final LogEvent logEvent, final AsyncLoggerConfig asyncLoggerConfig) {
- disruptor.getRingBuffer().publishEvent(translator, logEvent, asyncLoggerConfig);
+ if (synchronizeEnqueueWhenQueueFull()) {
+ synchronized (queueFullEnqueueLock) {
+ disruptor.getRingBuffer().publishEvent(translator, logEvent, asyncLoggerConfig);
+ }
+ } else {
+ disruptor.getRingBuffer().publishEvent(translator, logEvent, asyncLoggerConfig);
+ }
+ }
+
+ private boolean synchronizeEnqueueWhenQueueFull() {
+ return DisruptorUtil.ASYNC_CONFIG_SYNCHRONIZE_ENQUEUE_WHEN_QUEUE_FULL
+ // Background thread must never block
+ && backgroundThreadId != Thread.currentThread().getId()
+ // Threads owned by log4j are most likely to result in
+ // deadlocks because they generally consume events.
+ // This prevents deadlocks between AsyncLoggerContext
+ // disruptors.
+ && !(Thread.currentThread() instanceof Log4jThread);
}
@Override
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLoggerDisruptor.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncLoggerDisruptor.java
index 7a7e546..377100e 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
@@ -20,9 +20,12 @@
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;
+import com.lmax.disruptor.EventTranslatorVararg;
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;
@@ -32,6 +35,7 @@
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,6 +47,8 @@
private static final int SLEEP_MILLIS_BETWEEN_DRAIN_ATTEMPTS = 50;
private static final int MAX_DRAIN_ATTEMPTS_BEFORE_SHUTDOWN = 200;
+ private final Object queueFullEnqueueLock = new Object();
+
private volatile Disruptor<RingBufferLogEvent> disruptor;
private String contextName;
@@ -80,6 +86,11 @@
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");
@@ -202,32 +213,99 @@
return false;
}
- public boolean tryPublish(final RingBufferLogEventTranslator translator) {
- try {
- 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)));
- return false;
- }
- }
-
- void enqueueLogMessageInfo(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.
- disruptor.publishEvent(translator);
+ 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 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<RingBufferLogEvent> 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.
+ 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()
+ logWarningOnNpeFromDisruptorPublish(level, fqcn, msg, thrown);
+ }
+ }
+
+ private boolean synchronizeEnqueueWhenQueueFull() {
+ return DisruptorUtil.ASYNC_LOGGER_SYNCHRONIZE_ENQUEUE_WHEN_QUEUE_FULL
+ // Background thread must never block
+ && backgroundThreadId != Thread.currentThread().getId()
+ // Threads owned by log4j are most likely to result in
+ // deadlocks because they generally consume events.
+ // This prevents deadlocks between AsyncLoggerContext
+ // disruptors.
+ && !(Thread.currentThread() instanceof Log4jThread);
+ }
+
+ private void logWarningOnNpeFromDisruptorPublish(final RingBufferLogEventTranslator translator) {
+ logWarningOnNpeFromDisruptorPublish(
+ translator.level, translator.loggerName, translator.message, translator.thrown);
+ }
+
+ private void logWarningOnNpeFromDisruptorPublish(
+ final Level level, final String fqcn, final Message msg, final Throwable thrown) {
+ LOGGER.warn("[{}] Ignoring log event after log4j was shut down: {} [{}] {}{}", contextName,
+ level, fqcn, msg.getFormattedMessage(), thrown == null ? "" : Throwables.toStringList(thrown));
+ }
+
/**
* Returns whether it is allowed to store non-JDK classes in ThreadLocal objects for efficiency.
*
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncQueueFullPolicyFactory.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncQueueFullPolicyFactory.java
index 534a899..a80c787 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncQueueFullPolicyFactory.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/AsyncQueueFullPolicyFactory.java
@@ -18,8 +18,8 @@
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.Logger;
+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;
/**
@@ -66,22 +66,29 @@
*/
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(
+ String propertyValue,
+ Class<? extends AsyncQueueFullPolicy> policy,
+ 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<? extends AsyncQueueFullPolicy> cls = LoaderUtil.loadClass(router).asSubclass(AsyncQueueFullPolicy.class);
+ final Class<? extends AsyncQueueFullPolicy> cls = Loader.loadClass(router).asSubclass(AsyncQueueFullPolicy.class);
LOGGER.debug("Creating custom AsyncQueueFullPolicy '{}'", router);
return cls.newInstance();
} catch (final Exception ex) {
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 b495d5f..cd96f7b 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/BlockingQueueFactory.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/BlockingQueueFactory.java
@@ -26,7 +26,7 @@
public interface BlockingQueueFactory<E> {
/**
- * The {@link org.apache.logging.log4j.core.config.plugins.Plugin#elementType() element type} to use for plugins
+ * The {@link org.apache.logging.log4j.plugins.Plugin#elementType() element type} to use for plugins
* implementing this interface.
*/
String ELEMENT_TYPE = "BlockingQueueFactory";
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/DefaultAsyncQueueFullPolicy.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/DefaultAsyncQueueFullPolicy.java
index 1d48113..d3d53e1 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/DefaultAsyncQueueFullPolicy.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/DefaultAsyncQueueFullPolicy.java
@@ -17,6 +17,7 @@
package org.apache.logging.log4j.core.async;
import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.core.util.Log4jThread;
/**
* Default router: enqueue the event for asynchronous logging in the background thread, unless the current thread is the
@@ -29,7 +30,13 @@
// LOG4J2-471: prevent deadlock when RingBuffer is full and object
// being logged calls Logger.log() from its toString() method
- if (Thread.currentThread().getId() == backgroundThreadId) {
+ 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/DisruptorBlockingQueueFactory.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/DisruptorBlockingQueueFactory.java
index 5c941db..766daf6 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
@@ -20,10 +20,10 @@
import com.conversantmedia.util.concurrent.DisruptorBlockingQueue;
import com.conversantmedia.util.concurrent.SpinPolicy;
-import org.apache.logging.log4j.core.config.Node;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
-import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+import org.apache.logging.log4j.plugins.Node;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginAttribute;
+import org.apache.logging.log4j.plugins.PluginFactory;
/**
* Factory for creating instances of {@link DisruptorBlockingQueue}.
@@ -46,7 +46,7 @@
@PluginFactory
public static <E> DisruptorBlockingQueueFactory<E> createFactory(
- @PluginAttribute(value = "SpinPolicy", defaultString = "WAITING") final SpinPolicy spinPolicy
+ @PluginAttribute(defaultString = "WAITING") final SpinPolicy spinPolicy
) {
return new DisruptorBlockingQueueFactory<>(spinPolicy);
}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/DisruptorUtil.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/DisruptorUtil.java
index 4fc5ea0..5925187 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
@@ -17,19 +17,25 @@
package org.apache.logging.log4j.core.async;
-import java.util.Locale;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
-import com.lmax.disruptor.*;
+import com.lmax.disruptor.BlockingWaitStrategy;
+import com.lmax.disruptor.BusySpinWaitStrategy;
+import com.lmax.disruptor.ExceptionHandler;
+import com.lmax.disruptor.SleepingWaitStrategy;
+import com.lmax.disruptor.TimeoutBlockingWaitStrategy;
+import com.lmax.disruptor.WaitStrategy;
+import com.lmax.disruptor.YieldingWaitStrategy;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.core.util.Constants;
import org.apache.logging.log4j.core.util.Integers;
+import org.apache.logging.log4j.core.util.Loader;
import org.apache.logging.log4j.status.StatusLogger;
-import org.apache.logging.log4j.util.LoaderUtil;
import org.apache.logging.log4j.util.PropertiesUtil;
+import org.apache.logging.log4j.util.Strings;
/**
* Utility methods for getting Disruptor related configuration.
@@ -40,39 +46,60 @@
private static final int RINGBUFFER_DEFAULT_SIZE = 256 * 1024;
private static final int RINGBUFFER_NO_GC_DEFAULT_SIZE = 4 * 1024;
+ /**
+ * 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 final boolean ASYNC_CONFIG_SYNCHRONIZE_ENQUEUE_WHEN_QUEUE_FULL = PropertiesUtil.getProperties()
+ .getBooleanProperty("AsyncLoggerConfig.SynchronizeEnqueueWhenQueueFull", true);
+
private DisruptorUtil() {
}
- static long getTimeout(final String propertyName, final long defaultTimeout) {
- return PropertiesUtil.getProperties().getLongProperty(propertyName, defaultTimeout);
- }
-
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);
+ final String strategy = PropertiesUtil.getProperties().getStringProperty(propertyName, "Timeout");
+ LOGGER.trace("property {}={}", propertyName, strategy);
+ final String strategyUp = Strings.toRootUpperCase(strategy);
+ final long timeoutMillis = parseAdditionalLongProperty(propertyName, "Timeout", 10L);
+ // 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);
+ return new SleepingWaitStrategy(retries, sleepTimeNs);
+ 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);
+ }
}
- 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 static String getFullPropertyKey(final String strategyKey, final String additionalKey) {
+ return strategyKey.startsWith("AsyncLogger.")
+ ? "AsyncLogger." + additionalKey
+ : "AsyncLoggerConfig." + additionalKey;
+ }
+
+ private static long parseAdditionalLongProperty(
+ final String propertyName,
+ final String additionalKey,
+ long defaultValue) {
+ final String key = getFullPropertyKey(propertyName, additionalKey);
+ return PropertiesUtil.getProperties().getLongProperty(key, defaultValue);
}
static int calculateRingBufferSize(final String propertyName) {
@@ -101,7 +128,7 @@
try {
@SuppressWarnings("unchecked")
final Class<? extends ExceptionHandler<RingBufferLogEvent>> klass =
- (Class<? extends ExceptionHandler<RingBufferLogEvent>>) LoaderUtil.loadClass(cls);
+ (Class<? extends ExceptionHandler<RingBufferLogEvent>>) Loader.loadClass(cls);
return klass.newInstance();
} catch (final Exception ignored) {
LOGGER.debug("Invalid AsyncLogger.ExceptionHandler value: error creating {}: ", cls, ignored);
@@ -117,7 +144,7 @@
try {
@SuppressWarnings("unchecked")
final Class<? extends ExceptionHandler<AsyncLoggerConfigDisruptor.Log4jEventWrapper>> klass =
- (Class<? extends ExceptionHandler<AsyncLoggerConfigDisruptor.Log4jEventWrapper>>) LoaderUtil.loadClass(cls);
+ (Class<? extends ExceptionHandler<AsyncLoggerConfigDisruptor.Log4jEventWrapper>>) Loader.loadClass(cls);
return klass.newInstance();
} catch (final Exception ignored) {
LOGGER.debug("Invalid AsyncLoggerConfig.ExceptionHandler value: error creating {}: ", cls, ignored);
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 cdb5451..bc2f5c7 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
@@ -53,6 +53,8 @@
},
/**
* 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
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 facc59e..9333611 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
@@ -21,10 +21,10 @@
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.LockSupport;
-import org.apache.logging.log4j.core.config.Node;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
-import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+import org.apache.logging.log4j.plugins.Node;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginAttribute;
+import org.apache.logging.log4j.plugins.PluginFactory;
import org.jctools.queues.MpscArrayQueue;
/**
@@ -48,7 +48,7 @@
@PluginFactory
public static <E> JCToolsBlockingQueueFactory<E> createFactory(
- @PluginAttribute(value = "WaitStrategy", defaultString = "PARK") final WaitStrategy waitStrategy) {
+ @PluginAttribute(defaultString = "PARK") final WaitStrategy waitStrategy) {
return new JCToolsBlockingQueueFactory<>(waitStrategy);
}
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 0d4628e..906bb8e 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
@@ -20,9 +20,9 @@
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;
+import org.apache.logging.log4j.plugins.Node;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginFactory;
/**
* Factory for creating instances of {@link LinkedTransferQueue}.
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 96059a9..0859cab 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
@@ -60,18 +60,13 @@
@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 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;
@@ -134,10 +129,8 @@
final ReusableMessage reusable = (ReusableMessage) msg;
reusable.formatTo(getMessageTextForWriting());
messageFormat = reusable.getFormat();
- if (parameters != null) {
- parameters = reusable.swapParameters(parameters);
- parameterCount = reusable.getParameterCount();
- }
+ parameters = reusable.swapParameters(parameters == null ? new Object[10] : parameters);
+ parameterCount = reusable.getParameterCount();
} else {
this.message = InternalAsyncUtil.makeMessageImmutable(msg);
}
@@ -145,8 +138,8 @@
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);
@@ -348,12 +341,6 @@
this.contextData = contextData;
}
- @SuppressWarnings("unchecked")
- @Override
- public Map<String, String> getContextMap() {
- return contextData.toMap();
- }
-
@Override
public ContextStack getContextStack() {
return contextStack;
@@ -418,12 +405,18 @@
}
// ensure that excessively long char[] arrays are not kept in memory forever
- StringBuilders.trimToMaxSize(messageText, Constants.MAX_REUSABLE_MESSAGE_SIZE);
+ if (Constants.ENABLE_THREADLOCALS) {
+ StringBuilders.trimToMaxSize(messageText, Constants.MAX_REUSABLE_MESSAGE_SIZE);
- if (parameters != null) {
- for (int i = 0; i < parameters.length; i++) {
- parameters[i] = null;
+ if (parameters != null) {
+ Arrays.fill(parameters, null);
}
+ } else {
+ // A user may have manually logged a ReusableMessage implementation, when thread locals are
+ // disabled we remove the reference in order to avoid permanently holding references to these
+ // buffers.
+ messageText = null;
+ parameters = null;
}
}
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 e4a496c..dfec3e4 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/RingBufferLogEventHandler.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/RingBufferLogEventHandler.java
@@ -42,12 +42,19 @@
@Override
public void onEvent(final RingBufferLogEvent event, final long sequence,
final boolean endOfBatch) throws Exception {
- event.execute(endOfBatch);
- event.clear();
+ try {
+ event.execute(endOfBatch);
+ }
+ finally {
+ event.clear();
+ // notify the BatchEventProcessor that the sequence has progressed.
+ // Without this callback the sequence would not be progressed
+ // until the batch has completely finished.
+ notifyCallback(sequence);
+ }
+ }
- // notify the BatchEventProcessor that the sequence has progressed.
- // Without this callback the sequence would not be progressed
- // until the batch has completely finished.
+ private void notifyCallback(long sequence) {
if (++counter > NOTIFY_PROGRESS_THRESHOLD) {
sequenceCallback.set(sequence);
counter = 0;
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/async/RingBufferLogEventTranslator.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/async/RingBufferLogEventTranslator.java
index cac5d39..c536c04 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
@@ -37,7 +37,7 @@
public class RingBufferLogEventTranslator implements
EventTranslator<RingBufferLogEvent> {
- private final ContextDataInjector injector = ContextDataInjectorFactory.createInjector();
+ private static final ContextDataInjector INJECTOR = ContextDataInjectorFactory.createInjector();
private AsyncLogger asyncLogger;
String loggerName;
protected Marker marker;
@@ -60,7 +60,7 @@
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,
+ INJECTOR.injectContextData(null, (StringMap) event.getContextData()), contextStack,
threadId, threadName, threadPriority, location, clock, nanoClock);
clear(); // clear the translator
@@ -69,7 +69,7 @@
/**
* Release references held by this object to allow objects to be garbage-collected.
*/
- private void clear() {
+ void clear() {
setBasicValues(null, // asyncLogger
null, // loggerName
null, // marker
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 1741155..32e15e8 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
@@ -17,7 +17,11 @@
package org.apache.logging.log4j.core.async;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
import org.apache.logging.log4j.status.StatusLogger;
+import org.apache.logging.log4j.util.Constants;
import org.apache.logging.log4j.util.PropertiesUtil;
/**
@@ -44,24 +48,39 @@
private static final StatusLogger LOGGER = StatusLogger.getLogger();
private static final ThreadLocal<String> THREADLOCAL_NAME = new ThreadLocal<>();
+ static final ThreadNameCachingStrategy DEFAULT_STRATEGY = isAllocatingThreadGetName() ? CACHED : UNCACHED;
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);
+ final ThreadNameCachingStrategy result = name != null ? ThreadNameCachingStrategy.valueOf(name) : DEFAULT_STRATEGY;
LOGGER.debug("AsyncLogger.ThreadNameStrategy={} (user specified {}, default is {})",
- result, name, defaultStrategy);
+ result.name(), name, DEFAULT_STRATEGY.name());
return result;
} catch (final Exception ex) {
LOGGER.debug("Using AsyncLogger.ThreadNameStrategy.{}: '{}' not valid: {}",
- defaultStrategy, name, ex.toString());
- return ThreadNameCachingStrategy.valueOf(defaultStrategy);
+ DEFAULT_STRATEGY.name(), name, ex.toString());
+ return DEFAULT_STRATEGY;
}
}
-}
\ No newline at end of file
+
+ 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 {
+ Pattern javaVersionPattern = Pattern.compile("(\\d+)\\.(\\d+)\\.(\\d+)_(\\d+)");
+ Matcher m = javaVersionPattern.matcher(System.getProperty("java.version"));
+ if (m.matches()) {
+ return Integer.parseInt(m.group(3)) == 0 && Integer.parseInt(m.group(4)) < 102;
+ }
+ return true;
+ } catch (Exception e) {
+ return true;
+ }
+ } else {
+ return Constants.JAVA_MAJOR_VERSION < 8;
+ }
+ }
+}
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 34fe325..47794fa 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
@@ -40,7 +40,7 @@
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.LifeCycle;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.Version;
@@ -50,8 +50,6 @@
import org.apache.logging.log4j.core.async.AsyncLoggerConfigDelegate;
import org.apache.logging.log4j.core.async.AsyncLoggerConfigDisruptor;
import org.apache.logging.log4j.core.config.plugins.util.PluginBuilder;
-import org.apache.logging.log4j.core.config.plugins.util.PluginManager;
-import org.apache.logging.log4j.core.config.plugins.util.PluginType;
import org.apache.logging.log4j.core.filter.AbstractFilterable;
import org.apache.logging.log4j.core.layout.PatternLayout;
import org.apache.logging.log4j.core.lookup.Interpolator;
@@ -65,9 +63,15 @@
import org.apache.logging.log4j.core.util.Constants;
import org.apache.logging.log4j.core.time.internal.DummyNanoClock;
import org.apache.logging.log4j.core.util.Loader;
-import org.apache.logging.log4j.core.util.NameUtil;
+import org.apache.logging.log4j.util.NameUtil;
+import org.apache.logging.log4j.core.util.Source;
import org.apache.logging.log4j.core.time.NanoClock;
import org.apache.logging.log4j.core.util.WatchManager;
+import org.apache.logging.log4j.core.util.Watcher;
+import org.apache.logging.log4j.core.util.WatcherFactory;
+import org.apache.logging.log4j.plugins.Node;
+import org.apache.logging.log4j.plugins.util.PluginManager;
+import org.apache.logging.log4j.plugins.util.PluginType;
import org.apache.logging.log4j.util.PropertiesUtil;
/**
@@ -240,6 +244,41 @@
LOGGER.debug("Configuration {} initialized", this);
}
+ protected void initializeWatchers(Reconfigurable reconfigurable, ConfigurationSource configSource,
+ int monitorIntervalSeconds) {
+ if (configSource.getFile() != null || configSource.getURL() != null) {
+ if (monitorIntervalSeconds > 0) {
+ watchManager.setIntervalSeconds(monitorIntervalSeconds);
+ if (configSource.getFile() != null) {
+ final Source cfgSource = new Source(configSource);
+ final long lastModifeid = configSource.getFile().lastModified();
+ final ConfigurationFileWatcher watcher = new ConfigurationFileWatcher(this, reconfigurable,
+ listeners, lastModifeid);
+ 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(Reconfigurable reconfigurable, ConfigurationSource configSource) {
+ if (configSource.getLastModified() > 0) {
+ final Source cfgSource = new Source(configSource);
+ 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.
*/
@@ -251,7 +290,7 @@
}
LOGGER.debug("Starting configuration {}", this);
this.setStarting();
- if (watchManager.getIntervalSeconds() > 0) {
+ if (watchManager.getIntervalSeconds() >= 0) {
watchManager.start();
}
if (hasAsyncLoggers()) {
@@ -338,11 +377,7 @@
// LOG4J2-511, LOG4J2-392 stop AsyncAppenders first
LOGGER.trace("{} stopping {} AsyncAppenders.", cls, async.size());
for (final Appender appender : async) {
- if (appender instanceof LifeCycle2) {
- ((LifeCycle2) appender).stop(timeout, timeUnit);
- } else {
- appender.stop();
- }
+ ((LifeCycle) appender).stop(timeout, timeUnit);
}
}
@@ -356,11 +391,7 @@
int appenderCount = 0;
for (int i = array.length - 1; i >= 0; --i) {
if (array[i].isStarted()) { // then stop remaining Appenders
- if (array[i] instanceof LifeCycle2) {
- ((LifeCycle2) array[i]).stop(timeout, timeUnit);
- } else {
- array[i].stop();
- }
+ ((LifeCycle) array[i]).stop(timeout, timeUnit);
appenderCount++;
}
}
@@ -582,8 +613,8 @@
// LOG4J2-1176 facilitate memory leak investigation
setName(DefaultConfiguration.DEFAULT_NAME + "@" + Integer.toHexString(hashCode()));
final Layout<? extends Serializable> layout = PatternLayout.newBuilder()
- .withPattern(DefaultConfiguration.DEFAULT_PATTERN)
- .withConfiguration(this)
+ .setPattern(DefaultConfiguration.DEFAULT_PATTERN)
+ .setConfiguration(this)
.build();
final Appender appender = ConsoleAppender.createDefaultAppenderForLayout(layout);
appender.start();
@@ -646,7 +677,7 @@
@Override
@SuppressWarnings("unchecked")
public <T extends Appender> T getAppender(final String appenderName) {
- return (T) appenders.get(appenderName);
+ return appenderName != null ? (T) appenders.get(appenderName) : null;
}
/**
@@ -666,7 +697,9 @@
*/
@Override
public void addAppender(final Appender appender) {
- appenders.putIfAbsent(appender.getName(), appender);
+ if (appender != null) {
+ appenders.putIfAbsent(appender.getName(), appender);
+ }
}
@Override
@@ -707,6 +740,9 @@
@Override
public synchronized void addLoggerAppender(final org.apache.logging.log4j.core.Logger logger,
final Appender appender) {
+ if (appender == null || logger == null) {
+ return;
+ }
final String loggerName = logger.getName();
appenders.putIfAbsent(appender.getName(), appender);
final LoggerConfig lc = getLoggerConfig(loggerName);
@@ -782,7 +818,7 @@
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();
@@ -904,27 +940,27 @@
/**
* Invokes a static factory method to either create the desired object or to create a builder object that creates
* the desired object. In the case of a factory method, it should be annotated with
- * {@link org.apache.logging.log4j.core.config.plugins.PluginFactory}, and each parameter should be annotated with
+ * {@link org.apache.logging.log4j.plugins.PluginFactory}, and each parameter should be annotated with
* an appropriate plugin annotation depending on what that parameter describes. Parameters annotated with
- * {@link org.apache.logging.log4j.core.config.plugins.PluginAttribute} must be a type that can be converted from a
- * string using one of the {@link org.apache.logging.log4j.core.config.plugins.convert.TypeConverter TypeConverters}
- * . Parameters with {@link org.apache.logging.log4j.core.config.plugins.PluginElement} may be any plugin class or
+ * {@link org.apache.logging.log4j.plugins.PluginAttribute} must be a type that can be converted from a
+ * string using one of the {@link org.apache.logging.log4j.plugins.convert.TypeConverter TypeConverters}
+ * . Parameters with {@link org.apache.logging.log4j.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
+ * {@link org.apache.logging.log4j.plugins.util.Builder}. In that case, a static method annotated with
+ * {@link org.apache.logging.log4j.plugins.PluginBuilderAttribute} should create the builder class, and
* the various fields in the builder class should be annotated similarly to the method parameters. However, instead
* of using PluginAttribute, one should use
- * {@link org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute} where the default value can be
+ * {@link org.apache.logging.log4j.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}).
+ * {@link org.apache.logging.log4j.plugins.Node} (
+ * {@link org.apache.logging.log4j.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.
@@ -934,8 +970,8 @@
* @param event the LogEvent that spurred the creation of this plugin
* @return the created plugin object or {@code null} if there was an error setting it up.
* @see org.apache.logging.log4j.core.config.plugins.util.PluginBuilder
- * @see org.apache.logging.log4j.core.config.plugins.visitors.PluginVisitor
- * @see org.apache.logging.log4j.core.config.plugins.convert.TypeConverter
+ * @see org.apache.logging.log4j.plugins.inject.ConfigurationInjector
+ * @see org.apache.logging.log4j.plugins.convert.TypeConverter
*/
private Object createPluginObject(final PluginType<?> type, final Node node, final LogEvent event) {
final Class<?> clazz = type.getPluginClass();
@@ -956,7 +992,7 @@
}
}
- return new PluginBuilder(type).withConfiguration(this).withConfigurationNode(node).forLogEvent(event).build();
+ return new PluginBuilder(type).setConfiguration(this).setConfigurationNode(node).forLogEvent(event).build();
}
private static Map<String, ?> 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 de0a777..ee2f28f 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
@@ -46,7 +46,7 @@
* @param filter the Filter(s) to apply.
*/
public AppenderControl(final Appender appender, final Level level, final Filter filter) {
- super(filter);
+ super(filter, null);
this.appender = appender;
this.appenderName = appender.getName();
this.level = level;
@@ -155,14 +155,14 @@
try {
appender.append(event);
} catch (final RuntimeException ex) {
- handleAppenderError(ex);
+ handleAppenderError(event, ex);
} catch (final Exception ex) {
- handleAppenderError(new AppenderLoggingException(ex));
+ handleAppenderError(event, new AppenderLoggingException(ex));
}
}
- 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/AppenderRef.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AppenderRef.java
index e0ee77a..04a932b 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AppenderRef.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AppenderRef.java
@@ -17,14 +17,14 @@
package org.apache.logging.log4j.core.config;
import org.apache.logging.log4j.Level;
-import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.core.Filter;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginAliases;
-import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
-import org.apache.logging.log4j.core.config.plugins.PluginElement;
-import org.apache.logging.log4j.core.config.plugins.PluginFactory;
-import org.apache.logging.log4j.status.StatusLogger;
+import org.apache.logging.log4j.plugins.Node;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginAliases;
+import org.apache.logging.log4j.plugins.PluginAttribute;
+import org.apache.logging.log4j.plugins.PluginElement;
+import org.apache.logging.log4j.plugins.PluginFactory;
+import org.apache.logging.log4j.plugins.validation.constraints.Required;
/**
* An Appender reference.
@@ -32,7 +32,6 @@
@Plugin(name = "AppenderRef", category = Node.CATEGORY, printObject = true)
@PluginAliases("appender-ref")
public final class AppenderRef {
- private static final Logger LOGGER = StatusLogger.getLogger();
private final String ref;
private final Level level;
@@ -70,14 +69,9 @@
*/
@PluginFactory
public static AppenderRef createAppenderRef(
- @PluginAttribute("ref") final String ref,
- @PluginAttribute("level") final Level level,
- @PluginElement("Filter") final Filter filter) {
-
- if (ref == null) {
- LOGGER.error("Appender references must contain a reference");
- return null;
- }
+ @PluginAttribute @Required(message = "Appender references must contain a reference") final String ref,
+ @PluginAttribute final Level level,
+ @PluginElement final Filter filter) {
return new AppenderRef(ref, level, filter);
}
}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AppendersPlugin.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AppendersPlugin.java
index 03f5e06..60645bd 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
@@ -21,9 +21,9 @@
import org.apache.logging.log4j.core.Appender;
import org.apache.logging.log4j.core.Core;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginElement;
-import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginElement;
+import org.apache.logging.log4j.plugins.PluginFactory;
/**
* An Appender container.
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 ccc35d0..e9031e8 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
@@ -49,7 +49,7 @@
/*
* (non-Javadoc)
- *
+ *
* @see org.apache.logging.log4j.core.config.ReliabilityStrategy#log(org.apache.logging.log4j.util.Supplier,
* java.lang.String, java.lang.String, org.apache.logging.log4j.Marker, org.apache.logging.log4j.Level,
* org.apache.logging.log4j.message.Message, java.lang.Throwable)
@@ -68,7 +68,26 @@
/*
* (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<LoggerConfig> reconfigured, final String loggerName, final String fqcn,
+ final StackTraceElement location, final Marker marker, final Level level, final Message data,
+ final Throwable t) {
+ final LoggerConfig config = getActiveLoggerConfig(reconfigured);
+ try {
+ config.log(loggerName, fqcn, location, marker, level, data, t);
+ } finally {
+ config.getReliabilityStrategy().afterLogEvent();
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
* @see org.apache.logging.log4j.core.config.ReliabilityStrategy#log(org.apache.logging.log4j.util.Supplier,
* org.apache.logging.log4j.core.LogEvent)
*/
@@ -84,7 +103,7 @@
/*
* (non-Javadoc)
- *
+ *
* @see
* org.apache.logging.log4j.core.config.ReliabilityStrategy#beforeLogEvent(org.apache.logging.log4j.core.config.
* LoggerConfig, org.apache.logging.log4j.util.Supplier)
@@ -94,7 +113,7 @@
LoggerConfig result = this.loggerConfig;
if (!beforeLogEvent()) {
result = next.get();
- return result.getReliabilityStrategy().getActiveLoggerConfig(next);
+ return result == this.loggerConfig ? result : result.getReliabilityStrategy().getActiveLoggerConfig(next);
}
return result;
}
@@ -122,7 +141,7 @@
/*
* (non-Javadoc)
- *
+ *
* @see org.apache.logging.log4j.core.config.ReliabilityStrategy#beforeStopAppenders()
*/
@Override
@@ -162,7 +181,7 @@
/*
* (non-Javadoc)
- *
+ *
* @see
* org.apache.logging.log4j.core.config.ReliabilityStrategy#beforeStopConfiguration(org.apache.logging.log4j.core
* .config.Configuration)
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AwaitUnconditionallyReliabilityStrategy.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/AwaitUnconditionallyReliabilityStrategy.java
index 357f18b..ce88285 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
@@ -60,6 +60,20 @@
/*
* (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<LoggerConfig> 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)
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 b2e5cb7..d47a02f 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
@@ -32,6 +32,7 @@
import org.apache.logging.log4j.core.script.ScriptManager;
import org.apache.logging.log4j.core.time.NanoClock;
import org.apache.logging.log4j.core.util.WatchManager;
+import org.apache.logging.log4j.plugins.Node;
/**
* Interface that must be implemented to create a configuration.
@@ -40,7 +41,6 @@
* </p>
*
* @see AbstractConfiguration
- * @see org.apache.logging.log4j.core.LifeCycle2
*/
public interface Configuration extends Filterable {
@@ -135,7 +135,10 @@
/**
* 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();
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 7e6dfb2..7112f56 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
@@ -19,8 +19,12 @@
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
+import java.io.UnsupportedEncodingException;
import java.net.URI;
+import java.net.URISyntaxException;
import java.net.URL;
+import java.net.URLConnection;
+import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -34,16 +38,20 @@
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilderFactory;
import org.apache.logging.log4j.core.config.composite.CompositeConfiguration;
-import org.apache.logging.log4j.core.config.plugins.util.PluginManager;
-import org.apache.logging.log4j.core.config.plugins.util.PluginType;
import org.apache.logging.log4j.core.lookup.Interpolator;
import org.apache.logging.log4j.core.lookup.StrSubstitutor;
+import org.apache.logging.log4j.core.net.UrlConnectionFactory;
+import org.apache.logging.log4j.core.util.AuthorizationProvider;
+import org.apache.logging.log4j.core.util.BasicAuthorizationProvider;
import org.apache.logging.log4j.core.util.FileUtils;
+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.plugins.util.PluginManager;
+import org.apache.logging.log4j.plugins.util.PluginType;
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.ReflectionUtil;
import org.apache.logging.log4j.util.Strings;
/**
@@ -86,8 +94,14 @@
*/
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 = "log4j2.authorizationProvider";
+
/**
- * Plugin category used to inject a ConfigurationFactory {@link org.apache.logging.log4j.core.config.plugins.Plugin}
+ * Plugin category used to inject a ConfigurationFactory {@link org.apache.logging.log4j.plugins.Plugin}
* class.
*
* @since 2.1
@@ -109,6 +123,9 @@
*/
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,7 +136,9 @@
*/
private static final String CLASS_PATH_SCHEME = "classpath";
- private static volatile List<ConfigurationFactory> factories = null;
+ private static final String OVERRIDE_PARAM = "override";
+
+ private static volatile List<ConfigurationFactory> factories;
private static ConfigurationFactory configFactory = new Factory();
@@ -127,6 +146,11 @@
private static final Lock LOCK = new ReentrantLock();
+ private static final String HTTPS = "https";
+ private static final String HTTP = "http";
+
+ private static volatile AuthorizationProvider authorizationProvider;
+
/**
* Returns the ConfigurationFactory.
* @return the ConfigurationFactory.
@@ -139,7 +163,8 @@
try {
if (factories == null) {
final List<ConfigurationFactory> list = new ArrayList<>();
- final String factoryClass = PropertiesUtil.getProperties().getStringProperty(CONFIGURATION_FACTORY_PROPERTY);
+ PropertiesUtil props = PropertiesUtil.getProperties();
+ final String factoryClass = props.getStringProperty(CONFIGURATION_FACTORY_PROPERTY);
if (factoryClass != null) {
addFactory(list, factoryClass);
}
@@ -161,6 +186,7 @@
// see above comments about double-checked locking
//noinspection NonThreadSafeLazyInitialization
factories = Collections.unmodifiableList(list);
+ authorizationProvider = authorizationProvider(props);
}
} finally {
LOCK.unlock();
@@ -171,9 +197,34 @@
return configFactory;
}
+ public static AuthorizationProvider authorizationProvider(PropertiesUtil props) {
+ final String authClass = props.getStringProperty(AUTHORIZATION_PROVIDER);
+ AuthorizationProvider provider = null;
+ if (authClass != null) {
+ try {
+ 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<ConfigurationFactory> 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);
}
@@ -216,6 +267,18 @@
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;
}
@@ -293,7 +356,13 @@
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()));
+ URLConnection urlConnection = UrlConnectionFactory.createConnection(url);
+ File file = FileUtils.fileFromUri(url.toURI());
+ if (file != null) {
+ return new ConfigurationSource(urlConnection.getInputStream(), FileUtils.fileFromUri(url.toURI()));
+ } else {
+ return new ConfigurationSource(urlConnection.getInputStream(), url, urlConnection.getLastModified());
+ }
} catch (final Exception ex) {
final ConfigurationSource source = ConfigurationSource.fromResource(config, loader);
if (source == null) {
@@ -329,7 +398,7 @@
final String configLocationStr = this.substitutor.replace(PropertiesUtil.getProperties()
.getStringProperty(CONFIGURATION_FILE_PROPERTY));
if (configLocationStr != null) {
- final String[] sources = configLocationStr.split(",");
+ String[] sources = parseConfigLocations(configLocationStr);
if (sources.length > 1) {
final List<AbstractConfiguration> configs = new ArrayList<>();
for (final String sourceLocation : sources) {
@@ -344,6 +413,13 @@
return new CompositeConfiguration(configs);
}
return getConfiguration(loggerContext, configLocationStr);
+ } else {
+ 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();
@@ -360,6 +436,20 @@
}
} else {
// configLocation != null
+ String[] sources = parseConfigLocations(configLocation);
+ if (sources.length > 1) {
+ final List<AbstractConfiguration> configs = new ArrayList<>();
+ for (final String sourceLocation : sources) {
+ final Configuration config = getConfiguration(loggerContext, sourceLocation.trim());
+ if (config instanceof AbstractConfiguration) {
+ configs.add((AbstractConfiguration) config);
+ } else {
+ LOGGER.error("Failed to created configuration at {}", sourceLocation);
+ return null;
+ }
+ }
+ return new CompositeConfiguration(configs);
+ }
final String configLocationStr = configLocation.toString();
for (final ConfigurationFactory factory : getFactories()) {
final String[] types = factory.getSupportedTypes();
@@ -389,7 +479,7 @@
if (config != null) {
return config;
}
- LOGGER.error("No Log4j 2 configuration file found. " +
+ 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' " +
@@ -399,6 +489,11 @@
}
private Configuration getConfiguration(final LoggerContext loggerContext, final String configLocationStr) {
+ return getConfiguration(null, loggerContext, configLocationStr);
+ }
+
+ private Configuration getConfiguration(String requiredVersion, final LoggerContext loggerContext,
+ final String configLocationStr) {
ConfigurationSource source = null;
try {
source = ConfigurationSource.fromUri(NetUtils.toURI(configLocationStr));
@@ -412,6 +507,9 @@
}
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) {
@@ -433,7 +531,7 @@
final ClassLoader loader = LoaderUtil.getThreadContextClassLoader();
for (final ConfigurationFactory factory : getFactories()) {
String configName;
- final String prefix = isTest ? TEST_PREFIX : DEFAULT_PREFIX;
+ final String prefix = isTest ? factory.getTestPrefix() : factory.getDefaultPrefix();
final String [] types = factory.getSupportedTypes();
if (types == null) {
continue;
@@ -486,6 +584,41 @@
LOGGER.error("Cannot process configuration, input source is null");
return null;
}
+
+ private String[] parseConfigLocations(URI configLocations) {
+ final String[] uris = configLocations.toString().split("\\?");
+ final List<String> 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(new String[0]);
+ }
+ return new String[] {uris[0]};
+ }
+
+ private String[] parseConfigLocations(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<ConfigurationFactory> 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 0000000..e777fbf
--- /dev/null
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ConfigurationFileWatcher.java
@@ -0,0 +1,72 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.logging.log4j.core.config;
+
+import java.io.File;
+import java.util.List;
+
+import org.apache.logging.log4j.core.util.AbstractWatcher;
+import org.apache.logging.log4j.core.util.FileWatcher;
+import org.apache.logging.log4j.core.util.Source;
+import org.apache.logging.log4j.core.util.Watcher;
+
+/**
+ * Watcher for configuration files. Causes a reconfiguration when a file changes.
+ */
+public class ConfigurationFileWatcher extends AbstractWatcher implements FileWatcher {
+
+ private File file;
+ private long lastModifiedMillis;
+
+ public ConfigurationFileWatcher(final Configuration configuration, final Reconfigurable reconfigurable,
+ final List<ConfigurationListener> configurationListeners, long lastModifiedMillis) {
+ super(configuration, reconfigurable, configurationListeners);
+ this.lastModifiedMillis = lastModifiedMillis;
+ }
+
+ public long getLastModified() {
+ return file != null ? file.lastModified() : 0;
+ }
+
+ @Override
+ public void fileModified(final File file) {
+ lastModifiedMillis = file.lastModified();
+ }
+
+ @Override
+ public void watching(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<ConfigurationListener> listeners,
+ long lastModifiedMillis) {
+ ConfigurationFileWatcher watcher = new ConfigurationFileWatcher(getConfiguration(), reconfigurable, listeners,
+ lastModifiedMillis);
+ if (getSource() != null) {
+ watcher.watching(getSource());
+ }
+ return watcher;
+ }
+}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ConfigurationScheduler.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ConfigurationScheduler.java
index 5341337..e393d4c 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ConfigurationScheduler.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ConfigurationScheduler.java
@@ -38,8 +38,8 @@
private static final Logger LOGGER = StatusLogger.getLogger();
private static final String SIMPLE_NAME = "Log4j2 " + ConfigurationScheduler.class.getSimpleName();
private static final int MAX_SCHEDULED_ITEMS = 5;
-
- private ScheduledExecutorService executorService;
+
+ private volatile ScheduledExecutorService executorService;
private int scheduledItems = 0;
private final String name;
@@ -193,17 +193,21 @@
private ScheduledExecutorService getExecutorService() {
if (executorService == null) {
- if (scheduledItems > 0) {
- LOGGER.debug("{} starting {} threads", name, scheduledItems);
- scheduledItems = Math.min(scheduledItems, MAX_SCHEDULED_ITEMS);
- final ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(scheduledItems,
- Log4jThreadFactory.createDaemonThreadFactory("Scheduled"));
- executor.setContinueExistingPeriodicTasksAfterShutdownPolicy(false);
- executor.setExecuteExistingDelayedTasksAfterShutdownPolicy(false);
- this.executorService = executor;
+ synchronized (this) {
+ if (executorService == null) {
+ if (scheduledItems > 0) {
+ LOGGER.debug("{} starting {} threads", name, scheduledItems);
+ scheduledItems = Math.min(scheduledItems, MAX_SCHEDULED_ITEMS);
+ final ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(scheduledItems,
+ Log4jThreadFactory.createDaemonThreadFactory("Scheduled"));
+ executor.setContinueExistingPeriodicTasksAfterShutdownPolicy(false);
+ executor.setExecuteExistingDelayedTasksAfterShutdownPolicy(false);
+ this.executorService = executor;
- } else {
- LOGGER.debug("{}: No scheduled items", name);
+ } else {
+ LOGGER.debug("{}: No scheduled items", name);
+ }
+ }
}
}
return executorService;
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ConfigurationSource.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ConfigurationSource.java
index 7848d6a..ad94891 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
@@ -28,27 +28,41 @@
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
+import java.net.URLConnection;
import java.util.Objects;
import org.apache.logging.log4j.Level;
+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.util.LoaderUtil;
/**
* Represents the source for the logging configuration.
*/
public class ConfigurationSource {
+
/**
* ConfigurationSource to use with Configurations that do not require a "real" configuration source.
*/
- public static final ConfigurationSource NULL_SOURCE = new ConfigurationSource(new byte[0]);
+ public static final ConfigurationSource NULL_SOURCE = new ConfigurationSource(new byte[0], null, 0);
+ /**
+ * ConfigurationSource to use with {@link org.apache.logging.log4j.core.config.composite.CompositeConfiguration}.
+ */
+ public static final ConfigurationSource COMPOSITE_SOURCE = new ConfigurationSource(new byte[0], null, 0);
+ private static final String HTTPS = "https";
+ private static final String HTTP = "http";
private final File file;
private final URL url;
private final String location;
private final InputStream stream;
- private final byte[] data;
+ private volatile byte[] data;
+ private volatile Source source;
+ private final long lastModified;
+ // Set when the configuration has been updated so reset can use it for the next lastModified timestamp.
+ private volatile long modifiedMillis;
/**
* Constructs a new {@code ConfigurationSource} with the specified input stream that originated from the specified
@@ -63,6 +77,30 @@
this.location = file.getAbsolutePath();
this.url = null;
this.data = null;
+ long modified = 0;
+ try {
+ modified = file.lastModified();
+ } catch (Exception ex) {
+ // There is a problem with the file. It will be handled somewhere else.
+ }
+ this.lastModified = modified;
+ }
+
+ /**
+ * Constructs a new {@code ConfigurationSource} with the specified input stream that originated from the specified
+ * url.
+ *
+ * @param stream the input stream
+ * @param url the URL where the input stream originated
+ * @param lastModified when the source was last modified.
+ */
+ public ConfigurationSource(final InputStream stream, final URL url, long lastModified) {
+ this.stream = Objects.requireNonNull(stream, "stream is null");
+ this.url = Objects.requireNonNull(url, "URL is null");
+ this.location = url.toString();
+ this.file = null;
+ this.data = null;
+ this.lastModified = lastModified;
}
/**
@@ -78,6 +116,7 @@
this.location = url.toString();
this.file = null;
this.data = null;
+ this.lastModified = 0;
}
/**
@@ -88,15 +127,29 @@
* @throws IOException if an exception occurred reading from the specified stream
*/
public ConfigurationSource(final InputStream stream) throws IOException {
- this(toByteArray(stream));
+ this(toByteArray(stream), null, 0);
}
- private ConfigurationSource(final byte[] data) {
+ public ConfigurationSource(final Source source, final byte[] data, long lastModified) throws IOException {
+ Objects.requireNonNull(source, "source is null");
+ this.data = Objects.requireNonNull(data, "data is null");
+ this.stream = new ByteArrayInputStream(data);
+ this.file = source.getFile();
+ this.url = source.getURI().toURL();
+ this.location = source.getLocation();
+ this.lastModified = lastModified;
+ }
+
+ private ConfigurationSource(final byte[] data, final URL url, long lastModified) {
this.data = Objects.requireNonNull(data, "data is null");
this.stream = new ByteArrayInputStream(data);
this.file = null;
- this.url = null;
+ this.url = url;
this.location = null;
+ this.lastModified = lastModified;
+ if ( url == null ) {
+ this.data = data;
+ }
}
/**
@@ -139,6 +192,18 @@
return url;
}
+ public void setSource(Source source) {
+ this.source = source;
+ }
+
+ public void setData(byte[] data) {
+ this.data = data;
+ }
+
+ public void setModifiedMillis(long modifiedMillis) {
+ this.modifiedMillis = modifiedMillis;
+ }
+
/**
* Returns a URI representing the configuration resource or null if it cannot be determined.
* @return The URI.
@@ -171,6 +236,14 @@
}
/**
+ * Returns the time the resource was last modified or 0 if it is not available.
+ * @return the last modified time of the resource.
+ */
+ public long getLastModified() {
+ return lastModified;
+ }
+
+ /**
* Returns a string describing the configuration source file or URL, or {@code null} if this configuration source
* has neither a file nor an URL.
*
@@ -196,13 +269,19 @@
* @throws IOException if a problem occurred while opening the new input stream
*/
public ConfigurationSource resetInputStream() throws IOException {
- if (file != null) {
+ if (source != null) {
+ return new ConfigurationSource(source, data, this.lastModified);
+ } else if (file != null) {
return new ConfigurationSource(new FileInputStream(file), file);
+ } else if (url != null && data != null) {
+ // Creates a ConfigurationSource without accessing the URL since the data was provided.
+ return new ConfigurationSource(data, url, modifiedMillis == 0 ? lastModified : modifiedMillis);
} else if (url != null) {
- return new ConfigurationSource(url.openStream(), url);
- } else {
- return new ConfigurationSource(data);
+ return fromUri(getURI());
+ } else if (data != null) {
+ return new ConfigurationSource(data, null, lastModified);
}
+ return null;
}
@Override
@@ -213,6 +292,9 @@
if (this == NULL_SOURCE) {
return "NULL_SOURCE";
}
+ if (this == COMPOSITE_SOURCE) {
+ return "COMPOSITE_SOURCE";
+ }
final int length = data == null ? -1 : data.length;
return "stream (" + length + " bytes, unknown location)";
}
@@ -244,7 +326,11 @@
return null;
}
try {
- return new ConfigurationSource(configLocation.toURL().openStream(), configLocation.toURL());
+ URL url = configLocation.toURL();
+ URLConnection urlConnection = UrlConnectionFactory.createConnection(url);
+ InputStream is = urlConnection.getInputStream();
+ long lastModified = urlConnection.getLastModified();
+ return new ConfigurationSource(is, configLocation.toURL(), lastModified);
} catch (final MalformedURLException ex) {
ConfigurationFactory.LOGGER.error("Invalid URL {}", configLocation.toString(), ex);
} catch (final Exception ex) {
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 38491f2..0000000
--- 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<ConfigurationListener> configurationListeners;
- private final Log4jThreadFactory threadFactory;
-
- public ConfiguratonFileWatcher(final Reconfigurable reconfigurable, final List<ConfigurationListener> configurationListeners) {
- this.reconfigurable = reconfigurable;
- this.configurationListeners = configurationListeners;
- this.threadFactory = Log4jThreadFactory.createDaemonThreadFactory("ConfiguratonFileWatcher");
- }
-
- public List<ConfigurationListener> 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 28dd85f..8effd0a 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
@@ -227,6 +227,59 @@
}
/**
+ * 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 {
+ 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 {
+ 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 <code>parentLogger</code> and all 'child' loggers to the given <code>level</code>.
* @param parentLogger the parent logger
* @param level the new level
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 ca6bd9b..d54a9f0 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
@@ -20,9 +20,9 @@
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.core.Core;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
-import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginAttribute;
+import org.apache.logging.log4j.plugins.PluginFactory;
import org.apache.logging.log4j.status.StatusLogger;
/**
@@ -50,7 +50,7 @@
@PluginFactory
public static CustomLevelConfig createLevel(// @formatter:off
@PluginAttribute("name") final String levelName,
- @PluginAttribute("intLevel") final int intLevel) {
+ @PluginAttribute final int intLevel) {
// @formatter:on
StatusLogger.getLogger().debug("Creating CustomLevel(name='{}', intValue={})", levelName, intLevel);
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 0b43858..18838f7 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
@@ -22,9 +22,9 @@
import java.util.List;
import org.apache.logging.log4j.core.Core;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginElement;
-import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginElement;
+import org.apache.logging.log4j.plugins.PluginFactory;
/**
* Container for CustomLevelConfig objects.
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 cc0e1bc..5c4185f 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
@@ -18,8 +18,9 @@
import java.util.Map;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.apache.logging.log4j.core.net.Advertiser;
+import org.apache.logging.log4j.plugins.Node;
+import org.apache.logging.log4j.plugins.Plugin;
/**
* The default advertiser does not do anything.
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 18fcae4..4777f2f 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
@@ -51,6 +51,20 @@
/*
* (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<LoggerConfig> 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)
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 0000000..485481d
--- /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 java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.List;
+
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginAliases;
+import org.apache.logging.log4j.core.net.UrlConnectionFactory;
+import org.apache.logging.log4j.core.net.ssl.SslConfiguration;
+import org.apache.logging.log4j.core.net.ssl.SslConfigurationFactory;
+import org.apache.logging.log4j.core.util.AbstractWatcher;
+import org.apache.logging.log4j.core.util.Source;
+import org.apache.logging.log4j.core.util.Watcher;
+import org.apache.logging.log4j.status.StatusLogger;
+
+/**
+ *
+ */
+@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 final SslConfiguration sslConfiguration;
+ private URL url;
+ private volatile long lastModifiedMillis;
+ private static final int NOT_MODIFIED = 304;
+ private static final int OK = 200;
+ private static final int BUF_SIZE = 1024;
+ private static final String HTTP = "http";
+ private static final String HTTPS = "https";
+
+ public HttpWatcher(final Configuration configuration, final Reconfigurable reconfigurable,
+ final List<ConfigurationListener> configurationListeners, long lastModifiedMillis) {
+ super(configuration, reconfigurable, configurationListeners);
+ sslConfiguration = SslConfigurationFactory.getSslConfiguration();
+ this.lastModifiedMillis = lastModifiedMillis;
+ }
+
+ @Override
+ public long getLastModified() {
+ return lastModifiedMillis;
+ }
+
+ @Override
+ public boolean isModified() {
+ return refreshConfiguration();
+ }
+
+ @Override
+ public void watching(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();
+ } catch (MalformedURLException ex) {
+ throw new IllegalArgumentException("Invalid URL for HttpWatcher " + source.getURI(), ex);
+ }
+ super.watching(source);
+ }
+
+ @Override
+ public Watcher newWatcher(Reconfigurable reconfigurable, List<ConfigurationListener> listeners,
+ long lastModifiedMillis) {
+ HttpWatcher watcher = new HttpWatcher(getConfiguration(), reconfigurable, listeners, lastModifiedMillis);
+ if (getSource() != null) {
+ watcher.watching(getSource());
+ }
+ return watcher;
+ }
+
+ private boolean refreshConfiguration() {
+ try {
+ final HttpURLConnection urlConnection = UrlConnectionFactory.createConnection(url, lastModifiedMillis,
+ sslConfiguration);
+ urlConnection.connect();
+
+ try {
+ int code = urlConnection.getResponseCode();
+ switch (code) {
+ case NOT_MODIFIED: {
+ LOGGER.debug("Configuration Not Modified");
+ return false;
+ }
+ case OK: {
+ try (InputStream is = urlConnection.getInputStream()) {
+ ConfigurationSource configSource = getConfiguration().getConfigurationSource();
+ configSource.setData(readStream(is));
+ lastModifiedMillis = urlConnection.getLastModified();
+ configSource.setModifiedMillis(lastModifiedMillis);
+ LOGGER.debug("Content was modified for {}", url.toString());
+ return true;
+ } catch (final IOException e) {
+ try (InputStream es = urlConnection.getErrorStream()) {
+ LOGGER.info("Error accessing configuration at {}: {}", url, readStream(es));
+ } catch (final IOException ioe) {
+ LOGGER.error("Error accessing configuration at {}: {}", url, e.getMessage());
+ }
+ return false;
+ }
+ }
+ default: {
+ if (code < 0) {
+ LOGGER.info("Invalid response code returned");
+ } else {
+ LOGGER.info("Unexpected response code returned {}", code);
+ }
+ return false;
+ }
+ }
+ } catch (final IOException ioe) {
+ LOGGER.error("Error accessing configuration at {}: {}", url, ioe.getMessage());
+ }
+ } catch (final IOException ioe) {
+ LOGGER.error("Error connecting to configuration at {}: {}", url, ioe.getMessage());
+ }
+ return false;
+ }
+
+ private byte[] readStream(InputStream is) throws IOException {
+ ByteArrayOutputStream result = new ByteArrayOutputStream();
+ byte[] buffer = new byte[BUF_SIZE];
+ int length;
+ while ((length = is.read(buffer)) != -1) {
+ result.write(buffer, 0, length);
+ }
+ return result.toByteArray();
+ }
+}
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 c930f8b..21e3557 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/LockingReliabilityStrategy.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/LockingReliabilityStrategy.java
@@ -33,7 +33,7 @@
public class LockingReliabilityStrategy implements ReliabilityStrategy {
private final LoggerConfig loggerConfig;
private final ReadWriteLock reconfigureLock = new ReentrantReadWriteLock();
- private volatile boolean isStopping = false;
+ private volatile boolean isStopping;
public LockingReliabilityStrategy(final LoggerConfig loggerConfig) {
this.loggerConfig = Objects.requireNonNull(loggerConfig, "loggerConfig was null");
@@ -41,7 +41,7 @@
/*
* (non-Javadoc)
- *
+ *
* @see org.apache.logging.log4j.core.config.ReliabilityStrategy#log(org.apache.logging.log4j.util.Supplier,
* java.lang.String, java.lang.String, org.apache.logging.log4j.Marker, org.apache.logging.log4j.Level,
* org.apache.logging.log4j.message.Message, java.lang.Throwable)
@@ -60,7 +60,26 @@
/*
* (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<LoggerConfig> reconfigured, final String loggerName, final String fqcn,
+ final StackTraceElement location, final Marker marker, final Level level, final Message data,
+ final Throwable t) {
+ final LoggerConfig config = getActiveLoggerConfig(reconfigured);
+ try {
+ config.log(loggerName, fqcn, location, marker, level, data, t);
+ } finally {
+ config.getReliabilityStrategy().afterLogEvent();
+ }
+ }
+
+ /*
+ * (non-Javadoc)
+ *
* @see org.apache.logging.log4j.core.config.ReliabilityStrategy#log(org.apache.logging.log4j.util.Supplier,
* org.apache.logging.log4j.core.LogEvent)
*/
@@ -76,7 +95,7 @@
/*
* (non-Javadoc)
- *
+ *
* @see
* org.apache.logging.log4j.core.config.ReliabilityStrategy#beforeLogEvent(org.apache.logging.log4j.core.config.
* LoggerConfig, org.apache.logging.log4j.util.Supplier)
@@ -86,7 +105,7 @@
LoggerConfig result = this.loggerConfig;
if (!beforeLogEvent()) {
result = next.get();
- return result.getReliabilityStrategy().getActiveLoggerConfig(next);
+ return result == this.loggerConfig ? result : result.getReliabilityStrategy().getActiveLoggerConfig(next);
}
return result;
}
@@ -107,7 +126,7 @@
/*
* (non-Javadoc)
- *
+ *
* @see org.apache.logging.log4j.core.config.ReliabilityStrategy#beforeStopAppenders()
*/
@Override
@@ -122,7 +141,7 @@
/*
* (non-Javadoc)
- *
+ *
* @see
* org.apache.logging.log4j.core.config.ReliabilityStrategy#beforeStopConfiguration(org.apache.logging.log4j.core
* .config.Configuration)
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/LoggerConfig.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/LoggerConfig.java
index 8db7c32..4170f42 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
@@ -19,7 +19,6 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
-import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -34,12 +33,7 @@
import org.apache.logging.log4j.core.async.AsyncLoggerConfig;
import org.apache.logging.log4j.core.async.AsyncLoggerContext;
import org.apache.logging.log4j.core.async.AsyncLoggerContextSelector;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
-import org.apache.logging.log4j.core.config.plugins.PluginElement;
-import org.apache.logging.log4j.core.config.plugins.PluginFactory;
-import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required;
import org.apache.logging.log4j.core.filter.AbstractFilterable;
import org.apache.logging.log4j.core.impl.DefaultLogEventFactory;
import org.apache.logging.log4j.core.impl.Log4jLogEvent;
@@ -48,10 +42,17 @@
import org.apache.logging.log4j.core.lookup.StrSubstitutor;
import org.apache.logging.log4j.core.util.Booleans;
import org.apache.logging.log4j.core.util.Constants;
+import org.apache.logging.log4j.core.util.Loader;
import org.apache.logging.log4j.message.Message;
-import org.apache.logging.log4j.util.LoaderUtil;
+import org.apache.logging.log4j.plugins.Node;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginAttribute;
+import org.apache.logging.log4j.plugins.PluginElement;
+import org.apache.logging.log4j.plugins.PluginFactory;
+import org.apache.logging.log4j.plugins.validation.constraints.Required;
import org.apache.logging.log4j.util.PerformanceSensitive;
import org.apache.logging.log4j.util.PropertiesUtil;
+import org.apache.logging.log4j.util.StackLocatorUtil;
import org.apache.logging.log4j.util.Strings;
/**
@@ -81,7 +82,7 @@
final String factory = PropertiesUtil.getProperties().getStringProperty(Constants.LOG4J_LOG_EVENT_FACTORY);
if (factory != null) {
try {
- final Class<?> clazz = LoaderUtil.loadClass(factory);
+ final Class<?> clazz = Loader.loadClass(factory);
if (clazz != null && LogEventFactory.class.isAssignableFrom(clazz)) {
LOG_EVENT_FACTORY = (LogEventFactory) clazz.newInstance();
}
@@ -130,7 +131,7 @@
protected LoggerConfig(final String name, final List<AppenderRef> appenders, final Filter filter,
final Level level, final boolean additive, final Property[] properties, final Configuration config,
final boolean includeLocation) {
- super(filter);
+ super(filter, null);
this.logEventFactory = LOG_EVENT_FACTORY;
this.name = name;
this.appenderRefs = appenders;
@@ -318,34 +319,6 @@
}
/**
- * Returns an unmodifiable map with the configuration properties, or {@code null} if this {@code LoggerConfig} does
- * not have any configuration properties.
- * <p>
- * For each {@code Property} key in the map, the value is {@code true} if the property value has a variable that
- * needs to be substituted.
- *
- * @return an unmodifiable map with the configuration properties, or {@code null}
- * @see Configuration#getStrSubstitutor()
- * @see StrSubstitutor
- * @deprecated use {@link #getPropertyList()} instead
- */
- // LOG4J2-157
- @Deprecated
- public Map<Property, Boolean> getProperties() {
- if (properties == null) {
- return null;
- }
- if (propertiesMap == null) { // lazily initialize: only used by user custom code, not by Log4j any more
- final Map<Property, Boolean> result = new HashMap<>(properties.size() * 2);
- for (int i = 0; i < properties.size(); i++) {
- result.put(properties.get(i), Boolean.valueOf(properties.get(i).isValueNeedsLookup()));
- }
- propertiesMap = Collections.unmodifiableMap(result);
- }
- return propertiesMap;
- }
-
- /**
* Returns an unmodifiable list with the configuration properties, or {@code null} if this {@code LoggerConfig} does
* not have any configuration properties.
* <p>
@@ -401,7 +374,54 @@
}
}
}
- final LogEvent logEvent = logEventFactory.createEvent(loggerName, marker, fqcn, level, data, props, t);
+ StackTraceElement location = requiresLocation() ? StackLocatorUtil.calcLocation(fqcn) : null;
+ final LogEvent logEvent = logEventFactory.createEvent(loggerName, marker, fqcn, location, 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);
+ }
+ }
+
+ /**
+ * 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) {
+ List<Property> props = null;
+ if (!propertiesRequireLookup) {
+ props = properties;
+ } else {
+ if (properties != null) {
+ props = new ArrayList<>(properties.size());
+ final LogEvent event = Log4jLogEvent.newBuilder()
+ .setMessage(data)
+ .setMarker(marker)
+ .setLevel(level)
+ .setLoggerName(loggerName)
+ .setLoggerFqcn(fqcn)
+ .setThrown(t)
+ .build();
+ for (int i = 0; i < properties.size(); i++) {
+ final Property prop = properties.get(i);
+ final String value = prop.isValueNeedsLookup() // since LOG4J2-1575
+ ? config.getStrSubstitutor().replace(event, prop.getValue()) //
+ : prop.getValue();
+ props.add(Property.createProperty(prop.getName(), value));
+ }
+ }
+ }
+ final LogEvent logEvent = logEventFactory.createEvent(loggerName, marker, fqcn, location, level, data, props, t);
try {
log(logEvent, LoggerConfigPredicate.ALL);
} finally {
@@ -450,6 +470,30 @@
logParent(event, predicate);
}
+ public boolean requiresLocation() {
+ if (!includeLocation) {
+ return false;
+ }
+ AppenderControl[] controls = appenders.get();
+ LoggerConfig loggerConfig = this;
+ while (loggerConfig != null) {
+ for (AppenderControl control : controls) {
+ if (control.getAppender().requiresLocation()) {
+ return true;
+ }
+ }
+ if (loggerConfig.additive) {
+ loggerConfig = loggerConfig.parent;
+ if (loggerConfig != null) {
+ controls = loggerConfig.appenders.get();
+ }
+ } else {
+ break;
+ }
+ }
+ return false;
+ }
+
private void logParent(final LogEvent event, final LoggerConfigPredicate predicate) {
if (additive && parent != null) {
parent.log(event, predicate);
@@ -473,44 +517,6 @@
/**
* Factory method to create a LoggerConfig.
*
- * @param additivity True if additive, false otherwise.
- * @param level The Level to be associated with the Logger.
- * @param loggerName The name of the Logger.
- * @param includeLocation whether location should be passed downstream
- * @param refs An array of Appender names.
- * @param properties Properties to pass to the Logger.
- * @param config The Configuration.
- * @param filter A Filter.
- * @return A new LoggerConfig.
- * @deprecated Deprecated in 2.7; use {@link #createLogger(boolean, Level, String, String, AppenderRef[], Property[], Configuration, Filter)}
- */
- @Deprecated
- public static LoggerConfig createLogger(final String additivity,
- // @formatter:off
- final Level level,
- @PluginAttribute("name") final String loggerName,
- final String includeLocation,
- final AppenderRef[] refs,
- final Property[] properties,
- @PluginConfiguration final Configuration config,
- final Filter filter) {
- // @formatter:on
- if (loggerName == null) {
- LOGGER.error("Loggers cannot be configured without a name");
- return null;
- }
-
- final List<AppenderRef> appenderRefs = Arrays.asList(refs);
- final String name = loggerName.equals(ROOT) ? Strings.EMPTY : loggerName;
- final boolean additive = Booleans.parseBoolean(additivity, true);
-
- return new LoggerConfig(name, appenderRefs, filter, level, additive, properties, config,
- includeLocation(includeLocation, config));
- }
-
- /**
- * 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.
@@ -525,14 +531,14 @@
@PluginFactory
public static LoggerConfig createLogger(
// @formatter:off
- @PluginAttribute(value = "additivity", defaultBoolean = true) final boolean additivity,
- @PluginAttribute("level") final Level level,
+ @PluginAttribute(defaultBoolean = true) final boolean additivity,
+ @PluginAttribute 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,
+ @PluginAttribute final String includeLocation,
+ @PluginElement final AppenderRef[] refs,
+ @PluginElement final Property[] properties,
@PluginConfiguration final Configuration config,
- @PluginElement("Filter") final Filter filter
+ @PluginElement final Filter filter
// @formatter:on
) {
final String name = loggerName.equals(ROOT) ? Strings.EMPTY : loggerName;
@@ -540,14 +546,6 @@
includeLocation(includeLocation, config));
}
- /**
- * @deprecated Please use {@link #includeLocation(String, Configuration)}
- */
- @Deprecated
- 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, final Configuration configuration) {
@@ -578,13 +576,13 @@
@PluginFactory
public static LoggerConfig createLogger(
// @formatter:off
- @PluginAttribute("additivity") final String additivity,
- @PluginAttribute("level") final Level level,
- @PluginAttribute("includeLocation") final String includeLocation,
- @PluginElement("AppenderRef") final AppenderRef[] refs,
- @PluginElement("Properties") final Property[] properties,
+ @PluginAttribute final String additivity,
+ @PluginAttribute final Level level,
+ @PluginAttribute final String includeLocation,
+ @PluginElement final AppenderRef[] refs,
+ @PluginElement final Property[] properties,
@PluginConfiguration final Configuration config,
- @PluginElement("Filter") final Filter filter) {
+ @PluginElement final Filter filter) {
// @formatter:on
final List<AppenderRef> appenderRefs = Arrays.asList(refs);
final Level actualLevel = level == null ? Level.ERROR : level;
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 a9fb94c..92c9040 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
@@ -19,9 +19,10 @@
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;
+import org.apache.logging.log4j.plugins.Node;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginElement;
+import org.apache.logging.log4j.plugins.PluginFactory;
/**
* Container of Logger objects.
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/Node.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/Node.java
deleted file mode 100644
index c92c904..0000000
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/Node.java
+++ /dev/null
@@ -1,156 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache license, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the license for the specific language governing permissions and
- * limitations under the license.
- */
-package org.apache.logging.log4j.core.config;
-
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import org.apache.logging.log4j.core.config.plugins.util.PluginType;
-
-/**
- * A Configuration node.
- */
-public class Node {
-
- /**
- * Main plugin category for plugins which are represented as a configuration node. Such plugins tend to be
- * available as XML elements in a configuration file.
- *
- * @since 2.1
- */
- public static final String CATEGORY = "Core";
-
- private final Node parent;
- private final String name;
- private String value;
- private final PluginType<?> type;
- private final Map<String, String> attributes = new HashMap<>();
- private final List<Node> children = new ArrayList<>();
- private Object object;
-
-
- /**
- * Creates a new instance of {@code Node} and initializes it
- * with a name and the corresponding XML element.
- *
- * @param parent the node's parent.
- * @param name the node's name.
- * @param type The Plugin Type associated with the node.
- */
- public Node(final Node parent, final String name, final PluginType<?> type) {
- this.parent = parent;
- this.name = name;
- this.type = type;
- }
-
- public Node() {
- this.parent = null;
- this.name = null;
- this.type = null;
- }
-
- public Node(final Node node) {
- this.parent = node.parent;
- this.name = node.name;
- this.type = node.type;
- this.attributes.putAll(node.getAttributes());
- this.value = node.getValue();
- for (final Node child : node.getChildren()) {
- this.children.add(new Node(child));
- }
- this.object = node.object;
- }
-
- public Map<String, String> getAttributes() {
- return attributes;
- }
-
- public List<Node> getChildren() {
- return children;
- }
-
- public boolean hasChildren() {
- return !children.isEmpty();
- }
-
- public String getValue() {
- return value;
- }
-
- public void setValue(final String value) {
- this.value = value;
- }
-
- public Node getParent() {
- return parent;
- }
-
- public String getName() {
- return name;
- }
-
- public boolean isRoot() {
- return parent == null;
- }
-
- public void setObject(final Object obj) {
- object = obj;
- }
-
- @SuppressWarnings("unchecked")
- public <T> T getObject() {
- return (T) object;
- }
-
- /**
- * Returns this node's object cast to the given class.
- *
- * @param clazz the class to cast this node's object to.
- * @param <T> the type to cast to.
- * @return this node's object.
- * @since 2.1
- */
- public <T> T getObject(final Class<T> clazz) {
- return clazz.cast(object);
- }
-
- /**
- * Determines if this node's object is an instance of the given class.
- *
- * @param clazz the class to check.
- * @return {@code true} if this node's object is an instance of the given class.
- * @since 2.1
- */
- public boolean isInstanceOf(final Class<?> clazz) {
- return clazz.isInstance(object);
- }
-
- public PluginType<?> getType() {
- return type;
- }
-
- @Override
- public String toString() {
- if (object == null) {
- return "null";
- }
- return type.isObjectPrintable() ? object.toString() :
- type.getPluginClass().getName() + " with name " + name;
- }
-}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/PropertiesPlugin.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/PropertiesPlugin.java
index f0ed358..9ead135 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
@@ -19,13 +19,15 @@
import java.util.HashMap;
import java.util.Map;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
+
import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
-import org.apache.logging.log4j.core.config.plugins.PluginElement;
-import org.apache.logging.log4j.core.config.plugins.PluginFactory;
import org.apache.logging.log4j.core.lookup.Interpolator;
import org.apache.logging.log4j.core.lookup.MapLookup;
import org.apache.logging.log4j.core.lookup.StrLookup;
+import org.apache.logging.log4j.plugins.Node;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginElement;
+import org.apache.logging.log4j.plugins.PluginFactory;
/**
* Handles properties defined in the configuration.
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 09b07a5..5e180f1 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
@@ -18,12 +18,12 @@
import java.util.Objects;
-import org.apache.logging.log4j.Logger;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
-import org.apache.logging.log4j.core.config.plugins.PluginFactory;
-import org.apache.logging.log4j.core.config.plugins.PluginValue;
-import org.apache.logging.log4j.status.StatusLogger;
+import org.apache.logging.log4j.plugins.Node;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginAttribute;
+import org.apache.logging.log4j.plugins.PluginFactory;
+import org.apache.logging.log4j.plugins.PluginValue;
+import org.apache.logging.log4j.plugins.validation.constraints.Required;
import org.apache.logging.log4j.util.Strings;
/**
@@ -32,7 +32,10 @@
@Plugin(name = "property", category = Node.CATEGORY, printObject = true)
public final class Property {
- private static final Logger LOGGER = StatusLogger.getLogger();
+ /**
+ * @since 2.11.2
+ */
+ public static final Property[] EMPTY_ARRAY = new Property[0];
private final String name;
private final String value;
@@ -77,11 +80,8 @@
*/
@PluginFactory
public static Property createProperty(
- @PluginAttribute("name") final String name,
- @PluginValue("value") final String value) {
- if (name == null) {
- LOGGER.error("Property name cannot be null");
- }
+ @PluginAttribute @Required(message = "Property name cannot be null") final String name,
+ @PluginValue final String value) {
return new Property(name, value);
}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ReliabilityStrategy.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ReliabilityStrategy.java
index f21a92d..6400435 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ReliabilityStrategy.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ReliabilityStrategy.java
@@ -42,6 +42,22 @@
*/
void log(Supplier<LoggerConfig> reconfigured, String loggerName, String fqcn, Marker marker, Level level,
Message data, Throwable t);
+ /**
+ * Logs an event.
+ *
+ * @param reconfigured supplies the next LoggerConfig if the strategy's LoggerConfig is no longer active
+ * @param loggerName The name of the Logger.
+ * @param fqcn The fully qualified class name of the caller.
+ * @param location The location of the caller or null.
+ * @param marker A Marker or null if none is present.
+ * @param level The event Level.
+ * @param data The Message.
+ * @param t A Throwable or null.
+ * @since 3.0
+ */
+ default void log(Supplier<LoggerConfig> reconfigured, String loggerName, String fqcn, StackTraceElement location,
+ Marker marker, Level level, Message data, Throwable t) {
+ }
/**
* Logs an event.
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 1e9fc05..953d96f 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
@@ -17,8 +17,8 @@
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;
/**
@@ -58,7 +58,7 @@
return new LockingReliabilityStrategy(loggerConfig);
}
try {
- final Class<? extends ReliabilityStrategy> cls = LoaderUtil.loadClass(strategy).asSubclass(
+ final Class<? extends ReliabilityStrategy> cls = Loader.loadClass(strategy).asSubclass(
ReliabilityStrategy.class);
return cls.getConstructor(LoggerConfig.class).newInstance(loggerConfig);
} catch (final Exception dynamicFailed) {
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ScriptsPlugin.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/ScriptsPlugin.java
index 4c3ae89..6329f89 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
@@ -17,9 +17,9 @@
package org.apache.logging.log4j.core.config;
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.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginElement;
+import org.apache.logging.log4j.plugins.PluginFactory;
import org.apache.logging.log4j.core.script.AbstractScript;
/**
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 5ad7bab..55090a2 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/ComponentBuilder.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/ComponentBuilder.java
@@ -18,7 +18,7 @@
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.core.config.Configuration;
-import org.apache.logging.log4j.core.util.Builder;
+import org.apache.logging.log4j.plugins.util.Builder;
/**
* Builds arbitrary components and is the base type for the provided components.
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/ConfigurationBuilder.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/ConfigurationBuilder.java
index 1cbf6cc..fa84b43 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/ConfigurationBuilder.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/api/ConfigurationBuilder.java
@@ -25,7 +25,7 @@
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.ConfigurationSource;
-import org.apache.logging.log4j.core.util.Builder;
+import org.apache.logging.log4j.plugins.util.Builder;
/**
* Interface for building logging configurations.
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 ae932ee..7462df6 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
@@ -16,7 +16,6 @@
*/
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;
@@ -25,16 +24,14 @@
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;
+import org.apache.logging.log4j.plugins.Node;
+import org.apache.logging.log4j.plugins.util.PluginManager;
+import org.apache.logging.log4j.plugins.util.PluginType;
+import org.apache.logging.log4j.plugins.util.ResolverUtil;
/**
* This is the general version of the Configuration created by the Builder. It may be extended to
@@ -56,7 +53,7 @@
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().setVerboseClasses(VERBOSE_CLASSES).setStatus(getDefaultStatus());
for (final Component component : rootComponent.getComponents()) {
switch (component.getPluginType()) {
case "Scripts": {
@@ -153,17 +150,7 @@
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/DefaultConfigurationBuilder.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/builder/impl/DefaultConfigurationBuilder.java
index 27d8710..4d703fa 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
@@ -23,6 +23,7 @@
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
+
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamWriter;
@@ -60,12 +61,12 @@
private static final String EOL = System.lineSeparator();
private final Component root = new Component();
- private Component loggers;
- private Component appenders;
- private Component filters;
- private Component properties;
- private Component customLevels;
- private Component scripts;
+ private final Component loggers;
+ private final Component appenders;
+ private final Component filters;
+ private final Component properties;
+ private final Component customLevels;
+ private final Component scripts;
private final Class<T> clazz;
private ConfigurationSource source;
private int monitorInterval;
@@ -170,19 +171,18 @@
}
final Constructor<T> constructor = clazz.getConstructor(LoggerContext.class, ConfigurationSource.class, Component.class);
configuration = constructor.newInstance(loggerContext, source, root);
- configuration.setMonitorInterval(monitorInterval);
configuration.getRootNode().getAttributes().putAll(root.getAttributes());
if (name != null) {
configuration.setName(name);
}
if (level != null) {
- configuration.getStatusConfiguration().withStatus(level);
+ configuration.getStatusConfiguration().setStatus(level);
}
if (verbosity != null) {
- configuration.getStatusConfiguration().withVerbosity(verbosity);
+ configuration.getStatusConfiguration().setVerbosity(verbosity);
}
if (destination != null) {
- configuration.getStatusConfiguration().withDestination(destination);
+ configuration.getStatusConfiguration().setDestination(destination);
}
if (packages != null) {
configuration.setPluginPackages(packages);
@@ -196,6 +196,7 @@
if (advertiser != null) {
configuration.createAdvertiser(advertiser, source);
}
+ configuration.setMonitorInterval(monitorInterval);
} catch (final Exception ex) {
throw new IllegalArgumentException("Invalid Configuration class specified", ex);
}
@@ -382,7 +383,7 @@
@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
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 aee0281..4a6de10 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/composite/CompositeConfiguration.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/composite/CompositeConfiguration.java
@@ -16,7 +16,6 @@
*/
package org.apache.logging.log4j.core.config.composite;
-import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.net.URI;
import java.util.ArrayList;
@@ -29,16 +28,16 @@
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.plugins.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;
+import org.apache.logging.log4j.plugins.util.ResolverUtil;
/**
* A Composite Configuration.
@@ -57,40 +56,40 @@
private MergeStrategy mergeStrategy;
/**
- * Construct the ComponsiteConfiguration.
+ * Construct the CompositeConfiguration.
*
* @param configurations The List of Configurations to merge.
*/
public CompositeConfiguration(final List<? extends AbstractConfiguration> configurations) {
- super(configurations.get(0).getLoggerContext(), ConfigurationSource.NULL_SOURCE);
+ super(configurations.get(0).getLoggerContext(), ConfigurationSource.COMPOSITE_SOURCE);
rootNode = configurations.get(0).getRootNode();
this.configurations = configurations;
final String mergeStrategyClassName = PropertiesUtil.getProperties().getStringProperty(MERGE_STRATEGY_PROPERTY,
DefaultMergeStrategy.class.getName());
try {
- mergeStrategy = LoaderUtil.newInstanceOf(mergeStrategyClassName);
- } catch (ClassNotFoundException | IllegalAccessException | NoSuchMethodException | InvocationTargetException |
+ mergeStrategy = Loader.newInstanceOf(mergeStrategyClassName);
+ } catch (ClassNotFoundException | IllegalAccessException | 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().setVerboseClasses(VERBOSE_CLASSES)
+ .setStatus(getDefaultStatus());
for (final Map.Entry<String, String> entry : rootNode.getAttributes().entrySet()) {
final String key = entry.getKey();
final String value = getStrSubstitutor().replace(entry.getValue());
if ("status".equalsIgnoreCase(key)) {
- statusConfig.withStatus(value.toUpperCase());
+ statusConfig.setStatus(value.toUpperCase());
} else if ("dest".equalsIgnoreCase(key)) {
- statusConfig.withDestination(value);
+ statusConfig.setDestination(value);
} else if ("shutdownHook".equalsIgnoreCase(key)) {
isShutdownHookEnabled = !"disable".equalsIgnoreCase(value);
} else if ("shutdownTimeout".equalsIgnoreCase(key)) {
shutdownTimeoutMillis = Long.parseLong(value);
} else if ("verbose".equalsIgnoreCase(key)) {
- statusConfig.withVerbosity(value);
+ statusConfig.setVerbosity(value);
} else if ("packages".equalsIgnoreCase(key)) {
pluginPackages.addAll(Arrays.asList(value.split(Patterns.COMMA_SEPARATOR)));
} else if ("name".equalsIgnoreCase(key)) {
@@ -106,20 +105,18 @@
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<File, FileWatcher> watchers = targetWatchManager.getWatchers();
- for (final Map.Entry<File, FileWatcher> entry : watchers.entrySet()) {
- if (entry.getValue() instanceof ConfiguratonFileWatcher) {
- watchManager.watchFile(entry.getKey(), fileWatcher);
- }
+ final Map<Source, Watcher> watchers = targetWatchManager.getConfigurationWatchers();
+ for (final Map.Entry<Source, Watcher> entry : watchers.entrySet()) {
+ watchManager.watch(entry.getKey(), entry.getValue().newWatcher(this, listeners,
+ entry.getValue().getLastModified()));
}
}
for (final AbstractConfiguration sourceConfiguration : configurations.subList(1, configurations.size())) {
staffChildConfiguration(sourceConfiguration);
final Node sourceRoot = sourceConfiguration.getRootNode();
- mergeStrategy.mergConfigurations(rootNode, sourceRoot, getPluginManager());
+ mergeStrategy.mergeConfigurations(rootNode, sourceRoot, getPluginManager());
if (LOGGER.isEnabled(Level.ALL)) {
final StringBuilder sb = new StringBuilder();
printNodes("", rootNode, sb);
@@ -132,11 +129,10 @@
watchManager.setIntervalSeconds(monitorInterval);
}
final WatchManager sourceWatchManager = sourceConfiguration.getWatchManager();
- final Map<File, FileWatcher> watchers = sourceWatchManager.getWatchers();
- for (final Map.Entry<File, FileWatcher> entry : watchers.entrySet()) {
- if (entry.getValue() instanceof ConfiguratonFileWatcher) {
- watchManager.watchFile(entry.getKey(), fileWatcher);
- }
+ final Map<Source, Watcher> watchers = sourceWatchManager.getConfigurationWatchers();
+ for (final Map.Entry<Source, Watcher> entry : watchers.entrySet()) {
+ watchManager.watch(entry.getKey(), entry.getValue().newWatcher(this, listeners,
+ entry.getValue().getLastModified()));
}
}
}
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 7b9a21d..343d315 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/composite/DefaultMergeStrategy.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/composite/DefaultMergeStrategy.java
@@ -24,10 +24,10 @@
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.core.Filter;
import org.apache.logging.log4j.core.config.AbstractConfiguration;
-import org.apache.logging.log4j.core.config.Node;
-import org.apache.logging.log4j.core.config.plugins.util.PluginManager;
-import org.apache.logging.log4j.core.config.plugins.util.PluginType;
import org.apache.logging.log4j.core.filter.CompositeFilter;
+import org.apache.logging.log4j.plugins.Node;
+import org.apache.logging.log4j.plugins.util.PluginManager;
+import org.apache.logging.log4j.plugins.util.PluginType;
/**
* The default merge strategy for composite configurations.
@@ -41,7 +41,7 @@
* configurations.</li>
* <li>Filters are aggregated under a CompositeFilter if more than one Filter is defined. Since Filters are not named
* duplicates may be present.</li>
- * <li>Scripts and ScriptFile references are aggregated. Duplicate definiations replace those in previous
+ * <li>Scripts and ScriptFile references are aggregated. Duplicate definitions replace those in previous
* configurations.</li>
* <li>Appenders are aggregated. Appenders with the same name are replaced by those in later configurations, including
* all of the Appender's subcomponents.</li>
@@ -113,7 +113,7 @@
* @param pluginManager The PluginManager.
*/
@Override
- public void mergConfigurations(final Node target, final Node source, final PluginManager pluginManager) {
+ public void mergeConfigurations(final Node target, final Node source, final PluginManager pluginManager) {
for (final Node sourceChildNode : source.getChildren()) {
final boolean isFilter = isFilterNode(sourceChildNode);
boolean isMerged = false;
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 a01f6a3..415c1de 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/composite/MergeStrategy.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/composite/MergeStrategy.java
@@ -17,8 +17,8 @@
package org.apache.logging.log4j.core.config.composite;
import org.apache.logging.log4j.core.config.AbstractConfiguration;
-import org.apache.logging.log4j.core.config.Node;
-import org.apache.logging.log4j.core.config.plugins.util.PluginManager;
+import org.apache.logging.log4j.plugins.Node;
+import org.apache.logging.log4j.plugins.util.PluginManager;
/**
* Merges two configurations together
@@ -33,9 +33,9 @@
void mergeRootProperties(Node rootNode, AbstractConfiguration configuration);
/**
- * Merge the soure node tree into the target node tree.
+ * Merge the source node tree into the target node tree.
* @param target The target Node tree.
* @param source The source Node tree.
*/
- void mergConfigurations(Node target, Node source, PluginManager pluginManager);
+ void mergeConfigurations(Node target, Node source, PluginManager pluginManager);
}
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 fd920fa..0f289b6 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/composite/package-info.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/composite/package-info.java
@@ -18,4 +18,4 @@
/**
* Support for composite configurations.
*/
-package org.apache.logging.log4j.core.config.composite;
\ No newline at end of file
+package org.apache.logging.log4j.core.config.composite;
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/json/JsonConfiguration.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/json/JsonConfiguration.java
index 5b38dfb..ef32a6c 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
@@ -33,15 +33,13 @@
import org.apache.logging.log4j.core.config.AbstractConfiguration;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.ConfigurationSource;
-import org.apache.logging.log4j.core.config.ConfiguratonFileWatcher;
import org.apache.logging.log4j.core.config.LoggerConfig;
-import org.apache.logging.log4j.core.config.Node;
import org.apache.logging.log4j.core.config.Reconfigurable;
-import org.apache.logging.log4j.core.config.plugins.util.PluginType;
-import org.apache.logging.log4j.core.config.plugins.util.ResolverUtil;
import org.apache.logging.log4j.core.config.status.StatusConfiguration;
-import org.apache.logging.log4j.core.util.FileWatcher;
import org.apache.logging.log4j.core.util.Patterns;
+import org.apache.logging.log4j.plugins.Node;
+import org.apache.logging.log4j.plugins.util.PluginType;
+import org.apache.logging.log4j.plugins.util.ResolverUtil;
/**
* Creates a Node hierarchy from a JSON file.
@@ -68,39 +66,34 @@
}
}
processAttributes(rootNode, root);
- final StatusConfiguration statusConfig = new StatusConfiguration().withVerboseClasses(VERBOSE_CLASSES)
- .withStatus(getDefaultStatus());
+ final StatusConfiguration statusConfig = new StatusConfiguration().setVerboseClasses(VERBOSE_CLASSES)
+ .setStatus(getDefaultStatus());
+ int monitorIntervalSeconds = 0;
for (final Map.Entry<String, String> entry : rootNode.getAttributes().entrySet()) {
final String key = entry.getKey();
final String value = getStrSubstitutor().replace(entry.getValue());
// TODO: this duplicates a lot of the XmlConfiguration constructor
if ("status".equalsIgnoreCase(key)) {
- statusConfig.withStatus(value);
+ statusConfig.setStatus(value);
} else if ("dest".equalsIgnoreCase(key)) {
- statusConfig.withDestination(value);
+ statusConfig.setDestination(value);
} else if ("shutdownHook".equalsIgnoreCase(key)) {
isShutdownHookEnabled = !"disable".equalsIgnoreCase(value);
} else if ("shutdownTimeout".equalsIgnoreCase(key)) {
shutdownTimeoutMillis = Long.parseLong(value);
} else if ("verbose".equalsIgnoreCase(entry.getKey())) {
- statusConfig.withVerbosity(value);
+ statusConfig.setVerbosity(value);
} else if ("packages".equalsIgnoreCase(key)) {
pluginPackages.addAll(Arrays.asList(value.split(Patterns.COMMA_SEPARATOR)));
} else if ("name".equalsIgnoreCase(key)) {
setName(value);
} else if ("monitorInterval".equalsIgnoreCase(key)) {
- final int intervalSeconds = Integer.parseInt(value);
- if (intervalSeconds > 0) {
- getWatchManager().setIntervalSeconds(intervalSeconds);
- if (configFile != null) {
- final FileWatcher watcher = new ConfiguratonFileWatcher(this, listeners);
- getWatchManager().watchFile(configFile, watcher);
- }
- }
+ monitorIntervalSeconds = Integer.parseInt(value);
} else if ("advertiser".equalsIgnoreCase(key)) {
createAdvertiser(value, configSource, buffer, "application/json");
}
}
+ initializeWatchers(this, configSource, monitorIntervalSeconds);
statusConfig.initialize();
if (getName() == null) {
setName(configSource.getLocation());
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 20c957c..8b4d2e6 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
@@ -21,7 +21,7 @@
import org.apache.logging.log4j.core.config.ConfigurationFactory;
import org.apache.logging.log4j.core.config.ConfigurationSource;
import org.apache.logging.log4j.core.config.Order;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.plugins.Plugin;
import org.apache.logging.log4j.core.util.Loader;
@Plugin(name = "JsonConfigurationFactory", category = ConfigurationFactory.CATEGORY)
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/Plugin.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/Plugin.java
deleted file mode 100644
index 8aaf117..0000000
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/Plugin.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache license, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the license for the specific language governing permissions and
- * limitations under the license.
- */
-package org.apache.logging.log4j.core.config.plugins;
-
-import java.lang.annotation.Documented;
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-import org.apache.logging.log4j.util.Strings;
-
-/**
- * Annotation that identifies a Class as a Plugin.
- */
-@Documented
-@Retention(RetentionPolicy.RUNTIME)
-@Target(ElementType.TYPE)
-public @interface Plugin {
-
- /**
- * Value of the elementType when none is specified.
- */
- String EMPTY = Strings.EMPTY;
-
- /**
- * Name of the plugin. Note that this name is case-insensitive.
- */
- String name();
-
- /**
- * Category to place the plugin under. Category names are case-sensitive.
- */
- String category();
-
- /**
- * Name of the corresponding category of elements this plugin belongs under. For example, {@code appender} would
- * indicate an {@link org.apache.logging.log4j.core.Appender} plugin which would be in the
- * {@code <Appenders/>} element of a {@link org.apache.logging.log4j.core.config.Configuration}.
- */
- String elementType() default EMPTY;
-
- /**
- * Indicates if the plugin class implements a useful {@link Object#toString()} method for use in log messages.
- */
- boolean printObject() default false;
-
- boolean deferChildren() default false;
-}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginAliases.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginAliases.java
index 7be3dea..2b06df6 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginAliases.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginAliases.java
@@ -16,14 +16,11 @@
*/
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 java.lang.annotation.*;
/**
- * Identifies a list of aliases for a {@link Plugin}, {@link PluginAttribute}, or {@link PluginBuilderAttribute}.
+ * Identifies a list of aliases for a Plugin, PluginAttribute, or PluginBuilderAttribute.
+ * @deprecated Exists for compatibility with Log4j 2 2.x plugins. Not used for Log4j 2 3.x plugins.
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
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 bd88220..e75de09 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginAttribute.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginAttribute.java
@@ -16,26 +16,28 @@
*/
package org.apache.logging.log4j.core.config.plugins;
+import org.apache.logging.log4j.core.config.plugins.visitors.PluginAttributeVisitor;
+import org.apache.logging.log4j.plugins.inject.InjectorStrategy;
+import org.apache.logging.log4j.util.Strings;
+
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
-import org.apache.logging.log4j.core.config.plugins.visitors.PluginAttributeVisitor;
-import org.apache.logging.log4j.util.Strings;
-
/**
* Identifies a Plugin Attribute and its default value. Note that only one of the defaultFoo attributes will be
* used based on the type this annotation is attached to. Thus, for primitive types, the default<i>Type</i>
* attribute will be used for some <i>Type</i>. However, for more complex types (including enums), the default
* string value is used instead and should correspond to the string that would correctly convert to the appropriate
* enum value using {@link Enum#valueOf(Class, String) Enum.valueOf}.
+ * @deprecated Exists for compatibility with Log4j 2 2.x plugins. Not used for Log4j 2 3.x plugins.
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.PARAMETER, ElementType.FIELD})
-@PluginVisitorStrategy(PluginAttributeVisitor.class)
+@InjectorStrategy(PluginAttributeVisitor.class)
public @interface PluginAttribute {
/**
@@ -88,7 +90,6 @@
*/
String defaultString() default Strings.EMPTY;
- // TODO: could we allow a blank value and infer the attribute name through reflection?
/**
* Specifies the name of the attribute (case-insensitive) this annotation corresponds to.
*/
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginBuilderAttribute.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginBuilderAttribute.java
index 675d78f..17ad890 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginBuilderAttribute.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginBuilderAttribute.java
@@ -17,22 +17,24 @@
package org.apache.logging.log4j.core.config.plugins;
+import org.apache.logging.log4j.core.config.plugins.visitors.PluginBuilderAttributeVisitor;
+import org.apache.logging.log4j.plugins.inject.InjectorStrategy;
+import org.apache.logging.log4j.util.Strings;
+
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
-import org.apache.logging.log4j.core.config.plugins.visitors.PluginBuilderAttributeVisitor;
-import org.apache.logging.log4j.util.Strings;
-
/**
* Marks a field as a Plugin Attribute.
+ * @deprecated Exists for compatibility with Log4j 2 2.x plugins. Not used for Log4j 2 3.x plugins.
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.PARAMETER, ElementType.FIELD})
-@PluginVisitorStrategy(PluginBuilderAttributeVisitor.class)
+@InjectorStrategy(PluginBuilderAttributeVisitor.class)
public @interface PluginBuilderAttribute {
/**
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginBuilderFactory.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginBuilderFactory.java
index 4e69262..0ea7221 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginBuilderFactory.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginBuilderFactory.java
@@ -17,14 +17,11 @@
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 java.lang.annotation.*;
/**
* Marks a method as a factory for custom Plugin builders.
+ * @deprecated Exists for compatibility with Log4j 2 2.x plugins. Not used for Log4j 2 3.x plugins.
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
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 ac7eb74..dee6f67 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginConfiguration.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginConfiguration.java
@@ -16,22 +16,24 @@
*/
package org.apache.logging.log4j.core.config.plugins;
+import org.apache.logging.log4j.core.config.plugins.inject.PluginConfigurationInjector;
+import org.apache.logging.log4j.plugins.inject.InjectorStrategy;
+
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
-import org.apache.logging.log4j.core.config.plugins.visitors.PluginConfigurationVisitor;
-
/**
- * Identifies a parameter or field as a Configuration.
- * @see org.apache.logging.log4j.core.config.Configuration
+ * Identifies the current {@link org.apache.logging.log4j.core.config.Configuration}. This can be injected as a
+ * parameter to a static {@linkplain org.apache.logging.log4j.plugins.PluginFactory factory method}, or as a field
+ * or single-parameter method in a plugin {@linkplain org.apache.logging.log4j.plugins.util.Builder builder class}.
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
-@Target({ElementType.PARAMETER, ElementType.FIELD})
-@PluginVisitorStrategy(PluginConfigurationVisitor.class)
+@Target({ElementType.PARAMETER, ElementType.FIELD, ElementType.METHOD})
+@InjectorStrategy(PluginConfigurationInjector.class)
public @interface PluginConfiguration {
// empty
}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginElement.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginElement.java
index 7ea358b..5c4f41e 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginElement.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginElement.java
@@ -16,21 +16,23 @@
*/
package org.apache.logging.log4j.core.config.plugins;
+import org.apache.logging.log4j.core.config.plugins.visitors.PluginElementVisitor;
+import org.apache.logging.log4j.plugins.inject.InjectorStrategy;
+
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
-import org.apache.logging.log4j.core.config.plugins.visitors.PluginElementVisitor;
-
/**
* Identifies a parameter as a Plugin and corresponds with an XML element (or equivalent) in configuration files.
+ * @deprecated Exists for compatibility with Log4j 2 2.x plugins. Not used for Log4j 2 3.x plugins.
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.PARAMETER, ElementType.FIELD})
-@PluginVisitorStrategy(PluginElementVisitor.class)
+@InjectorStrategy(PluginElementVisitor.class)
public @interface PluginElement {
/**
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginFactory.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginFactory.java
index 1c04106..2e25631 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginFactory.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginFactory.java
@@ -16,11 +16,7 @@
*/
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 java.lang.annotation.*;
/**
* Identifies a Method as the factory to create the plugin. This annotation should only be used on a {@code static}
@@ -28,6 +24,7 @@
* <p>
* There can only be one factory method per class.
* </p>
+ * @deprecated Exists for compatibility with Log4j 2 2.x plugins. Not used for Log4j 2 3.x plugins.
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
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 d60f1b5..14a136c 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginNode.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginNode.java
@@ -16,21 +16,23 @@
*/
package org.apache.logging.log4j.core.config.plugins;
+import org.apache.logging.log4j.core.config.plugins.visitors.PluginNodeVisitor;
+import org.apache.logging.log4j.plugins.inject.InjectorStrategy;
+
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
-import org.apache.logging.log4j.core.config.plugins.visitors.PluginNodeVisitor;
-
/**
* Identifies a Plugin configuration Node.
+ * @deprecated Exists for compatibility with Log4j 2 2.x plugins. Not used for Log4j 2 3.x plugins.
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.PARAMETER, ElementType.FIELD})
-@PluginVisitorStrategy(PluginNodeVisitor.class)
+@InjectorStrategy(PluginNodeVisitor.class)
public @interface PluginNode {
// empty
}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginValue.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginValue.java
index 9c20cc2..9389aa9 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginValue.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginValue.java
@@ -16,24 +16,26 @@
*/
package org.apache.logging.log4j.core.config.plugins;
+import org.apache.logging.log4j.core.config.plugins.visitors.PluginValueVisitor;
+import org.apache.logging.log4j.plugins.inject.InjectorStrategy;
+
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
-import org.apache.logging.log4j.core.config.plugins.visitors.PluginValueVisitor;
-
/**
* Identifies a parameter as a value. These correspond with property values generally, but are meant as values to be
* used as a placeholder value somewhere.
*
* @see org.apache.logging.log4j.core.config.PropertiesPlugin
+ * @deprecated Exists for compatibility with Log4j 2 2.x plugins. Not used for Log4j 2 3.x plugins.
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.PARAMETER, ElementType.FIELD})
-@PluginVisitorStrategy(PluginValueVisitor.class)
+@InjectorStrategy(PluginValueVisitor.class)
public @interface PluginValue {
String value();
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginVisitorStrategy.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginVisitorStrategy.java
deleted file mode 100644
index 65fcb76..0000000
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/PluginVisitorStrategy.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache license, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the license for the specific language governing permissions and
- * limitations under the license.
- */
-
-package org.apache.logging.log4j.core.config.plugins;
-
-import java.lang.annotation.Annotation;
-import java.lang.annotation.Documented;
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-import org.apache.logging.log4j.core.config.plugins.visitors.PluginVisitor;
-
-/**
- * Meta-annotation to denote the class name to use that implements
- * {@link org.apache.logging.log4j.core.config.plugins.visitors.PluginVisitor} for the annotated annotation.
- */
-@Documented
-@Retention(RetentionPolicy.RUNTIME)
-@Target(ElementType.ANNOTATION_TYPE)
-public @interface PluginVisitorStrategy {
-
- /**
- * The class to use that implements {@link org.apache.logging.log4j.core.config.plugins.visitors.PluginVisitor}
- * for the given annotation. The generic type in {@code PluginVisitor} should match the annotation this annotation
- * is applied to.
- */
- Class<? extends PluginVisitor<? extends Annotation>> value();
-}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/convert/Base64Converter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/convert/Base64Converter.java
deleted file mode 100644
index f4e421f..0000000
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/convert/Base64Converter.java
+++ /dev/null
@@ -1,79 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache license, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the license for the specific language governing permissions and
- * limitations under the license.
- */
-package org.apache.logging.log4j.core.config.plugins.convert;
-
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-
-import org.apache.logging.log4j.Logger;
-import org.apache.logging.log4j.status.StatusLogger;
-import org.apache.logging.log4j.util.LoaderUtil;
-
-/**
- * @Since 2.9
- */
-public class Base64Converter {
-
- private static final Logger LOGGER = StatusLogger.getLogger();
- private static Method method = null;
- private static Object decoder = null;
-
- static {
- try {
- // Base64 is available in Java 8 and up.
- Class<?> clazz = LoaderUtil.loadClass("java.util.Base64");
- final Method getDecoder = clazz.getMethod("getDecoder", (Class[]) null);
- decoder = getDecoder.invoke(null, (Object[]) null);
- clazz = decoder.getClass();
- method = clazz.getMethod("decode", String.class);
- } catch (final ClassNotFoundException ex) {
-
- } catch (final NoSuchMethodException ex) {
-
- } catch (final IllegalAccessException ex) {
-
- } catch (final InvocationTargetException ex) {
-
- }
- if (method == null) {
- try {
- // DatatypeConverter is not in the default module in Java 9.
- final Class<?> clazz = LoaderUtil.loadClass("javax.xml.bind.DatatypeConverter");
- method = clazz.getMethod("parseBase64Binary", String.class);
- } catch (final ClassNotFoundException ex) {
- LOGGER.error("No Base64 Converter is available");
- } catch (final NoSuchMethodException ex) {
-
- }
- }
- }
-
- public static byte[] parseBase64Binary(final String encoded) {
- if (method == null) {
- LOGGER.error("No base64 converter");
- } else {
- try {
- return (byte[]) method.invoke(decoder, encoded);
- } catch (final IllegalAccessException ex) {
- LOGGER.error("Error decoding string - " + ex.getMessage());
- } catch (final InvocationTargetException ex) {
- LOGGER.error("Error decoding string - " + ex.getMessage());
- }
- }
- return new byte[0];
- }
-}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/convert/CoreTypeConverters.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/convert/CoreTypeConverters.java
new file mode 100644
index 0000000..f83397b
--- /dev/null
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/convert/CoreTypeConverters.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.config.plugins.convert;
+
+import org.apache.logging.log4j.core.appender.rolling.action.Duration;
+import org.apache.logging.log4j.core.util.CronExpression;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.convert.TypeConverter;
+import org.apache.logging.log4j.plugins.convert.TypeConverters;
+
+/**
+ * Core specific type converters.
+ *
+ * @since 2.1 Moved to the {@code convert} package.
+ */
+public final class CoreTypeConverters {
+
+ @Plugin(name = "CronExpression", category = TypeConverters.CATEGORY)
+ public static class CronExpressionConverter implements TypeConverter<CronExpression> {
+ @Override
+ public CronExpression convert(final String s) throws Exception {
+ return new CronExpression(s);
+ }
+ }
+
+ /**
+ * Converts a {@link String} into a {@link Duration}.
+ * @since 2.5
+ */
+ @Plugin(name = "Duration", category = TypeConverters.CATEGORY)
+ public static class DurationConverter implements TypeConverter<Duration> {
+ @Override
+ public Duration convert(final String s) {
+ return Duration.parse(s);
+ }
+ }
+}
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
deleted file mode 100644
index 15a162c..0000000
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/convert/EnumConverter.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.core.config.plugins.convert;
-
-import org.apache.logging.log4j.util.EnglishEnums;
-
-/**
- * Converts a {@link String} into a {@link Enum}. Returns {@code null} for invalid enum names.
- *
- * @param <E> the enum class to parse.
- * @since 2.1 moved from TypeConverters
- */
-public class EnumConverter<E extends Enum<E>> implements TypeConverter<E> {
- private final Class<E> clazz;
-
- public EnumConverter(final Class<E> clazz) {
- this.clazz = clazz;
- }
-
- @Override
- public E convert(final String s) {
- return EnglishEnums.valueOf(clazz, s);
- }
-}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/convert/HexConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/convert/HexConverter.java
deleted file mode 100644
index e629657..0000000
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/convert/HexConverter.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache license, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the license for the specific language governing permissions and
- * limitations under the license.
- */
-package org.apache.logging.log4j.core.config.plugins.convert;
-
-/**
- * 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
- */
-public class HexConverter {
-
- 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));
- }
- 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
deleted file mode 100644
index e67e213..0000000
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/convert/TypeConverter.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache license, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the license for the specific language governing permissions and
- * limitations under the license.
- */
-
-package org.apache.logging.log4j.core.config.plugins.convert;
-
-/**
- * Interface for doing automatic String conversion to a specific type.
- *
- * @param <T> Converts Strings into the given type {@code T}.
- * @since 2.1 Moved to the {@code convert} package.
- */
-public interface TypeConverter<T> {
-
- /**
- * Converts a String to a given type.
- *
- * @param s the String to convert. Cannot be {@code null}.
- * @return the converted object.
- * @throws Exception thrown when a conversion error occurs
- */
- T convert(String s) throws Exception;
-}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/convert/TypeConverterRegistry.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/convert/TypeConverterRegistry.java
deleted file mode 100644
index 5088f15..0000000
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/convert/TypeConverterRegistry.java
+++ /dev/null
@@ -1,158 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache license, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the license for the specific language governing permissions and
- * limitations under the license.
- */
-package org.apache.logging.log4j.core.config.plugins.convert;
-
-import java.lang.reflect.ParameterizedType;
-import java.lang.reflect.Type;
-import java.util.Collection;
-import java.util.Map;
-import java.util.Objects;
-import java.util.UnknownFormatConversionException;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
-
-import org.apache.logging.log4j.Logger;
-import org.apache.logging.log4j.core.config.plugins.util.PluginManager;
-import org.apache.logging.log4j.core.config.plugins.util.PluginType;
-import org.apache.logging.log4j.core.util.ReflectionUtil;
-import org.apache.logging.log4j.core.util.TypeUtil;
-import org.apache.logging.log4j.status.StatusLogger;
-
-/**
- * Registry for {@link TypeConverter} plugins.
- *
- * @since 2.1
- */
-public class TypeConverterRegistry {
-
- private static final Logger LOGGER = StatusLogger.getLogger();
- private static volatile TypeConverterRegistry INSTANCE;
- private static final Object INSTANCE_LOCK = new Object();
-
- private final ConcurrentMap<Type, TypeConverter<?>> registry = new ConcurrentHashMap<>();
-
- /**
- * Gets the singleton instance of the TypeConverterRegistry.
- *
- * @return the singleton instance.
- */
- public static TypeConverterRegistry getInstance() {
- TypeConverterRegistry result = INSTANCE;
- if (result == null) {
- synchronized (INSTANCE_LOCK) {
- result = INSTANCE;
- if (result == null) {
- INSTANCE = result = new TypeConverterRegistry();
- }
- }
- }
- return result;
- }
-
- /**
- * Finds a {@link TypeConverter} for the given {@link Type}, falling back to an assignment-compatible TypeConverter
- * if none exist for the given type. That is, if the given Type does not have a TypeConverter, but another Type
- * which can be assigned to the given Type <em>does</em> have a TypeConverter, then that TypeConverter will be
- * used and registered.
- *
- * @param type the Type to find a TypeConverter for (must not be {@code null}).
- * @return a TypeConverter for the given Type.
- * @throws UnknownFormatConversionException if no TypeConverter can be found for the given type.
- */
- public TypeConverter<?> findCompatibleConverter(final Type type) {
- Objects.requireNonNull(type, "No type was provided");
- final TypeConverter<?> primary = registry.get(type);
- // cached type converters
- if (primary != null) {
- return primary;
- }
- // dynamic enum support
- if (type instanceof Class<?>) {
- final Class<?> clazz = (Class<?>) type;
- if (clazz.isEnum()) {
- @SuppressWarnings({"unchecked","rawtypes"})
- final EnumConverter<? extends Enum> converter = new EnumConverter(clazz.asSubclass(Enum.class));
- registry.putIfAbsent(type, converter);
- return converter;
- }
- }
- // look for compatible converters
- for (final Map.Entry<Type, TypeConverter<?>> entry : registry.entrySet()) {
- final Type key = entry.getKey();
- if (TypeUtil.isAssignable(type, key)) {
- LOGGER.debug("Found compatible TypeConverter<{}> for type [{}].", key, type);
- final TypeConverter<?> value = entry.getValue();
- registry.putIfAbsent(type, value);
- return value;
- }
- }
- throw new UnknownFormatConversionException(type.toString());
- }
-
- private TypeConverterRegistry() {
- LOGGER.trace("TypeConverterRegistry initializing.");
- final PluginManager manager = new PluginManager(TypeConverters.CATEGORY);
- manager.collectPlugins();
- loadKnownTypeConverters(manager.getPlugins().values());
- registerPrimitiveTypes();
- }
-
- private void loadKnownTypeConverters(final Collection<PluginType<?>> knownTypes) {
- for (final PluginType<?> knownType : knownTypes) {
- final Class<?> clazz = knownType.getPluginClass();
- if (TypeConverter.class.isAssignableFrom(clazz)) {
- @SuppressWarnings("rawtypes")
- final Class<? extends TypeConverter> pluginClass = clazz.asSubclass(TypeConverter.class);
- final Type conversionType = getTypeConverterSupportedType(pluginClass);
- final TypeConverter<?> converter = ReflectionUtil.instantiate(pluginClass);
- if (registry.putIfAbsent(conversionType, converter) != null) {
- LOGGER.warn("Found a TypeConverter [{}] for type [{}] that already exists.", converter,
- conversionType);
- }
- }
- }
- }
-
- private static Type getTypeConverterSupportedType(@SuppressWarnings("rawtypes") final Class<? extends TypeConverter> typeConverterClass) {
- for (final Type type : typeConverterClass.getGenericInterfaces()) {
- if (type instanceof ParameterizedType) {
- final ParameterizedType pType = (ParameterizedType) type;
- if (TypeConverter.class.equals(pType.getRawType())) {
- // TypeConverter<T> has only one type argument (T), so return that
- return pType.getActualTypeArguments()[0];
- }
- }
- }
- return Void.TYPE;
- }
-
- private void registerPrimitiveTypes() {
- registerTypeAlias(Boolean.class, Boolean.TYPE);
- registerTypeAlias(Byte.class, Byte.TYPE);
- registerTypeAlias(Character.class, Character.TYPE);
- registerTypeAlias(Double.class, Double.TYPE);
- registerTypeAlias(Float.class, Float.TYPE);
- registerTypeAlias(Integer.class, Integer.TYPE);
- registerTypeAlias(Long.class, Long.TYPE);
- registerTypeAlias(Short.class, Short.TYPE);
- }
-
- private void registerTypeAlias(final Type knownType, final Type aliasType) {
- registry.putIfAbsent(aliasType, registry.get(knownType));
- }
-
-}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/convert/TypeConverters.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/convert/TypeConverters.java
deleted file mode 100644
index dc833f0..0000000
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/convert/TypeConverters.java
+++ /dev/null
@@ -1,445 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache license, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the license for the specific language governing permissions and
- * limitations under the license.
- */
-
-package org.apache.logging.log4j.core.config.plugins.convert;
-
-import java.io.File;
-import java.math.BigDecimal;
-import java.math.BigInteger;
-import java.net.InetAddress;
-import java.net.MalformedURLException;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.net.URL;
-import java.nio.charset.Charset;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.security.Provider;
-import java.security.Security;
-import java.util.UUID;
-import java.util.regex.Pattern;
-
-import org.apache.logging.log4j.Level;
-import org.apache.logging.log4j.Logger;
-import org.apache.logging.log4j.core.appender.rolling.action.Duration;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.util.CronExpression;
-import org.apache.logging.log4j.status.StatusLogger;
-import org.apache.logging.log4j.util.LoaderUtil;
-
-/**
- * Collection of basic TypeConverter implementations. May be used to register additional TypeConverters or find
- * registered TypeConverters.
- *
- * @since 2.1 Moved to the {@code convert} package.
- */
-public final class TypeConverters {
-
- /**
- * The {@link Plugin#category() Plugin Category} to use for {@link TypeConverter} plugins.
- *
- * @since 2.1
- */
- public static final String CATEGORY = "TypeConverter";
-
- /**
- * Parses a {@link String} into a {@link BigDecimal}.
- */
- @Plugin(name = "BigDecimal", category = CATEGORY)
- public static class BigDecimalConverter implements TypeConverter<BigDecimal> {
- @Override
- public BigDecimal convert(final String s) {
- return new BigDecimal(s);
- }
- }
-
- /**
- * Parses a {@link String} into a {@link BigInteger}.
- */
- @Plugin(name = "BigInteger", category = CATEGORY)
- public static class BigIntegerConverter implements TypeConverter<BigInteger> {
- @Override
- public BigInteger convert(final String s) {
- return new BigInteger(s);
- }
- }
-
- /**
- * Converts a {@link String} into a {@link Boolean}.
- */
- @Plugin(name = "Boolean", category = CATEGORY)
- public static class BooleanConverter implements TypeConverter<Boolean> {
- @Override
- public Boolean convert(final String s) {
- return Boolean.valueOf(s);
- }
- }
-
- /**
- * Converts a {@link String} into a {@code byte[]}.
- *
- * The supported formats are:
- * <ul>
- * <li>0x0123456789ABCDEF</li>
- * <li>Base64:ABase64String</li>
- * <li>String using {@link Charset#defaultCharset()} [TODO Should this be UTF-8 instead?]</li>
- * </ul>
- */
- @Plugin(name = "ByteArray", category = CATEGORY)
- public static class ByteArrayConverter implements TypeConverter<byte[]> {
-
- private static final String PREFIX_0x = "0x";
- private static final String PREFIX_BASE64 = "Base64:";
-
- @Override
- public byte[] convert(final String value) {
- byte[] bytes;
- if (value == null || value.isEmpty()) {
- bytes = new byte[0];
- } else if (value.startsWith(PREFIX_BASE64)) {
- final String lexicalXSDBase64Binary = value.substring(PREFIX_BASE64.length());
- bytes = Base64Converter.parseBase64Binary(lexicalXSDBase64Binary);
- } else if (value.startsWith(PREFIX_0x)) {
- final String lexicalXSDHexBinary = value.substring(PREFIX_0x.length());
- bytes = HexConverter.parseHexBinary(lexicalXSDHexBinary);
- } else {
- bytes = value.getBytes(Charset.defaultCharset());
- }
- return bytes;
- }
- }
-
- /**
- * Converts a {@link String} into a {@link Byte}.
- */
- @Plugin(name = "Byte", category = CATEGORY)
- public static class ByteConverter implements TypeConverter<Byte> {
- @Override
- public Byte convert(final String s) {
- return Byte.valueOf(s);
- }
- }
-
- /**
- * Converts a {@link String} into a {@link Character}.
- */
- @Plugin(name = "Character", category = CATEGORY)
- public static class CharacterConverter implements TypeConverter<Character> {
- @Override
- public Character convert(final String s) {
- if (s.length() != 1) {
- throw new IllegalArgumentException("Character string must be of length 1: " + s);
- }
- return Character.valueOf(s.toCharArray()[0]);
- }
- }
-
- /**
- * Converts a {@link String} into a {@code char[]}.
- */
- @Plugin(name = "CharacterArray", category = CATEGORY)
- public static class CharArrayConverter implements TypeConverter<char[]> {
- @Override
- public char[] convert(final String s) {
- return s.toCharArray();
- }
- }
-
- /**
- * Converts a {@link String} into a {@link Charset}.
- */
- @Plugin(name = "Charset", category = CATEGORY)
- public static class CharsetConverter implements TypeConverter<Charset> {
- @Override
- public Charset convert(final String s) {
- return Charset.forName(s);
- }
- }
-
- /**
- * Converts a {@link String} into a {@link Class}.
- */
- @Plugin(name = "Class", category = CATEGORY)
- public static class ClassConverter implements TypeConverter<Class<?>> {
- @Override
- public Class<?> convert(final String s) throws ClassNotFoundException {
- switch (s.toLowerCase()) {
- case "boolean":
- return boolean.class;
- case "byte":
- return byte.class;
- case "char":
- return char.class;
- case "double":
- return double.class;
- case "float":
- return float.class;
- case "int":
- return int.class;
- case "long":
- return long.class;
- case "short":
- return short.class;
- case "void":
- return void.class;
- default:
- return LoaderUtil.loadClass(s);
- }
-
- }
- }
-
- @Plugin(name = "CronExpression", category = CATEGORY)
- public static class CronExpressionConverter implements TypeConverter<CronExpression> {
- @Override
- public CronExpression convert(final String s) throws Exception {
- return new CronExpression(s);
- }
- }
-
- /**
- * Converts a {@link String} into a {@link Double}.
- */
- @Plugin(name = "Double", category = CATEGORY)
- public static class DoubleConverter implements TypeConverter<Double> {
- @Override
- public Double convert(final String s) {
- return Double.valueOf(s);
- }
- }
-
- /**
- * Converts a {@link String} into a {@link Duration}.
- * @since 2.5
- */
- @Plugin(name = "Duration", category = CATEGORY)
- public static class DurationConverter implements TypeConverter<Duration> {
- @Override
- public Duration convert(final String s) {
- return Duration.parse(s);
- }
- }
-
- /**
- * Converts a {@link String} into a {@link File}.
- */
- @Plugin(name = "File", category = CATEGORY)
- public static class FileConverter implements TypeConverter<File> {
- @Override
- public File convert(final String s) {
- return new File(s);
- }
- }
-
- /**
- * Converts a {@link String} into a {@link Float}.
- */
- @Plugin(name = "Float", category = CATEGORY)
- public static class FloatConverter implements TypeConverter<Float> {
- @Override
- public Float convert(final String s) {
- return Float.valueOf(s);
- }
- }
-
- /**
- * Converts a {@link String} into an {@link InetAddress}.
- */
- @Plugin(name = "InetAddress", category = CATEGORY)
- public static class InetAddressConverter implements TypeConverter<InetAddress> {
- @Override
- public InetAddress convert(final String s) throws Exception {
- return InetAddress.getByName(s);
- }
- }
-
- /**
- * Converts a {@link String} into a {@link Integer}.
- */
- @Plugin(name = "Integer", category = CATEGORY)
- public static class IntegerConverter implements TypeConverter<Integer> {
- @Override
- public Integer convert(final String s) {
- return Integer.valueOf(s);
- }
- }
-
- /**
- * Converts a {@link String} into a Log4j {@link Level}. Returns {@code null} for invalid level names.
- */
- @Plugin(name = "Level", category = CATEGORY)
- public static class LevelConverter implements TypeConverter<Level> {
- @Override
- public Level convert(final String s) {
- return Level.valueOf(s);
- }
- }
-
- /**
- * Converts a {@link String} into a {@link Long}.
- */
- @Plugin(name = "Long", category = CATEGORY)
- public static class LongConverter implements TypeConverter<Long> {
- @Override
- public Long convert(final String s) {
- return Long.valueOf(s);
- }
- }
-
- /**
- * Converts a {@link String} into a {@link Path}.
- * @since 2.8
- */
- @Plugin(name = "Path", category = CATEGORY)
- public static class PathConverter implements TypeConverter<Path> {
- @Override
- public Path convert(final String s) throws Exception {
- return Paths.get(s);
- }
- }
-
- /**
- * Converts a {@link String} into a {@link Pattern}.
- */
- @Plugin(name = "Pattern", category = CATEGORY)
- public static class PatternConverter implements TypeConverter<Pattern> {
- @Override
- public Pattern convert(final String s) {
- return Pattern.compile(s);
- }
- }
-
- /**
- * Converts a {@link String} into a {@link Provider}.
- */
- @Plugin(name = "SecurityProvider", category = CATEGORY)
- public static class SecurityProviderConverter implements TypeConverter<Provider> {
- @Override
- public Provider convert(final String s) {
- return Security.getProvider(s);
- }
- }
-
- /**
- * Converts a {@link String} into a {@link Short}.
- */
- @Plugin(name = "Short", category = CATEGORY)
- public static class ShortConverter implements TypeConverter<Short> {
- @Override
- public Short convert(final String s) {
- return Short.valueOf(s);
- }
- }
-
- /**
- * Returns the given {@link String}, no conversion takes place.
- */
- @Plugin(name = "String", category = CATEGORY)
- public static class StringConverter implements TypeConverter<String> {
- @Override
- public String convert(final String s) {
- return s;
- }
- }
-
- /**
- * Converts a {@link String} into a {@link URI}.
- */
- @Plugin(name = "URI", category = CATEGORY)
- public static class UriConverter implements TypeConverter<URI> {
- @Override
- public URI convert(final String s) throws URISyntaxException {
- return new URI(s);
- }
- }
-
- /**
- * Converts a {@link String} into a {@link URL}.
- */
- @Plugin(name = "URL", category = CATEGORY)
- public static class UrlConverter implements TypeConverter<URL> {
- @Override
- public URL convert(final String s) throws MalformedURLException {
- return new URL(s);
- }
- }
-
- /**
- * Converts a {@link String} into a {@link UUID}.
- * @since 2.8
- */
- @Plugin(name = "UUID", category = CATEGORY)
- public static class UuidConverter implements TypeConverter<UUID> {
- @Override
- public UUID convert(final String s) throws Exception {
- return UUID.fromString(s);
- }
- }
-
- /**
- * Converts a String to a given class if a TypeConverter is available for that class. Falls back to the provided
- * default value if the conversion is unsuccessful. However, if the default value is <em>also</em> invalid, then
- * {@code null} is returned (along with a nasty status log message).
- *
- * @param s
- * the string to convert
- * @param clazz
- * the class to try to convert the string to
- * @param defaultValue
- * the fallback object to use if the conversion is unsuccessful
- * @return the converted object which may be {@code null} if the string is invalid for the given type
- * @throws NullPointerException
- * if {@code clazz} is {@code null}
- * @throws IllegalArgumentException
- * if no TypeConverter exists for the given class
- */
- public static <T> T convert(final String s, final Class<? extends T> clazz, final Object defaultValue) {
- @SuppressWarnings("unchecked")
- final TypeConverter<T> converter = (TypeConverter<T>) TypeConverterRegistry.getInstance().findCompatibleConverter(clazz);
- if (s == null) {
- // don't debug print here, resulting output is hard to understand
- // LOGGER.debug("Null string given to convert. Using default [{}].", defaultValue);
- return parseDefaultValue(converter, defaultValue);
- }
- try {
- return converter.convert(s);
- } catch (final Exception e) {
- LOGGER.warn("Error while converting string [{}] to type [{}]. Using default value [{}].", s, clazz,
- defaultValue, e);
- return parseDefaultValue(converter, defaultValue);
- }
- }
-
- @SuppressWarnings("unchecked")
- private static <T> T parseDefaultValue(final TypeConverter<T> converter, final Object defaultValue) {
- if (defaultValue == null) {
- return null;
- }
- if (!(defaultValue instanceof String)) {
- return (T) defaultValue;
- }
- try {
- return converter.convert((String) defaultValue);
- } catch (final Exception e) {
- LOGGER.debug("Can't parse default value [{}] for type [{}].", defaultValue, converter.getClass(), e);
- return null;
- }
- }
-
- private static final Logger LOGGER = StatusLogger.getLogger();
-
-}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/inject/PluginConfigurationInjector.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/inject/PluginConfigurationInjector.java
new file mode 100644
index 0000000..f7bbf34
--- /dev/null
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/inject/PluginConfigurationInjector.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.config.plugins.inject;
+
+import org.apache.logging.log4j.core.config.Configuration;
+import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
+import org.apache.logging.log4j.core.util.TypeUtil;
+import org.apache.logging.log4j.plugins.inject.AbstractConfigurationInjector;
+
+public class PluginConfigurationInjector extends AbstractConfigurationInjector<PluginConfiguration, Configuration> {
+ @Override
+ public void inject(final Object factory) {
+ if (TypeUtil.isAssignable(conversionType, configuration.getClass())) {
+ debugLog.append("Configuration");
+ if (configuration.getName() != null) {
+ debugLog.append('(').append(configuration.getName()).append(')');
+ }
+ configurationBinder.bindObject(factory, configuration);
+ } else {
+ LOGGER.warn("Element with type {} annotated with @PluginConfiguration is not compatible with type {}.",
+ conversionType, configuration.getClass());
+ }
+ }
+}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/package-info.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/package-info.java
index 5e74915..0224a3e 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/package-info.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/package-info.java
@@ -18,4 +18,4 @@
/**
* Annotations for Log4j 2 plugins.
*/
-package org.apache.logging.log4j.core.config.plugins;
\ No newline at end of file
+package org.apache.logging.log4j.core.config.plugins;
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/processor/PluginCache.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/processor/PluginCache.java
deleted file mode 100644
index 2fd4160..0000000
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/processor/PluginCache.java
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache license, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the license for the specific language governing permissions and
- * limitations under the license.
- */
-
-package org.apache.logging.log4j.core.config.plugins.processor;
-
-import java.io.BufferedInputStream;
-import java.io.BufferedOutputStream;
-import java.io.DataInputStream;
-import java.io.DataOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.net.URL;
-import java.util.Enumeration;
-import java.util.LinkedHashMap;
-import java.util.Map;
-
-/**
- *
- */
-public class PluginCache {
- private final Map<String, Map<String, PluginEntry>> categories =
- new LinkedHashMap<>();
-
- /**
- * Returns all categories of plugins in this cache.
- *
- * @return all categories of plugins in this cache.
- * @since 2.1
- */
- public Map<String, Map<String, PluginEntry>> getAllCategories() {
- return categories;
- }
-
- /**
- * Gets or creates a category of plugins.
- *
- * @param category name of category to look up.
- * @return plugin mapping of names to plugin entries.
- */
- public Map<String, PluginEntry> getCategory(final String category) {
- final String key = category.toLowerCase();
- if (!categories.containsKey(key)) {
- categories.put(key, new LinkedHashMap<String, PluginEntry>());
- }
- return categories.get(key);
- }
-
- /**
- * Stores the plugin cache to a given OutputStream.
- *
- * @param os destination to save cache to.
- * @throws IOException if an I/O exception occurs.
- */
- // NOTE: if this file format is to be changed, the filename should change and this format should still be readable
- public void writeCache(final OutputStream os) throws IOException {
- try (final DataOutputStream out = new DataOutputStream(new BufferedOutputStream(os))) {
- // See PluginManager.readFromCacheFiles for the corresponding decoder. Format may not be changed
- // without breaking existing Log4j2Plugins.dat files.
- out.writeInt(categories.size());
- for (final Map.Entry<String, Map<String, PluginEntry>> category : categories.entrySet()) {
- out.writeUTF(category.getKey());
- final Map<String, PluginEntry> m = category.getValue();
- out.writeInt(m.size());
- for (final Map.Entry<String, PluginEntry> entry : m.entrySet()) {
- final PluginEntry plugin = entry.getValue();
- out.writeUTF(plugin.getKey());
- out.writeUTF(plugin.getClassName());
- out.writeUTF(plugin.getName());
- out.writeBoolean(plugin.isPrintable());
- out.writeBoolean(plugin.isDefer());
- }
- }
- }
- }
-
- /**
- * Loads and merges all the Log4j plugin cache files specified. Usually, this is obtained via a ClassLoader.
- *
- * @param resources URLs to all the desired plugin cache files to load.
- * @throws IOException if an I/O exception occurs.
- */
- public void loadCacheFiles(final Enumeration<URL> resources) throws IOException {
- categories.clear();
- while (resources.hasMoreElements()) {
- final URL url = resources.nextElement();
- try (final DataInputStream in = new DataInputStream(new BufferedInputStream(url.openStream()))) {
- final int count = in.readInt();
- for (int i = 0; i < count; i++) {
- final String category = in.readUTF();
- final Map<String, PluginEntry> m = getCategory(category);
- final int entries = in.readInt();
- for (int j = 0; j < entries; j++) {
- final PluginEntry entry = new PluginEntry();
- entry.setKey(in.readUTF());
- entry.setClassName(in.readUTF());
- entry.setName(in.readUTF());
- entry.setPrintable(in.readBoolean());
- entry.setDefer(in.readBoolean());
- entry.setCategory(category);
- if (!m.containsKey(entry.getKey())) {
- m.put(entry.getKey(), entry);
- }
- }
- }
- }
- }
- }
-
- /**
- * Gets the number of plugin categories registered.
- *
- * @return number of plugin categories in cache.
- */
- public int size() {
- return categories.size();
- }
-}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/processor/PluginEntry.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/processor/PluginEntry.java
deleted file mode 100644
index dd43601..0000000
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/processor/PluginEntry.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache license, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the license for the specific language governing permissions and
- * limitations under the license.
- */
-
-package org.apache.logging.log4j.core.config.plugins.processor;
-
-import java.io.Serializable;
-
-/**
- * Memento object for storing a plugin entry to a cache file.
- */
-public class PluginEntry implements Serializable {
- private static final long serialVersionUID = 1L;
-
- private String key;
- private String className;
- private String name;
- private boolean printable;
- private boolean defer;
- private transient String category;
-
- public String getKey() {
- return key;
- }
-
- public void setKey(final String key) {
- this.key = key;
- }
-
- public String getClassName() {
- return className;
- }
-
- public void setClassName(final String className) {
- this.className = className;
- }
-
- public String getName() {
- return name;
- }
-
- public void setName(final String name) {
- this.name = name;
- }
-
- public boolean isPrintable() {
- return printable;
- }
-
- public void setPrintable(final boolean printable) {
- this.printable = printable;
- }
-
- public boolean isDefer() {
- return defer;
- }
-
- public void setDefer(final boolean defer) {
- this.defer = defer;
- }
-
- public String getCategory() {
- return category;
- }
-
- public void setCategory(final String category) {
- this.category = category;
- }
-
- @Override
- public String toString() {
- return "PluginEntry [key=" + key + ", className=" + className + ", name=" + name + ", printable=" + printable
- + ", defer=" + defer + ", category=" + category + "]";
- }
-}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/processor/PluginProcessor.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/processor/PluginProcessor.java
deleted file mode 100644
index 2f3b53f..0000000
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/processor/PluginProcessor.java
+++ /dev/null
@@ -1,181 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache license, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the license for the specific language governing permissions and
- * limitations under the license.
- */
-
-package org.apache.logging.log4j.core.config.plugins.processor;
-
-import java.io.IOException;
-import java.io.OutputStream;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.Locale;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Set;
-import javax.annotation.processing.AbstractProcessor;
-import javax.annotation.processing.RoundEnvironment;
-import javax.annotation.processing.SupportedAnnotationTypes;
-import javax.lang.model.SourceVersion;
-import javax.lang.model.element.Element;
-import javax.lang.model.element.ElementVisitor;
-import javax.lang.model.element.TypeElement;
-import javax.lang.model.util.Elements;
-import javax.lang.model.util.SimpleElementVisitor7;
-import javax.tools.Diagnostic.Kind;
-import javax.tools.FileObject;
-import javax.tools.StandardLocation;
-
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginAliases;
-import org.apache.logging.log4j.util.Strings;
-
-/**
- * Annotation processor for pre-scanning Log4j 2 plugins.
- */
-@SupportedAnnotationTypes("org.apache.logging.log4j.core.config.plugins.*")
-public class PluginProcessor extends AbstractProcessor {
-
- // TODO: this could be made more abstract to allow for compile-time and run-time plugin processing
-
- /**
- * The location of the plugin cache data file. This file is written to by this processor, and read from by
- * {@link org.apache.logging.log4j.core.config.plugins.util.PluginManager}.
- */
- public static final String PLUGIN_CACHE_FILE =
- "META-INF/org/apache/logging/log4j/core/config/plugins/Log4j2Plugins.dat";
-
- private final PluginCache pluginCache = new PluginCache();
-
- @Override
- public SourceVersion getSupportedSourceVersion() {
- return SourceVersion.latest();
- }
-
- @Override
- public boolean process(final Set<? extends TypeElement> annotations, final RoundEnvironment roundEnv) {
- System.out.println("Processing annotations");
- try {
- final Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(Plugin.class);
- if (elements.isEmpty()) {
- System.out.println("No elements to process");
- return false;
- }
- collectPlugins(elements);
- writeCacheFile(elements.toArray(new Element[elements.size()]));
- System.out.println("Annotations processed");
- return true;
- } catch (final IOException e) {
- e.printStackTrace();
- error(e.getMessage());
- return false;
- } catch (final Exception ex) {
- ex.printStackTrace();
- error(ex.getMessage());
- return false;
- }
- }
-
- private void error(final CharSequence message) {
- processingEnv.getMessager().printMessage(Kind.ERROR, message);
- }
-
- private void collectPlugins(final Iterable<? extends Element> elements) {
- final Elements elementUtils = processingEnv.getElementUtils();
- final ElementVisitor<PluginEntry, Plugin> pluginVisitor = new PluginElementVisitor(elementUtils);
- final ElementVisitor<Collection<PluginEntry>, Plugin> pluginAliasesVisitor = new PluginAliasesElementVisitor(
- elementUtils);
- for (final Element element : elements) {
- final Plugin plugin = element.getAnnotation(Plugin.class);
- if (plugin == null) {
- continue;
- }
- final PluginEntry entry = element.accept(pluginVisitor, plugin);
- final Map<String, PluginEntry> category = pluginCache.getCategory(entry.getCategory());
- category.put(entry.getKey(), entry);
- final Collection<PluginEntry> entries = element.accept(pluginAliasesVisitor, plugin);
- for (final PluginEntry pluginEntry : entries) {
- category.put(pluginEntry.getKey(), pluginEntry);
- }
- }
- }
-
- private void writeCacheFile(final Element... elements) throws IOException {
- final FileObject fileObject = processingEnv.getFiler().createResource(StandardLocation.CLASS_OUTPUT, Strings.EMPTY,
- PLUGIN_CACHE_FILE, elements);
- try (final OutputStream out = fileObject.openOutputStream()) {
- pluginCache.writeCache(out);
- }
- }
-
- /**
- * ElementVisitor to scan the Plugin annotation.
- */
- private static class PluginElementVisitor extends SimpleElementVisitor7<PluginEntry, Plugin> {
-
- private final Elements elements;
-
- private PluginElementVisitor(final Elements elements) {
- this.elements = elements;
- }
-
- @Override
- public PluginEntry visitType(final TypeElement e, final Plugin plugin) {
- Objects.requireNonNull(plugin, "Plugin annotation is null.");
- final PluginEntry entry = new PluginEntry();
- entry.setKey(plugin.name().toLowerCase(Locale.US));
- entry.setClassName(elements.getBinaryName(e).toString());
- entry.setName(Plugin.EMPTY.equals(plugin.elementType()) ? plugin.name() : plugin.elementType());
- entry.setPrintable(plugin.printObject());
- entry.setDefer(plugin.deferChildren());
- entry.setCategory(plugin.category());
- return entry;
- }
- }
-
- /**
- * ElementVisitor to scan the PluginAliases annotation.
- */
- private static class PluginAliasesElementVisitor extends SimpleElementVisitor7<Collection<PluginEntry>, Plugin> {
-
- private final Elements elements;
-
- private PluginAliasesElementVisitor(final Elements elements) {
- super(Collections.<PluginEntry> emptyList());
- this.elements = elements;
- }
-
- @Override
- public Collection<PluginEntry> visitType(final TypeElement e, final Plugin plugin) {
- final PluginAliases aliases = e.getAnnotation(PluginAliases.class);
- if (aliases == null) {
- return DEFAULT_VALUE;
- }
- final Collection<PluginEntry> entries = new ArrayList<>(aliases.value().length);
- for (final String alias : aliases.value()) {
- final PluginEntry entry = new PluginEntry();
- entry.setKey(alias.toLowerCase(Locale.US));
- entry.setClassName(elements.getBinaryName(e).toString());
- entry.setName(Plugin.EMPTY.equals(plugin.elementType()) ? alias : plugin.elementType());
- entry.setPrintable(plugin.printObject());
- entry.setDefer(plugin.deferChildren());
- entry.setCategory(plugin.category());
- entries.add(entry);
- }
- return entries;
- }
- }
-}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/processor/package-info.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/processor/package-info.java
deleted file mode 100644
index 4f6ddda..0000000
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/processor/package-info.java
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache license, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the license for the specific language governing permissions and
- * limitations under the license.
- */
-
-/**
- * Java annotation processor for pre-scanning Log4j 2 plugins. This is provided as an alternative to using the
- * executable {@link org.apache.logging.log4j.core.config.plugins.util.PluginManager} class in your build process.
- */
-package org.apache.logging.log4j.core.config.plugins.processor;
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/util/PluginBuilder.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/util/PluginBuilder.java
index 443c612..8ebb37d 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/util/PluginBuilder.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/util/PluginBuilder.java
@@ -17,35 +17,36 @@
package org.apache.logging.log4j.core.config.plugins.util;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.config.Configuration;
+import org.apache.logging.log4j.core.lookup.StrSubstitutor;
+import org.apache.logging.log4j.plugins.Node;
+import org.apache.logging.log4j.plugins.PluginAliases;
+import org.apache.logging.log4j.plugins.PluginFactory;
+import org.apache.logging.log4j.plugins.bind.FactoryMethodBinder;
+import org.apache.logging.log4j.plugins.bind.FieldConfigurationBinder;
+import org.apache.logging.log4j.plugins.bind.MethodConfigurationBinder;
+import org.apache.logging.log4j.plugins.inject.ConfigurationInjector;
+import org.apache.logging.log4j.plugins.util.Builder;
+import org.apache.logging.log4j.plugins.util.PluginType;
+import org.apache.logging.log4j.plugins.util.TypeUtil;
+import org.apache.logging.log4j.status.StatusLogger;
+import org.apache.logging.log4j.util.ReflectionUtil;
+import org.apache.logging.log4j.util.StringBuilders;
+
import java.lang.annotation.Annotation;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
-import java.util.ArrayList;
-import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
-
-import org.apache.logging.log4j.Logger;
-import org.apache.logging.log4j.core.LogEvent;
-import org.apache.logging.log4j.core.config.Configuration;
-import org.apache.logging.log4j.core.config.ConfigurationException;
-import org.apache.logging.log4j.core.config.Node;
-import org.apache.logging.log4j.core.config.plugins.PluginAliases;
-import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
-import org.apache.logging.log4j.core.config.plugins.PluginFactory;
-import org.apache.logging.log4j.core.config.plugins.validation.ConstraintValidator;
-import org.apache.logging.log4j.core.config.plugins.validation.ConstraintValidators;
-import org.apache.logging.log4j.core.config.plugins.visitors.PluginVisitor;
-import org.apache.logging.log4j.core.config.plugins.visitors.PluginVisitors;
-import org.apache.logging.log4j.core.util.Builder;
-import org.apache.logging.log4j.core.util.ReflectionUtil;
-import org.apache.logging.log4j.core.util.TypeUtil;
-import org.apache.logging.log4j.status.StatusLogger;
-import org.apache.logging.log4j.util.StringBuilders;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.function.Function;
/**
* Builder class to instantiate and configure a Plugin object using a PluginFactory method or PluginBuilderFactory
@@ -61,6 +62,8 @@
private Configuration configuration;
private Node node;
private LogEvent event;
+ private Substitutor substitutor;
+ private final ConcurrentMap<String, Boolean> aliases = new ConcurrentHashMap<>();
/**
* Constructs a PluginBuilder for a given PluginType.
@@ -78,7 +81,7 @@
* @param configuration the configuration to use.
* @return {@code this}
*/
- public PluginBuilder withConfiguration(final Configuration configuration) {
+ public PluginBuilder setConfiguration(final Configuration configuration) {
this.configuration = configuration;
return this;
}
@@ -89,7 +92,7 @@
* @param node the plugin configuration node to use.
* @return {@code this}
*/
- public PluginBuilder withConfigurationNode(final Node node) {
+ public PluginBuilder setConfigurationNode(final Node node) {
this.node = node;
return this;
}
@@ -113,32 +116,31 @@
@Override
public Object build() {
verify();
+ LOGGER.debug("Building Plugin[name={}, class={}].", pluginType.getElementName(),
+ pluginType.getPluginClass().getName());
+ substitutor = new Substitutor(event);
// first try to use a builder class if one is available
try {
- LOGGER.debug("Building Plugin[name={}, class={}].", pluginType.getElementName(),
- pluginType.getPluginClass().getName());
final Builder<?> builder = createBuilder(this.clazz);
if (builder != null) {
- injectFields(builder);
- return builder.build();
+ return injectBuilder(builder);
}
- } catch (final ConfigurationException e) { // LOG4J2-1908
- LOGGER.error("Could not create plugin of type {} for element {}", this.clazz, node.getName(), e);
+ } catch (final InvocationTargetException e) {
+ LOGGER.error("Could not create plugin builder for plugin {} and element {}", clazz, node.getName(), e.getCause());
+ return null;
+ } catch (final IllegalAccessException e) {
+ LOGGER.error("Could not access plugin builder for plugin {} and element {}", clazz, node.getName());
+ return null;
+ } catch (final RuntimeException e) { // LOG4J2-1908
+ LOGGER.error("Could not create plugin of type {} for element {}", clazz, node.getName(), e);
return null; // no point in trying the factory method
- } catch (final Exception e) {
- LOGGER.error("Could not create plugin of type {} for element {}: {}",
- this.clazz, node.getName(),
- (e instanceof InvocationTargetException ? ((InvocationTargetException) e).getCause() : e).toString(), e);
}
// or fall back to factory method if no builder class is available
try {
- final Method factory = findFactoryMethod(this.clazz);
- final Object[] params = generateParameters(factory);
- return factory.invoke(null, params);
- } catch (final Exception e) {
- LOGGER.error("Unable to invoke factory method in {} for element {}: {}",
- this.clazz, this.node.getName(),
- (e instanceof InvocationTargetException ? ((InvocationTargetException) e).getCause() : e).toString(), e);
+ return injectFactoryMethod(findFactoryMethod(this.clazz));
+ } catch (final Throwable e) {
+ LOGGER.error("Could not create plugin of type {} for element {}: {}", clazz, node.getName(),
+ e.toString(), e);
return null;
}
}
@@ -151,65 +153,62 @@
private static Builder<?> createBuilder(final Class<?> clazz)
throws InvocationTargetException, IllegalAccessException {
for (final Method method : clazz.getDeclaredMethods()) {
- if (method.isAnnotationPresent(PluginBuilderFactory.class) &&
+ if ((method.isAnnotationPresent(PluginFactory.class)) &&
Modifier.isStatic(method.getModifiers()) &&
TypeUtil.isAssignable(Builder.class, method.getReturnType())) {
ReflectionUtil.makeAccessible(method);
return (Builder<?>) method.invoke(null);
+ } else if (method.isAnnotationPresent(org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory.class) &&
+ Modifier.isStatic(method.getModifiers()) &&
+ TypeUtil.isAssignable(org.apache.logging.log4j.core.util.Builder.class, method.getReturnType())) {
+ ReflectionUtil.makeAccessible(method);
+ return new BuilderWrapper((org.apache.logging.log4j.core.util.Builder<?>) method.invoke(null));
}
}
return null;
}
- private void injectFields(final Builder<?> builder) throws IllegalAccessException {
- final List<Field> fields = TypeUtil.getAllDeclaredFields(builder.getClass());
- AccessibleObject.setAccessible(fields.toArray(new Field[] {}), true);
+ private Object injectBuilder(final Builder<?> builder) {
+ final Object target = builder instanceof BuilderWrapper ? ((BuilderWrapper) builder).getBuilder() : builder;
+ final List<Field> fields = TypeUtil.getAllDeclaredFields(target.getClass());
+ AccessibleObject.setAccessible(fields.toArray(new Field[0]), true);
final StringBuilder log = new StringBuilder();
- boolean invalid = false;
- String reason = "";
+ // TODO: collect OptionBindingExceptions into a composite error message (ConfigurationException?)
for (final Field field : fields) {
- log.append(log.length() == 0 ? simpleName(builder) + "(" : ", ");
- final Annotation[] annotations = field.getDeclaredAnnotations();
- final String[] aliases = extractPluginAliases(annotations);
- for (final Annotation a : annotations) {
- if (a instanceof PluginAliases) {
- continue; // already processed
+ ConfigurationInjector.forAnnotatedElement(field).ifPresent(injector -> {
+ log.append(log.length() == 0 ? simpleName(target) + "(" : ", ");
+ injector.withAliases(extractPluginAliases(field.getAnnotations()))
+ .withConversionType(field.getGenericType())
+ .withConfigurationBinder(new FieldConfigurationBinder(field))
+ .withDebugLog(log)
+ .withStringSubstitutionStrategy(substitutor)
+ .withConfiguration(configuration)
+ .withNode(node)
+ .inject(target);
+ });
+ }
+ // TODO: tests
+ for (final Method method : target.getClass().getMethods()) {
+ ConfigurationInjector.forAnnotatedElement(method).ifPresent(injector -> {
+ if (method.getParameterCount() != 1) {
+ throw new IllegalArgumentException("Cannot inject to a plugin builder method with parameter count other than 1");
}
- final PluginVisitor<? extends Annotation> visitor =
- PluginVisitors.findVisitor(a.annotationType());
- if (visitor != null) {
- final Object value = visitor.setAliases(aliases)
- .setAnnotation(a)
- .setConversionType(field.getType())
- .setStrSubstitutor(configuration.getStrSubstitutor())
- .setMember(field)
- .visit(configuration, node, event, log);
- // don't overwrite default values if the visitor gives us no value to inject
- if (value != null) {
- field.set(builder, value);
- }
- }
- }
- final Collection<ConstraintValidator<?>> validators =
- ConstraintValidators.findValidators(annotations);
- final Object value = field.get(builder);
- for (final ConstraintValidator<?> validator : validators) {
- if (!validator.isValid(field.getName(), value)) {
- invalid = true;
- if (!reason.isEmpty()) {
- reason += ", ";
- }
- reason += "field '" + field.getName() + "' has invalid value '" + value + "'";
- }
- }
+ log.append(log.length() == 0 ? simpleName(target) + "(" : ", ");
+ injector.withAliases(extractPluginAliases(method.getAnnotations()))
+ .withConversionType(method.getGenericParameterTypes()[0])
+ .withConfigurationBinder(new MethodConfigurationBinder(method))
+ .withDebugLog(log)
+ .withStringSubstitutionStrategy(substitutor)
+ .withConfiguration(configuration)
+ .withNode(node)
+ .inject(target);
+ });
}
log.append(log.length() == 0 ? builder.getClass().getSimpleName() + "()" : ")");
LOGGER.debug(log.toString());
- if (invalid) {
- throw new ConfigurationException("Arguments given for element " + node.getName() + " are invalid: " + reason);
- }
checkForRemainingAttributes();
verifyNodeChildrenUsed();
+ return builder.build();
}
/**
@@ -226,7 +225,8 @@
private static Method findFactoryMethod(final Class<?> clazz) {
for (final Method method : clazz.getDeclaredMethods()) {
- if (method.isAnnotationPresent(PluginFactory.class) &&
+ if ((method.isAnnotationPresent(PluginFactory.class) ||
+ method.isAnnotationPresent(org.apache.logging.log4j.core.config.plugins.PluginFactory.class)) &&
Modifier.isStatic(method.getModifiers())) {
ReflectionUtil.makeAccessible(method);
return method;
@@ -235,59 +235,35 @@
throw new IllegalStateException("No factory method found for class " + clazz.getName());
}
- private Object[] generateParameters(final Method factory) {
+ private Object injectFactoryMethod(final Method factory) throws Throwable {
final StringBuilder log = new StringBuilder();
- final Class<?>[] types = factory.getParameterTypes();
- final Annotation[][] annotations = factory.getParameterAnnotations();
- final Object[] args = new Object[annotations.length];
- boolean invalid = false;
- for (int i = 0; i < annotations.length; i++) {
+ final FactoryMethodBinder binder = new FactoryMethodBinder(factory);
+ binder.forEachParameter((parameter, optionBinder) -> {
log.append(log.length() == 0 ? factory.getName() + "(" : ", ");
- final String[] aliases = extractPluginAliases(annotations[i]);
- for (final Annotation a : annotations[i]) {
- if (a instanceof PluginAliases) {
- continue; // already processed
- }
- final PluginVisitor<? extends Annotation> visitor = PluginVisitors.findVisitor(
- a.annotationType());
- if (visitor != null) {
- final Object value = visitor.setAliases(aliases)
- .setAnnotation(a)
- .setConversionType(types[i])
- .setStrSubstitutor(configuration.getStrSubstitutor())
- .setMember(factory)
- .visit(configuration, node, event, log);
- // don't overwrite existing values if the visitor gives us no value to inject
- if (value != null) {
- args[i] = value;
- }
- }
- }
- final Collection<ConstraintValidator<?>> validators =
- ConstraintValidators.findValidators(annotations[i]);
- final Object value = args[i];
- final String argName = "arg[" + i + "](" + simpleName(value) + ")";
- for (final ConstraintValidator<?> validator : validators) {
- if (!validator.isValid(argName, value)) {
- invalid = true;
- }
- }
- }
+ ConfigurationInjector.forAnnotatedElement(parameter).ifPresent(injector -> injector
+ .withAliases(extractPluginAliases(parameter.getAnnotations()))
+ .withConversionType(parameter.getParameterizedType())
+ .withConfigurationBinder(optionBinder)
+ .withDebugLog(log)
+ .withStringSubstitutionStrategy(substitutor)
+ .withConfiguration(configuration)
+ .withNode(node)
+ .inject(binder));
+ });
log.append(log.length() == 0 ? factory.getName() + "()" : ")");
checkForRemainingAttributes();
verifyNodeChildrenUsed();
LOGGER.debug(log.toString());
- if (invalid) {
- throw new ConfigurationException("Arguments given for element " + node.getName() + " are invalid");
- }
- return args;
+ return binder.invoke();
}
private static String[] extractPluginAliases(final Annotation... parmTypes) {
- String[] aliases = null;
+ String[] aliases = {};
for (final Annotation a : parmTypes) {
if (a instanceof PluginAliases) {
aliases = ((PluginAliases) a).value();
+ } else if (a instanceof org.apache.logging.log4j.core.config.plugins.PluginAliases) {
+ aliases = ((org.apache.logging.log4j.core.config.plugins.PluginAliases) a).value();
}
}
return aliases;
@@ -325,4 +301,35 @@
}
}
}
+
+ private class Substitutor implements Function<String, String> {
+ private final LogEvent event;
+ private final StrSubstitutor strSubstitutor;
+
+ Substitutor(LogEvent event) {
+ this.event = event;
+ this.strSubstitutor = configuration.getStrSubstitutor();
+ }
+
+ @Override
+ public String apply(String str) {
+ return strSubstitutor.replace(event, str);
+ }
+ }
+
+ public static class BuilderWrapper<T> implements Builder<T> {
+ private final org.apache.logging.log4j.core.util.Builder<T> builder;
+
+ BuilderWrapper(org.apache.logging.log4j.core.util.Builder<T> builder) {
+ this.builder = builder;
+ }
+
+ public T build() {
+ return builder.build();
+ }
+
+ org.apache.logging.log4j.core.util.Builder<T> getBuilder() {
+ return builder;
+ }
+ }
}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/util/PluginManager.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/util/PluginManager.java
deleted file mode 100644
index 704ffd1..0000000
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/util/PluginManager.java
+++ /dev/null
@@ -1,176 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache license, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the license for the specific language governing permissions and
- * limitations under the license.
- */
-
-package org.apache.logging.log4j.core.config.plugins.util;
-
-import org.apache.logging.log4j.Logger;
-import org.apache.logging.log4j.status.StatusLogger;
-import org.apache.logging.log4j.util.Strings;
-
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.CopyOnWriteArrayList;
-
-/**
- * Loads and manages all the plugins.
- */
-public class PluginManager {
-
- private static final CopyOnWriteArrayList<String> PACKAGES = new CopyOnWriteArrayList<>();
- private static final String LOG4J_PACKAGES = "org.apache.logging.log4j.core";
-
- private static final Logger LOGGER = StatusLogger.getLogger();
-
- private Map<String, PluginType<?>> plugins = new HashMap<>();
- private final String category;
-
- /**
- * Constructs a PluginManager for the plugin category name given.
- *
- * @param category The plugin category name.
- */
- public PluginManager(final String category) {
- this.category = category;
- }
-
- /**
- * Process annotated plugins.
- *
- * @deprecated Use {@link org.apache.logging.log4j.core.config.plugins.processor.PluginProcessor} instead. To do so,
- * simply include {@code log4j-core} in your dependencies and make sure annotation processing is not
- * disabled. By default, supported Java compilers will automatically use that plugin processor provided
- * {@code log4j-core} is on the classpath.
- */
- @Deprecated
- // use PluginProcessor instead
- public static void main(final String[] args) {
- System.err.println("ERROR: this tool is superseded by the annotation processor included in log4j-core.");
- System.err.println("If the annotation processor does not work for you, please see the manual page:");
- System.err.println("http://logging.apache.org/log4j/2.x/manual/configuration.html#ConfigurationSyntax");
- System.exit(-1);
- }
-
- /**
- * Adds a package name to be scanned for plugins. Must be invoked prior to plugins being collected.
- *
- * @param p The package name. Ignored if {@code null} or empty.
- */
- public static void addPackage(final String p) {
- if (Strings.isBlank(p)) {
- return;
- }
- PACKAGES.addIfAbsent(p);
- }
-
- /**
- * Adds a list of package names to be scanned for plugins. Convenience method for {@link #addPackage(String)}.
- *
- * @param packages collection of package names to add. Empty and null package names are ignored.
- */
- public static void addPackages(final Collection<String> packages) {
- for (final String pkg : packages) {
- if (Strings.isNotBlank(pkg)) {
- PACKAGES.addIfAbsent(pkg);
- }
- }
- }
-
- /**
- * Returns the type of a specified plugin.
- *
- * @param name The name of the plugin.
- * @return The plugin's type.
- */
- public PluginType<?> getPluginType(final String name) {
- return plugins.get(name.toLowerCase());
- }
-
- /**
- * Returns all the matching plugins.
- *
- * @return A Map containing the name of the plugin and its type.
- */
- public Map<String, PluginType<?>> getPlugins() {
- return plugins;
- }
-
- /**
- * Locates all the plugins.
- */
- public void collectPlugins() {
- collectPlugins(null);
- }
-
- /**
- * Locates all the plugins including search of specific packages. Warns about name collisions.
- *
- * @param packages the list of packages to scan for plugins
- * @since 2.1
- */
- public void collectPlugins(final List<String> packages) {
- final String categoryLowerCase = category.toLowerCase();
- final Map<String, PluginType<?>> newPlugins = new LinkedHashMap<>();
-
- // First, iterate the Log4j2Plugin.dat files found in the main CLASSPATH
- Map<String, List<PluginType<?>>> builtInPlugins = PluginRegistry.getInstance().loadFromMainClassLoader();
- if (builtInPlugins.isEmpty()) {
- // If we didn't find any plugins above, someone must have messed with the log4j-core.jar.
- // Search the standard package in the hopes we can find our core plugins.
- builtInPlugins = PluginRegistry.getInstance().loadFromPackage(LOG4J_PACKAGES);
- }
- mergeByName(newPlugins, builtInPlugins.get(categoryLowerCase));
-
- // Next, iterate any Log4j2Plugin.dat files from OSGi Bundles
- for (final Map<String, List<PluginType<?>>> pluginsByCategory : PluginRegistry.getInstance().getPluginsByCategoryByBundleId().values()) {
- mergeByName(newPlugins, pluginsByCategory.get(categoryLowerCase));
- }
-
- // Next iterate any packages passed to the static addPackage method.
- for (final String pkg : PACKAGES) {
- mergeByName(newPlugins, PluginRegistry.getInstance().loadFromPackage(pkg).get(categoryLowerCase));
- }
- // Finally iterate any packages provided in the configuration (note these can be changed at runtime).
- if (packages != null) {
- for (final String pkg : packages) {
- mergeByName(newPlugins, PluginRegistry.getInstance().loadFromPackage(pkg).get(categoryLowerCase));
- }
- }
-
- LOGGER.debug("PluginManager '{}' found {} plugins", category, newPlugins.size());
-
- plugins = newPlugins;
- }
-
- private static void mergeByName(final Map<String, PluginType<?>> newPlugins, final List<PluginType<?>> plugins) {
- if (plugins == null) {
- return;
- }
- for (final PluginType<?> pluginType : plugins) {
- final String key = pluginType.getKey();
- final PluginType<?> existing = newPlugins.get(key);
- if (existing == null) {
- newPlugins.put(key, pluginType);
- } else if (!existing.getPluginClass().equals(pluginType.getPluginClass())) {
- LOGGER.warn("Plugin [{}] is already mapped to {}, ignoring {}",
- key, existing.getPluginClass(), pluginType.getPluginClass());
- }
- }
- }
-}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/util/PluginRegistry.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/util/PluginRegistry.java
deleted file mode 100644
index a657f1a..0000000
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/util/PluginRegistry.java
+++ /dev/null
@@ -1,308 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache license, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the license for the specific language governing permissions and
- * limitations under the license.
- */
-
-package org.apache.logging.log4j.core.config.plugins.util;
-
-import java.io.IOException;
-import java.net.URI;
-import java.net.URL;
-import java.text.DecimalFormat;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.Enumeration;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
-import java.util.concurrent.atomic.AtomicReference;
-
-import org.apache.logging.log4j.Logger;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginAliases;
-import org.apache.logging.log4j.core.config.plugins.processor.PluginCache;
-import org.apache.logging.log4j.core.config.plugins.processor.PluginEntry;
-import org.apache.logging.log4j.core.config.plugins.processor.PluginProcessor;
-import org.apache.logging.log4j.core.util.Loader;
-import org.apache.logging.log4j.status.StatusLogger;
-import org.apache.logging.log4j.util.Strings;
-
-/**
- * Registry singleton for PluginType maps partitioned by source type and then by category names.
- */
-public class PluginRegistry {
-
- private static final Logger LOGGER = StatusLogger.getLogger();
-
- private static volatile PluginRegistry INSTANCE;
- private static final Object INSTANCE_LOCK = new Object();
-
- /**
- * Contains plugins found in Log4j2Plugins.dat cache files in the main CLASSPATH.
- */
- private final AtomicReference<Map<String, List<PluginType<?>>>> pluginsByCategoryRef =
- new AtomicReference<>();
-
- /**
- * Contains plugins found in Log4j2Plugins.dat cache files in OSGi Bundles.
- */
- private final ConcurrentMap<Long, Map<String, List<PluginType<?>>>> pluginsByCategoryByBundleId =
- new ConcurrentHashMap<>();
-
- /**
- * Contains plugins found by searching for annotated classes at runtime.
- */
- private final ConcurrentMap<String, Map<String, List<PluginType<?>>>> pluginsByCategoryByPackage =
- new ConcurrentHashMap<>();
-
- private PluginRegistry() {
- }
-
- /**
- * Returns the global PluginRegistry instance.
- *
- * @return the global PluginRegistry instance.
- * @since 2.1
- */
- public static PluginRegistry getInstance() {
- PluginRegistry result = INSTANCE;
- if (result == null) {
- synchronized (INSTANCE_LOCK) {
- result = INSTANCE;
- if (result == null) {
- INSTANCE = result = new PluginRegistry();
- }
- }
- }
- return result;
- }
-
- /**
- * Resets the registry to an empty state.
- */
- public void clear() {
- pluginsByCategoryRef.set(null);
- pluginsByCategoryByPackage.clear();
- pluginsByCategoryByBundleId.clear();
- }
-
- /**
- * @since 2.1
- */
- public Map<Long, Map<String, List<PluginType<?>>>> getPluginsByCategoryByBundleId() {
- return pluginsByCategoryByBundleId;
- }
-
- /**
- * @since 2.1
- */
- public Map<String, List<PluginType<?>>> loadFromMainClassLoader() {
- final Map<String, List<PluginType<?>>> existing = pluginsByCategoryRef.get();
- if (existing != null) {
- // already loaded
- return existing;
- }
- final Map<String, List<PluginType<?>>> newPluginsByCategory = decodeCacheFiles(Loader.getClassLoader());
-
- // Note multiple threads could be calling this method concurrently. Both will do the work,
- // but only one will be allowed to store the result in the AtomicReference.
- // Return the map produced by whichever thread won the race, so all callers will get the same result.
- if (pluginsByCategoryRef.compareAndSet(null, newPluginsByCategory)) {
- return newPluginsByCategory;
- }
- return pluginsByCategoryRef.get();
- }
-
- /**
- * @since 2.1
- */
- public void clearBundlePlugins(final long bundleId) {
- pluginsByCategoryByBundleId.remove(bundleId);
- }
-
- /**
- * @since 2.1
- */
- public Map<String, List<PluginType<?>>> loadFromBundle(final long bundleId, final ClassLoader loader) {
- Map<String, List<PluginType<?>>> existing = pluginsByCategoryByBundleId.get(bundleId);
- if (existing != null) {
- // already loaded from this classloader
- return existing;
- }
- final Map<String, List<PluginType<?>>> newPluginsByCategory = decodeCacheFiles(loader);
-
- // Note multiple threads could be calling this method concurrently. Both will do the work,
- // but only one will be allowed to store the result in the outer map.
- // Return the inner map produced by whichever thread won the race, so all callers will get the same result.
- existing = pluginsByCategoryByBundleId.putIfAbsent(bundleId, newPluginsByCategory);
- if (existing != null) {
- return existing;
- }
- return newPluginsByCategory;
- }
-
- private Map<String, List<PluginType<?>>> decodeCacheFiles(final ClassLoader loader) {
- final long startTime = System.nanoTime();
- final PluginCache cache = new PluginCache();
- try {
- final Enumeration<URL> resources = loader.getResources(PluginProcessor.PLUGIN_CACHE_FILE);
- if (resources == null) {
- LOGGER.info("Plugin preloads not available from class loader {}", loader);
- } else {
- cache.loadCacheFiles(resources);
- }
- } catch (final IOException ioe) {
- LOGGER.warn("Unable to preload plugins", ioe);
- }
- final Map<String, List<PluginType<?>>> newPluginsByCategory = new HashMap<>();
- int pluginCount = 0;
- for (final Map.Entry<String, Map<String, PluginEntry>> outer : cache.getAllCategories().entrySet()) {
- final String categoryLowerCase = outer.getKey();
- final List<PluginType<?>> types = new ArrayList<>(outer.getValue().size());
- newPluginsByCategory.put(categoryLowerCase, types);
- for (final Map.Entry<String, PluginEntry> inner : outer.getValue().entrySet()) {
- final PluginEntry entry = inner.getValue();
- final String className = entry.getClassName();
- try {
- final Class<?> clazz = loader.loadClass(className);
- final PluginType<?> type = new PluginType<>(entry, clazz, entry.getName());
- types.add(type);
- ++pluginCount;
- } catch (final ClassNotFoundException e) {
- LOGGER.info("Plugin [{}] could not be loaded due to missing classes.", className, e);
- } catch (final VerifyError e) {
- LOGGER.info("Plugin [{}] could not be loaded due to verification error.", className, e);
- }
- }
- }
-
- final long endTime = System.nanoTime();
- final DecimalFormat numFormat = new DecimalFormat("#0.000000");
- final double seconds = (endTime - startTime) * 1e-9;
- LOGGER.debug("Took {} seconds to load {} plugins from {}",
- numFormat.format(seconds), pluginCount, loader);
- return newPluginsByCategory;
- }
-
- /**
- * @since 2.1
- */
- public Map<String, List<PluginType<?>>> loadFromPackage(final String pkg) {
- if (Strings.isBlank(pkg)) {
- // happens when splitting an empty string
- return Collections.emptyMap();
- }
- Map<String, List<PluginType<?>>> existing = pluginsByCategoryByPackage.get(pkg);
- if (existing != null) {
- // already loaded this package
- return existing;
- }
-
- final long startTime = System.nanoTime();
- final ResolverUtil resolver = new ResolverUtil();
- final ClassLoader classLoader = Loader.getClassLoader();
- if (classLoader != null) {
- resolver.setClassLoader(classLoader);
- }
- resolver.findInPackage(new PluginTest(), pkg);
-
- final Map<String, List<PluginType<?>>> newPluginsByCategory = new HashMap<>();
- for (final Class<?> clazz : resolver.getClasses()) {
- final Plugin plugin = clazz.getAnnotation(Plugin.class);
- final String categoryLowerCase = plugin.category().toLowerCase();
- List<PluginType<?>> list = newPluginsByCategory.get(categoryLowerCase);
- if (list == null) {
- newPluginsByCategory.put(categoryLowerCase, list = new ArrayList<>());
- }
- final PluginEntry mainEntry = new PluginEntry();
- final String mainElementName = plugin.elementType().equals(
- Plugin.EMPTY) ? plugin.name() : plugin.elementType();
- mainEntry.setKey(plugin.name().toLowerCase());
- mainEntry.setName(plugin.name());
- mainEntry.setCategory(plugin.category());
- mainEntry.setClassName(clazz.getName());
- mainEntry.setPrintable(plugin.printObject());
- mainEntry.setDefer(plugin.deferChildren());
- final PluginType<?> mainType = new PluginType<>(mainEntry, clazz, mainElementName);
- list.add(mainType);
- final PluginAliases pluginAliases = clazz.getAnnotation(PluginAliases.class);
- if (pluginAliases != null) {
- for (final String alias : pluginAliases.value()) {
- final PluginEntry aliasEntry = new PluginEntry();
- final String aliasElementName = plugin.elementType().equals(
- Plugin.EMPTY) ? alias.trim() : plugin.elementType();
- aliasEntry.setKey(alias.trim().toLowerCase());
- aliasEntry.setName(plugin.name());
- aliasEntry.setCategory(plugin.category());
- aliasEntry.setClassName(clazz.getName());
- aliasEntry.setPrintable(plugin.printObject());
- aliasEntry.setDefer(plugin.deferChildren());
- final PluginType<?> aliasType = new PluginType<>(aliasEntry, clazz, aliasElementName);
- list.add(aliasType);
- }
- }
- }
-
- final long endTime = System.nanoTime();
- final DecimalFormat numFormat = new DecimalFormat("#0.000000");
- final double seconds = (endTime - startTime) * 1e-9;
- LOGGER.debug("Took {} seconds to load {} plugins from package {}",
- numFormat.format(seconds), resolver.getClasses().size(), pkg);
-
- // Note multiple threads could be calling this method concurrently. Both will do the work,
- // but only one will be allowed to store the result in the outer map.
- // Return the inner map produced by whichever thread won the race, so all callers will get the same result.
- existing = pluginsByCategoryByPackage.putIfAbsent(pkg, newPluginsByCategory);
- if (existing != null) {
- return existing;
- }
- return newPluginsByCategory;
- }
-
- /**
- * A Test that checks to see if each class is annotated with the 'Plugin' annotation. If it
- * is, then the test returns true, otherwise false.
- *
- * @since 2.1
- */
- public static class PluginTest implements ResolverUtil.Test {
- @Override
- public boolean matches(final Class<?> type) {
- return type != null && type.isAnnotationPresent(Plugin.class);
- }
-
- @Override
- public String toString() {
- return "annotated with @" + Plugin.class.getSimpleName();
- }
-
- @Override
- public boolean matches(final URI resource) {
- throw new UnsupportedOperationException();
- }
-
- @Override
- public boolean doesMatchClass() {
- return true;
- }
-
- @Override
- public boolean doesMatchResource() {
- return false;
- }
- }
-}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/util/PluginType.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/util/PluginType.java
deleted file mode 100644
index cc6bc66..0000000
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/util/PluginType.java
+++ /dev/null
@@ -1,83 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache license, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the license for the specific language governing permissions and
- * limitations under the license.
- */
-package org.apache.logging.log4j.core.config.plugins.util;
-
-
-import org.apache.logging.log4j.core.config.plugins.processor.PluginEntry;
-
-/**
- * Plugin Descriptor. This is a memento object for Plugin annotations paired to their annotated classes.
- *
- * @param <T> The plug-in class, which can be any kind of class.
- * @see org.apache.logging.log4j.core.config.plugins.Plugin
- */
-public class PluginType<T> {
-
- private final PluginEntry pluginEntry;
- private final Class<T> pluginClass;
- private final String elementName;
-
- /**
- * @since 2.1
- */
- public PluginType(final PluginEntry pluginEntry, final Class<T> pluginClass, final String elementName) {
- this.pluginEntry = pluginEntry;
- this.pluginClass = pluginClass;
- this.elementName = elementName;
- }
-
- public Class<T> getPluginClass() {
- return this.pluginClass;
- }
-
- public String getElementName() {
- return this.elementName;
- }
-
- /**
- * @since 2.1
- */
- public String getKey() {
- return this.pluginEntry.getKey();
- }
-
- public boolean isObjectPrintable() {
- return this.pluginEntry.isPrintable();
- }
-
- public boolean isDeferChildren() {
- return this.pluginEntry.isDefer();
- }
-
- /**
- * @since 2.1
- */
- public String getCategory() {
- return this.pluginEntry.getCategory();
- }
-
- @Override
- public String toString() {
- return "PluginType [pluginClass=" + pluginClass +
- ", key=" + pluginEntry.getKey() +
- ", elementName=" + pluginEntry.getName() +
- ", isObjectPrintable=" + pluginEntry.isPrintable() +
- ", isDeferChildren==" + pluginEntry.isDefer() +
- ", category=" + pluginEntry.getCategory() +
- "]";
- }
-}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/util/ResolverUtil.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/util/ResolverUtil.java
deleted file mode 100644
index 73b5fc0..0000000
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/util/ResolverUtil.java
+++ /dev/null
@@ -1,456 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache license, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the license for the specific language governing permissions and
- * limitations under the license.
- */
-package org.apache.logging.log4j.core.config.plugins.util;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.UnsupportedEncodingException;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.net.URL;
-import java.net.URLDecoder;
-import java.nio.charset.StandardCharsets;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Enumeration;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
-import java.util.jar.JarEntry;
-import java.util.jar.JarInputStream;
-
-import org.apache.logging.log4j.Logger;
-import org.apache.logging.log4j.core.util.Loader;
-import org.apache.logging.log4j.status.StatusLogger;
-import org.osgi.framework.FrameworkUtil;
-import org.osgi.framework.wiring.BundleWiring;
-
-/**
- * <p>
- * ResolverUtil is used to locate classes that are available in the/a class path and meet arbitrary conditions. The two
- * most common conditions are that a class implements/extends another class, or that is it annotated with a specific
- * annotation. However, through the use of the {@link Test} class it is possible to search using arbitrary conditions.
- * </p>
- *
- * <p>
- * A ClassLoader is used to locate all locations (directories and jar files) in the class path that contain classes
- * within certain packages, and then to load those classes and check them. By default the ClassLoader returned by
- * {@code Thread.currentThread().getContextClassLoader()} is used, but this can be overridden by calling
- * {@link #setClassLoader(ClassLoader)} prior to invoking any of the {@code find()} methods.
- * </p>
- *
- * <p>
- * General searches are initiated by calling the {@link #find(ResolverUtil.Test, String...)} method and supplying a
- * package name and a Test instance. This will cause the named package <b>and all sub-packages</b> to be scanned for
- * classes that meet the test. There are also utility methods for the common use cases of scanning multiple packages for
- * extensions of particular classes, or classes annotated with a specific annotation.
- * </p>
- *
- * <p>
- * The standard usage pattern for the ResolverUtil class is as follows:
- * </p>
- *
- * <pre>
- * ResolverUtil resolver = new ResolverUtil();
- * resolver.findInPackage(new CustomTest(), pkg1);
- * resolver.find(new CustomTest(), pkg1);
- * resolver.find(new CustomTest(), pkg1, pkg2);
- * Set<Class<?>> beans = resolver.getClasses();
- * </pre>
- *
- * <p>
- * This class was copied and modified from Stripes - http://stripes.mc4j.org/confluence/display/stripes/Home
- * </p>
- */
-public class ResolverUtil {
- /** An instance of Log to use for logging in this class. */
- private static final Logger LOGGER = StatusLogger.getLogger();
-
- private static final String VFSZIP = "vfszip";
-
- private static final String VFS = "vfs";
-
- private static final String BUNDLE_RESOURCE = "bundleresource";
-
- /** The set of matches being accumulated. */
- private final Set<Class<?>> classMatches = new HashSet<>();
-
- /** The set of matches being accumulated. */
- private final Set<URI> resourceMatches = new HashSet<>();
-
- /**
- * The ClassLoader to use when looking for classes. If null then the ClassLoader returned by
- * Thread.currentThread().getContextClassLoader() will be used.
- */
- private ClassLoader classloader;
-
- /**
- * Provides access to the classes discovered so far. If no calls have been made to any of the {@code find()}
- * methods, this set will be empty.
- *
- * @return the set of classes that have been discovered.
- */
- public Set<Class<?>> getClasses() {
- return classMatches;
- }
-
- /**
- * Returns the matching resources.
- *
- * @return A Set of URIs that match the criteria.
- */
- public Set<URI> getResources() {
- return resourceMatches;
- }
-
- /**
- * Returns the ClassLoader that will be used for scanning for classes. If no explicit ClassLoader has been set by
- * the calling, the context class loader will be used.
- *
- * @return the ClassLoader that will be used to scan for classes
- */
- public ClassLoader getClassLoader() {
- return classloader != null ? classloader : (classloader = Loader.getClassLoader(ResolverUtil.class, null));
- }
-
- /**
- * Sets an explicit ClassLoader that should be used when scanning for classes. If none is set then the context
- * ClassLoader will be used.
- *
- * @param aClassloader
- * a ClassLoader to use when scanning for classes
- */
- public void setClassLoader(final ClassLoader aClassloader) {
- this.classloader = aClassloader;
- }
-
- /**
- * Attempts to discover classes that pass the test. Accumulated classes can be accessed by calling
- * {@link #getClasses()}.
- *
- * @param test
- * the test to determine matching classes
- * @param packageNames
- * one or more package names to scan (including subpackages) for classes
- */
- public void find(final Test test, final String... packageNames) {
- if (packageNames == null) {
- return;
- }
-
- for (final String pkg : packageNames) {
- findInPackage(test, pkg);
- }
- }
-
- /**
- * Scans for classes starting at the package provided and descending into subpackages. Each class is offered up to
- * the Test as it is discovered, and if the Test returns true the class is retained. Accumulated classes can be
- * fetched by calling {@link #getClasses()}.
- *
- * @param test
- * an instance of {@link Test} that will be used to filter classes
- * @param packageName
- * the name of the package from which to start scanning for classes, e.g. {@code net.sourceforge.stripes}
- */
- public void findInPackage(final Test test, String packageName) {
- packageName = packageName.replace('.', '/');
- final ClassLoader loader = getClassLoader();
- Enumeration<URL> urls;
-
- try {
- urls = loader.getResources(packageName);
- } catch (final IOException ioe) {
- LOGGER.warn("Could not read package: {}", packageName, ioe);
- return;
- }
-
- while (urls.hasMoreElements()) {
- try {
- final URL url = urls.nextElement();
- final String urlPath = extractPath(url);
-
- LOGGER.info("Scanning for classes in '{}' matching criteria {}", urlPath , test);
- // Check for a jar in a war in JBoss
- if (VFSZIP.equals(url.getProtocol())) {
- final String path = urlPath.substring(0, urlPath.length() - packageName.length() - 2);
- final URL newURL = new URL(url.getProtocol(), url.getHost(), path);
- @SuppressWarnings("resource")
- final JarInputStream stream = new JarInputStream(newURL.openStream());
- try {
- loadImplementationsInJar(test, packageName, path, stream);
- } finally {
- close(stream, newURL);
- }
- } else if (VFS.equals(url.getProtocol())) {
- final String containerPath = urlPath.substring(1, urlPath.length() - packageName.length() - 2);
- final File containerFile = new File(containerPath);
- if (containerFile.exists()) {
- if (containerFile.isDirectory()) {
- loadImplementationsInDirectory(test, packageName, new File(containerFile, packageName));
- } else {
- loadImplementationsInJar(test, packageName, containerFile);
- }
- } else {
- // fallback code for Jboss/Wildfly, if the file couldn't be found
- // by loading the path as a file, try to read the jar as a stream
- final String path = urlPath.substring(0, urlPath.length() - packageName.length() - 2);
- final URL newURL = new URL(url.getProtocol(), url.getHost(), path);
-
- try (final InputStream is = newURL.openStream()) {
- final JarInputStream jarStream;
- if (is instanceof JarInputStream) {
- jarStream = (JarInputStream) is;
- } else {
- jarStream = new JarInputStream(is);
- }
- loadImplementationsInJar(test, packageName, path, jarStream);
- }
- }
- } else if (BUNDLE_RESOURCE.equals(url.getProtocol())) {
- loadImplementationsInBundle(test, packageName);
- } else {
- final File file = new File(urlPath);
- if (file.isDirectory()) {
- loadImplementationsInDirectory(test, packageName, file);
- } else {
- loadImplementationsInJar(test, packageName, file);
- }
- }
- } catch (final IOException | URISyntaxException ioe) {
- LOGGER.warn("Could not read entries", ioe);
- }
- }
- }
-
- String extractPath(final URL url) throws UnsupportedEncodingException, URISyntaxException {
- String urlPath = url.getPath(); // same as getFile but without the Query portion
- // System.out.println(url.getProtocol() + "->" + urlPath);
-
- // I would be surprised if URL.getPath() ever starts with "jar:" but no harm in checking
- if (urlPath.startsWith("jar:")) {
- urlPath = urlPath.substring(4);
- }
- // For jar: URLs, the path part starts with "file:"
- if (urlPath.startsWith("file:")) {
- urlPath = urlPath.substring(5);
- }
- // If it was in a JAR, grab the path to the jar
- final int bangIndex = urlPath.indexOf('!');
- if (bangIndex > 0) {
- urlPath = urlPath.substring(0, bangIndex);
- }
-
- // LOG4J2-445
- // Finally, decide whether to URL-decode the file name or not...
- final String protocol = url.getProtocol();
- final List<String> neverDecode = Arrays.asList(VFS, VFSZIP, BUNDLE_RESOURCE);
- if (neverDecode.contains(protocol)) {
- return urlPath;
- }
- final String cleanPath = new URI(urlPath).getPath();
- if (new File(cleanPath).exists()) {
- // if URL-encoded file exists, don't decode it
- return cleanPath;
- }
- return URLDecoder.decode(urlPath, StandardCharsets.UTF_8.name());
- }
-
- private void loadImplementationsInBundle(final Test test, final String packageName) {
- final BundleWiring wiring = FrameworkUtil.getBundle(ResolverUtil.class).adapt(BundleWiring.class);
- final Collection<String> list = wiring.listResources(packageName, "*.class",
- BundleWiring.LISTRESOURCES_RECURSE);
- for (final String name : list) {
- addIfMatching(test, name);
- }
- }
-
- /**
- * Finds matches in a physical directory on a file system. Examines all files within a directory - if the File object
- * is not a directory, and ends with <i>.class</i> the file is loaded and tested to see if it is acceptable
- * according to the Test. Operates recursively to find classes within a folder structure matching the package
- * structure.
- *
- * @param test
- * a Test used to filter the classes that are discovered
- * @param parent
- * the package name up to this directory in the package hierarchy. E.g. if /classes is in the classpath and
- * we wish to examine files in /classes/org/apache then the values of <i>parent</i> would be
- * <i>org/apache</i>
- * @param location
- * a File object representing a directory
- */
- private void loadImplementationsInDirectory(final Test test, final String parent, final File location) {
- final File[] files = location.listFiles();
- if (files == null) {
- return;
- }
-
- StringBuilder builder;
- for (final File file : files) {
- builder = new StringBuilder();
- builder.append(parent).append('/').append(file.getName());
- final String packageOrClass = parent == null ? file.getName() : builder.toString();
-
- if (file.isDirectory()) {
- loadImplementationsInDirectory(test, packageOrClass, file);
- } else if (isTestApplicable(test, file.getName())) {
- addIfMatching(test, packageOrClass);
- }
- }
- }
-
- private boolean isTestApplicable(final Test test, final String path) {
- return test.doesMatchResource() || path.endsWith(".class") && test.doesMatchClass();
- }
-
- /**
- * Finds matching classes within a jar files that contains a folder structure matching the package structure. If the
- * File is not a JarFile or does not exist a warning will be logged, but no error will be raised.
- *
- * @param test
- * a Test used to filter the classes that are discovered
- * @param parent
- * the parent package under which classes must be in order to be considered
- * @param jarFile
- * the jar file to be examined for classes
- */
- private void loadImplementationsInJar(final Test test, final String parent, final File jarFile) {
- JarInputStream jarStream = null;
- try {
- jarStream = new JarInputStream(new FileInputStream(jarFile));
- loadImplementationsInJar(test, parent, jarFile.getPath(), jarStream);
- } catch (final IOException ex) {
- LOGGER.error("Could not search JAR file '{}' for classes matching criteria {}, file not found", jarFile,
- test, ex);
- } finally {
- close(jarStream, jarFile);
- }
- }
-
- /**
- * @param jarStream
- * @param source
- */
- private void close(final JarInputStream jarStream, final Object source) {
- if (jarStream != null) {
- try {
- jarStream.close();
- } catch (final IOException e) {
- LOGGER.error("Error closing JAR file stream for {}", source, e);
- }
- }
- }
-
- /**
- * Finds matching classes within a jar files that contains a folder structure matching the package structure. If the
- * File is not a JarFile or does not exist a warning will be logged, but no error will be raised.
- *
- * @param test
- * a Test used to filter the classes that are discovered
- * @param parent
- * the parent package under which classes must be in order to be considered
- * @param stream
- * The jar InputStream
- */
- private void loadImplementationsInJar(final Test test, final String parent, final String path,
- final JarInputStream stream) {
-
- try {
- JarEntry entry;
-
- while ((entry = stream.getNextJarEntry()) != null) {
- final String name = entry.getName();
- if (!entry.isDirectory() && name.startsWith(parent) && isTestApplicable(test, name)) {
- addIfMatching(test, name);
- }
- }
- } catch (final IOException ioe) {
- LOGGER.error("Could not search JAR file '{}' for classes matching criteria {} due to an IOException", path,
- test, ioe);
- }
- }
-
- /**
- * Add the class designated by the fully qualified class name provided to the set of resolved classes if and only if
- * it is approved by the Test supplied.
- *
- * @param test
- * the test used to determine if the class matches
- * @param fqn
- * the fully qualified name of a class
- */
- protected void addIfMatching(final Test test, final String fqn) {
- try {
- final ClassLoader loader = getClassLoader();
- if (test.doesMatchClass()) {
- final String externalName = fqn.substring(0, fqn.indexOf('.')).replace('/', '.');
- if (LOGGER.isDebugEnabled()) {
- LOGGER.debug("Checking to see if class {} matches criteria {}", externalName, test);
- }
-
- final Class<?> type = loader.loadClass(externalName);
- if (test.matches(type)) {
- classMatches.add(type);
- }
- }
- if (test.doesMatchResource()) {
- URL url = loader.getResource(fqn);
- if (url == null) {
- url = loader.getResource(fqn.substring(1));
- }
- if (url != null && test.matches(url.toURI())) {
- resourceMatches.add(url.toURI());
- }
- }
- } catch (final Throwable t) {
- LOGGER.warn("Could not examine class {}", fqn, t);
- }
- }
-
- /**
- * A simple interface that specifies how to test classes to determine if they are to be included in the results
- * produced by the ResolverUtil.
- */
- public interface Test {
- /**
- * Will be called repeatedly with candidate classes. Must return True if a class is to be included in the
- * results, false otherwise.
- *
- * @param type
- * The Class to match against.
- * @return true if the Class matches.
- */
- boolean matches(Class<?> type);
-
- /**
- * Test for a resource.
- *
- * @param resource
- * The URI to the resource.
- * @return true if the resource matches.
- */
- boolean matches(URI resource);
-
- boolean doesMatchClass();
-
- boolean doesMatchResource();
- }
-
-}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/validation/Constraint.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/validation/Constraint.java
deleted file mode 100644
index 0ac2223..0000000
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/validation/Constraint.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache license, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the license for the specific language governing permissions and
- * limitations under the license.
- */
-package org.apache.logging.log4j.core.config.plugins.validation;
-
-import java.lang.annotation.Annotation;
-import java.lang.annotation.Documented;
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-/**
- * Meta annotation to mark an annotation as a validation constraint. This annotation must specify a
- * {@link ConstraintValidator} implementation class that has a default constructor.
- *
- * @since 2.1
- */
-@Documented
-@Target(ElementType.ANNOTATION_TYPE)
-@Retention(RetentionPolicy.RUNTIME)
-public @interface Constraint {
-
- /**
- * {@link ConstraintValidator} class that implements the validation logic for the annotated constraint annotation.
- */
- Class<? extends ConstraintValidator<? extends Annotation>> value();
-}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/validation/ConstraintValidator.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/validation/ConstraintValidator.java
deleted file mode 100644
index 1d8c0c5..0000000
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/validation/ConstraintValidator.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache license, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the license for the specific language governing permissions and
- * limitations under the license.
- */
-package org.apache.logging.log4j.core.config.plugins.validation;
-
-import java.lang.annotation.Annotation;
-
-/**
- * Interface that {@link Constraint} annotations must implement to perform validation logic.
- *
- * @param <A> the {@link Constraint} annotation this interface validates.
- * @since 2.1
- */
-public interface ConstraintValidator<A extends Annotation> {
-
- /**
- * Called before this validator is used with the constraint annotation value.
- *
- * @param annotation the annotation value this validator will be validating.
- */
- void initialize(A annotation);
-
- /**
- * Indicates if the given value is valid.
- *
- * @param name the name to use for error reporting
- * @param value the value to validate.
- * @return {@code true} if the given value is valid.
- */
- boolean isValid(String name, Object value);
-}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/validation/ConstraintValidators.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/validation/ConstraintValidators.java
deleted file mode 100644
index 374c8ec..0000000
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/validation/ConstraintValidators.java
+++ /dev/null
@@ -1,84 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache license, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the license for the specific language governing permissions and
- * limitations under the license.
- */
-package org.apache.logging.log4j.core.config.plugins.validation;
-
-import java.lang.annotation.Annotation;
-import java.lang.reflect.ParameterizedType;
-import java.lang.reflect.Type;
-import java.util.ArrayList;
-import java.util.Collection;
-
-import org.apache.logging.log4j.core.util.ReflectionUtil;
-
-/**
- * Utility class to locate an appropriate {@link ConstraintValidator} implementation for an annotation.
- *
- * @since 2.1
- */
-public final class ConstraintValidators {
-
- private ConstraintValidators() {
- }
-
- /**
- * Finds all relevant {@link ConstraintValidator} objects from an array of annotations. All validators will be
- * {@link ConstraintValidator#initialize(java.lang.annotation.Annotation) initialized} before being returned.
- *
- * @param annotations the annotations to find constraint validators for
- * @return a collection of ConstraintValidators for the given annotations
- */
- public static Collection<ConstraintValidator<?>> findValidators(final Annotation... annotations) {
- final Collection<ConstraintValidator<?>> validators =
- new ArrayList<>();
- for (final Annotation annotation : annotations) {
- final Class<? extends Annotation> type = annotation.annotationType();
- if (type.isAnnotationPresent(Constraint.class)) {
- final ConstraintValidator<?> validator = getValidator(annotation, type);
- if (validator != null) {
- validators.add(validator);
- }
- }
- }
- return validators;
- }
-
- private static <A extends Annotation> ConstraintValidator<A> getValidator(final A annotation,
- final Class<? extends A> type) {
- final Constraint constraint = type.getAnnotation(Constraint.class);
- final Class<? extends ConstraintValidator<?>> validatorClass = constraint.value();
- if (type.equals(getConstraintValidatorAnnotationType(validatorClass))) {
- @SuppressWarnings("unchecked") // I don't think we could be any more thorough in validation here
- final ConstraintValidator<A> validator = (ConstraintValidator<A>)
- ReflectionUtil.instantiate(validatorClass);
- validator.initialize(annotation);
- return validator;
- }
- return null;
- }
-
- private static Type getConstraintValidatorAnnotationType(final Class<? extends ConstraintValidator<?>> type) {
- for (final Type parentType : type.getGenericInterfaces()) {
- if (parentType instanceof ParameterizedType) {
- final ParameterizedType parameterizedType = (ParameterizedType) parentType;
- if (ConstraintValidator.class.equals(parameterizedType.getRawType())) {
- return parameterizedType.getActualTypeArguments()[0];
- }
- }
- }
- return Void.TYPE;
- }
-}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/validation/constraints/Required.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/validation/constraints/Required.java
deleted file mode 100644
index e6f3c56..0000000
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/validation/constraints/Required.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache license, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the license for the specific language governing permissions and
- * limitations under the license.
- */
-
-package org.apache.logging.log4j.core.config.plugins.validation.constraints;
-
-import java.lang.annotation.Documented;
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-import org.apache.logging.log4j.core.config.plugins.validation.Constraint;
-import org.apache.logging.log4j.core.config.plugins.validation.validators.RequiredValidator;
-
-/**
- * Marks a plugin builder field or plugin factory parameter as required.
- *
- * @since 2.1
- */
-@Documented
-@Retention(RetentionPolicy.RUNTIME)
-@Target({ElementType.FIELD, ElementType.PARAMETER})
-@Constraint(RequiredValidator.class)
-public @interface Required {
-
- /**
- * The message to be logged if this constraint is violated. This should normally be overridden.
- */
- String message() default "The parameter is null";
-}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/validation/constraints/ValidHost.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/validation/constraints/ValidHost.java
deleted file mode 100644
index c652d40..0000000
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/validation/constraints/ValidHost.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache license, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the license for the specific language governing permissions and
- * limitations under the license.
- */
-package org.apache.logging.log4j.core.config.plugins.validation.constraints;
-
-import org.apache.logging.log4j.core.config.plugins.validation.Constraint;
-import org.apache.logging.log4j.core.config.plugins.validation.validators.ValidHostValidator;
-
-import java.lang.annotation.*;
-import java.net.InetAddress;
-
-/**
- * Indicates that a plugin attribute must be a valid host. This relies on the same validation rules as
- * {@link InetAddress#getByName(String)}.
- *
- * @since 2.8
- */
-@Documented
-@Retention(RetentionPolicy.RUNTIME)
-@Target({ElementType.FIELD, ElementType.PARAMETER})
-@Constraint(ValidHostValidator.class)
-public @interface ValidHost {
-
- /**
- * The message to be logged if this constraint is violated. This should normally be overridden.
- */
- String message() default "The hostname is invalid";
-}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/validation/constraints/ValidPort.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/validation/constraints/ValidPort.java
deleted file mode 100644
index a7c68b1..0000000
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/validation/constraints/ValidPort.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache license, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the license for the specific language governing permissions and
- * limitations under the license.
- */
-package org.apache.logging.log4j.core.config.plugins.validation.constraints;
-
-import java.lang.annotation.Documented;
-import java.lang.annotation.ElementType;
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.lang.annotation.Target;
-
-import org.apache.logging.log4j.core.config.plugins.validation.Constraint;
-import org.apache.logging.log4j.core.config.plugins.validation.validators.ValidPortValidator;
-
-/**
- * Indicates that a plugin attribute must be a valid port number. A valid port number is an integer between 0 and
- * 65535.
- *
- * @since 2.8
- */
-@Documented
-@Retention(RetentionPolicy.RUNTIME)
-@Target({ElementType.FIELD, ElementType.PARAMETER})
-@Constraint(ValidPortValidator.class)
-public @interface ValidPort {
-
- /**
- * The message to be logged if this constraint is violated. This should normally be overridden.
- */
- String message() default "The port number is invalid";
-}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/validation/constraints/package-info.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/validation/constraints/package-info.java
deleted file mode 100644
index f22ba49..0000000
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/validation/constraints/package-info.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache license, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the license for the specific language governing permissions and
- * limitations under the license.
- */
-
-/**
- * Validation annotations.
- *
- * @since 2.1
- */
-package org.apache.logging.log4j.core.config.plugins.validation.constraints;
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/validation/package-info.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/validation/package-info.java
deleted file mode 100644
index 171b25a..0000000
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/validation/package-info.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache license, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the license for the specific language governing permissions and
- * limitations under the license.
- */
-
-/**
- * Constraint validators for plugin factory methods.
- *
- * @since 2.1
- */
-package org.apache.logging.log4j.core.config.plugins.validation;
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/validation/validators/RequiredValidator.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/validation/validators/RequiredValidator.java
deleted file mode 100644
index 98c0a71..0000000
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/validation/validators/RequiredValidator.java
+++ /dev/null
@@ -1,60 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache license, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the license for the specific language governing permissions and
- * limitations under the license.
- */
-package org.apache.logging.log4j.core.config.plugins.validation.validators;
-
-import java.util.Collection;
-import java.util.Map;
-
-import org.apache.logging.log4j.Logger;
-import org.apache.logging.log4j.core.config.plugins.validation.ConstraintValidator;
-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;
-
-/**
- * Validator that checks an object for emptiness. Emptiness is defined here as:
- * <ul>
- * <li>The value {@code null}</li>
- * <li>An object of type {@link CharSequence} with length 0</li>
- * <li>An empty array</li>
- * <li>An empty {@link Collection}</li>
- * <li>An empty {@link Map}</li>
- * </ul>
- *
- * @since 2.1
- */
-public class RequiredValidator implements ConstraintValidator<Required> {
-
- private static final Logger LOGGER = StatusLogger.getLogger();
-
- private Required annotation;
-
- @Override
- public void initialize(final Required anAnnotation) {
- this.annotation = anAnnotation;
- }
-
- @Override
- public boolean isValid(final String name, final Object value) {
- return Assert.isNonEmpty(value) || err(name);
- }
-
- private boolean err(final String name) {
- LOGGER.error(annotation.message() + ": " + name);
- return false;
- }
-}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/validation/validators/ValidHostValidator.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/validation/validators/ValidHostValidator.java
deleted file mode 100644
index 6c01753..0000000
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/validation/validators/ValidHostValidator.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache license, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the license for the specific language governing permissions and
- * limitations under the license.
- */
-package org.apache.logging.log4j.core.config.plugins.validation.validators;
-
-import org.apache.logging.log4j.Logger;
-import org.apache.logging.log4j.core.config.plugins.validation.ConstraintValidator;
-import org.apache.logging.log4j.core.config.plugins.validation.constraints.ValidHost;
-import org.apache.logging.log4j.status.StatusLogger;
-
-import java.net.InetAddress;
-import java.net.UnknownHostException;
-
-/**
- * Validator that checks an object to verify it is a valid hostname or IP address. Validation rules follow the same
- * logic as in {@link InetAddress#getByName(String)}.
- *
- * @since 2.8
- */
-public class ValidHostValidator implements ConstraintValidator<ValidHost> {
-
- private static final Logger LOGGER = StatusLogger.getLogger();
-
- private ValidHost annotation;
-
- @Override
- public void initialize(final ValidHost annotation) {
- this.annotation = annotation;
- }
-
- @Override
- public boolean isValid(final String name, final Object value) {
- if (value == null) {
- LOGGER.error(annotation.message());
- return false;
- }
- if (value instanceof InetAddress) {
- // InetAddress factory methods all have built in validation
- return true;
- }
- try {
- InetAddress.getByName(value.toString());
- return true;
- } catch (final UnknownHostException e) {
- LOGGER.error(annotation.message(), e);
- return false;
- }
- }
-}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/validation/validators/ValidPortValidator.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/validation/validators/ValidPortValidator.java
deleted file mode 100644
index a59742c..0000000
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/validation/validators/ValidPortValidator.java
+++ /dev/null
@@ -1,57 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache license, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the license for the specific language governing permissions and
- * limitations under the license.
- */
-package org.apache.logging.log4j.core.config.plugins.validation.validators;
-
-import org.apache.logging.log4j.Logger;
-import org.apache.logging.log4j.core.config.plugins.convert.TypeConverters;
-import org.apache.logging.log4j.core.config.plugins.validation.ConstraintValidator;
-import org.apache.logging.log4j.core.config.plugins.validation.constraints.ValidPort;
-import org.apache.logging.log4j.status.StatusLogger;
-
-/**
- * Validator that checks an object to verify it is a valid port number (an integer between 0 and 65535).
- *
- * @since 2.8
- */
-public class ValidPortValidator implements ConstraintValidator<ValidPort> {
-
- private static final Logger LOGGER = StatusLogger.getLogger();
-
- private ValidPort annotation;
-
- @Override
- public void initialize(final ValidPort annotation) {
- this.annotation = annotation;
- }
-
- @Override
- public boolean isValid(final String name, final Object value) {
- if (value instanceof CharSequence) {
- return isValid(name, TypeConverters.convert(value.toString(), Integer.class, -1));
- }
- if (!Integer.class.isInstance(value)) {
- LOGGER.error(annotation.message());
- return false;
- }
- final int port = (int) value;
- if (port < 0 || port > 65535) {
- LOGGER.error(annotation.message());
- return false;
- }
- return true;
- }
-}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/validation/validators/package-info.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/validation/validators/package-info.java
deleted file mode 100644
index a8ac560..0000000
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/validation/validators/package-info.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache license, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the license for the specific language governing permissions and
- * limitations under the license.
- */
-
-/**
- * ConstraintValidator implementations for the constraint annotations.
- *
- * @since 2.1
- */
-package org.apache.logging.log4j.core.config.plugins.validation.validators;
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/AbstractPluginVisitor.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/AbstractPluginVisitor.java
deleted file mode 100644
index 560cbe3..0000000
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/AbstractPluginVisitor.java
+++ /dev/null
@@ -1,153 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache license, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the license for the specific language governing permissions and
- * limitations under the license.
- */
-
-package org.apache.logging.log4j.core.config.plugins.visitors;
-
-import java.lang.annotation.Annotation;
-import java.lang.reflect.Member;
-import java.util.Map;
-import java.util.Objects;
-
-import org.apache.logging.log4j.Logger;
-import org.apache.logging.log4j.core.config.plugins.convert.TypeConverters;
-import org.apache.logging.log4j.core.lookup.StrSubstitutor;
-import org.apache.logging.log4j.status.StatusLogger;
-import org.apache.logging.log4j.util.Strings;
-
-/**
- * Base class for PluginVisitor implementations. Provides convenience methods as well as all method implementations
- * other than the {@code visit} method.
- *
- * @param <A> the Plugin annotation type.
- */
-public abstract class AbstractPluginVisitor<A extends Annotation> implements PluginVisitor<A> {
-
- /** Status logger. */
- protected static final Logger LOGGER = StatusLogger.getLogger();
-
- /**
- *
- */
- protected final Class<A> clazz;
- /**
- *
- */
- protected A annotation;
- /**
- *
- */
- protected String[] aliases;
- /**
- *
- */
- protected Class<?> conversionType;
- /**
- *
- */
- protected StrSubstitutor substitutor;
- /**
- *
- */
- protected Member member;
-
- /**
- * This constructor must be overridden by implementation classes as a no-arg constructor.
- *
- * @param clazz the annotation class this PluginVisitor is for.
- */
- protected AbstractPluginVisitor(final Class<A> clazz) {
- this.clazz = clazz;
- }
-
- @SuppressWarnings("unchecked")
- @Override
- public PluginVisitor<A> setAnnotation(final Annotation anAnnotation) {
- final Annotation a = Objects.requireNonNull(anAnnotation, "No annotation was provided");
- if (this.clazz.isInstance(a)) {
- this.annotation = (A) a;
- }
- return this;
- }
-
- @Override
- public PluginVisitor<A> setAliases(final String... someAliases) {
- this.aliases = someAliases;
- return this;
- }
-
- @Override
- public PluginVisitor<A> setConversionType(final Class<?> aConversionType) {
- this.conversionType = Objects.requireNonNull(aConversionType, "No conversion type class was provided");
- return this;
- }
-
- @Override
- public PluginVisitor<A> setStrSubstitutor(final StrSubstitutor aSubstitutor) {
- this.substitutor = Objects.requireNonNull(aSubstitutor, "No StrSubstitutor was provided");
- return this;
- }
-
- @Override
- public PluginVisitor<A> setMember(final Member aMember) {
- this.member = aMember;
- return this;
- }
-
- /**
- * Removes an Entry from a given Map using a key name and aliases for that key. Keys are case-insensitive.
- *
- * @param attributes the Map to remove an Entry from.
- * @param name the key name to look up.
- * @param aliases optional aliases of the key name to look up.
- * @return the value corresponding to the given key or {@code null} if nonexistent.
- */
- protected static String removeAttributeValue(final Map<String, String> attributes,
- final String name,
- final String... aliases) {
- for (final Map.Entry<String, String> entry : attributes.entrySet()) {
- final String key = entry.getKey();
- final String value = entry.getValue();
- if (key.equalsIgnoreCase(name)) {
- attributes.remove(key);
- return value;
- }
- if (aliases != null) {
- for (final String alias : aliases) {
- if (key.equalsIgnoreCase(alias)) {
- attributes.remove(key);
- return value;
- }
- }
- }
- }
- return null;
- }
-
- /**
- * Converts the given value into the configured type falling back to the provided default value.
- *
- * @param value the value to convert.
- * @param defaultValue the fallback value to use in case of no value or an error.
- * @return the converted value whether that be based on the given value or the default value.
- */
- protected Object convert(final String value, final Object defaultValue) {
- if (defaultValue instanceof String) {
- return TypeConverters.convert(value, this.conversionType, Strings.trimToNull((String) defaultValue));
- }
- return TypeConverters.convert(value, this.conversionType, defaultValue);
- }
-}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginAttributeVisitor.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginAttributeVisitor.java
index f4da42b..76e9c31 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginAttributeVisitor.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginAttributeVisitor.java
@@ -14,68 +14,79 @@
* See the license for the specific language governing permissions and
* limitations under the license.
*/
-
package org.apache.logging.log4j.core.config.plugins.visitors;
-import java.util.Map;
-
-import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.config.Configuration;
-import org.apache.logging.log4j.core.config.Node;
import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
-import org.apache.logging.log4j.core.util.NameUtil;
+import org.apache.logging.log4j.plugins.inject.AbstractConfigurationInjector;
+import org.apache.logging.log4j.util.NameUtil;
import org.apache.logging.log4j.util.StringBuilders;
+import org.apache.logging.log4j.util.Strings;
+
+import java.lang.reflect.Type;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Function;
/**
- * PluginVisitor implementation for {@link PluginAttribute}.
+ * @deprecated Provided to support legacy plugins.
*/
-public class PluginAttributeVisitor extends AbstractPluginVisitor<PluginAttribute> {
- public PluginAttributeVisitor() {
- super(PluginAttribute.class);
+// copy of PluginAttributeInjector
+public class PluginAttributeVisitor extends AbstractConfigurationInjector<PluginAttribute, Configuration> {
+
+ private static final Map<Type, Function<PluginAttribute, Object>> DEFAULT_VALUE_EXTRACTORS;
+
+ static {
+ final Map<Class<?>, Function<PluginAttribute, Object>> extractors = new ConcurrentHashMap<>();
+ extractors.put(int.class, PluginAttribute::defaultInt);
+ extractors.put(Integer.class, PluginAttribute::defaultInt);
+ extractors.put(long.class, PluginAttribute::defaultLong);
+ extractors.put(Long.class, PluginAttribute::defaultLong);
+ extractors.put(boolean.class, PluginAttribute::defaultBoolean);
+ extractors.put(Boolean.class, PluginAttribute::defaultBoolean);
+ extractors.put(float.class, PluginAttribute::defaultFloat);
+ extractors.put(Float.class, PluginAttribute::defaultFloat);
+ extractors.put(double.class, PluginAttribute::defaultDouble);
+ extractors.put(Double.class, PluginAttribute::defaultDouble);
+ extractors.put(byte.class, PluginAttribute::defaultByte);
+ extractors.put(Byte.class, PluginAttribute::defaultByte);
+ extractors.put(char.class, PluginAttribute::defaultChar);
+ extractors.put(Character.class, PluginAttribute::defaultChar);
+ extractors.put(short.class, PluginAttribute::defaultShort);
+ extractors.put(Short.class, PluginAttribute::defaultShort);
+ extractors.put(Class.class, PluginAttribute::defaultClass);
+ DEFAULT_VALUE_EXTRACTORS = Collections.unmodifiableMap(extractors);
}
@Override
- public Object visit(final Configuration configuration, final Node node, final LogEvent event,
- final StringBuilder log) {
- final String name = this.annotation.value();
- final Map<String, String> attributes = node.getAttributes();
- final String rawValue = removeAttributeValue(attributes, name, this.aliases);
- final String replacedValue = this.substitutor.replace(event, rawValue);
- final Object defaultValue = findDefaultValue(event);
- final Object value = convert(replacedValue, defaultValue);
- final Object debugValue = this.annotation.sensitive() ? NameUtil.md5(value + this.getClass().getName()) : value;
- StringBuilders.appendKeyDqValue(log, name, debugValue);
- return value;
+ public void inject(final Object factory) {
+ final Optional<String> value = findAndRemoveNodeAttribute().map(stringSubstitutionStrategy);
+ if (value.isPresent()) {
+ configurationBinder.bindString(factory, value.get());
+ } else {
+ injectDefaultValue(factory);
+ }
}
- private Object findDefaultValue(final LogEvent event) {
- if (this.conversionType == int.class || this.conversionType == Integer.class) {
- return this.annotation.defaultInt();
+ private void injectDefaultValue(final Object factory) {
+ final Function<PluginAttribute, Object> extractor = DEFAULT_VALUE_EXTRACTORS.get(conversionType);
+ if (extractor != null) {
+ final Object value = extractor.apply(annotation);
+ debugLog(value);
+ configurationBinder.bindObject(factory, value);
+ } else {
+ final String value = stringSubstitutionStrategy.apply(annotation.defaultString());
+ if (Strings.isNotBlank(value)) {
+ debugLog(value);
+ configurationBinder.bindString(factory, value);
+ }
}
- if (this.conversionType == long.class || this.conversionType == Long.class) {
- return this.annotation.defaultLong();
- }
- if (this.conversionType == boolean.class || this.conversionType == Boolean.class) {
- return this.annotation.defaultBoolean();
- }
- if (this.conversionType == float.class || this.conversionType == Float.class) {
- return this.annotation.defaultFloat();
- }
- if (this.conversionType == double.class || this.conversionType == Double.class) {
- return this.annotation.defaultDouble();
- }
- if (this.conversionType == byte.class || this.conversionType == Byte.class) {
- return this.annotation.defaultByte();
- }
- if (this.conversionType == char.class || this.conversionType == Character.class) {
- return this.annotation.defaultChar();
- }
- if (this.conversionType == short.class || this.conversionType == Short.class) {
- return this.annotation.defaultShort();
- }
- if (this.conversionType == Class.class) {
- return this.annotation.defaultClass();
- }
- return this.substitutor.replace(event, this.annotation.defaultString());
+ }
+
+ private void debugLog(final Object value) {
+ final Object debugValue = annotation.sensitive() ? NameUtil.md5(value + getClass().getName()) : value;
+ StringBuilders.appendKeyDqValue(debugLog, name, debugValue);
}
}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginBuilderAttributeVisitor.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginBuilderAttributeVisitor.java
index e951456..dc3aa06 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginBuilderAttributeVisitor.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginBuilderAttributeVisitor.java
@@ -17,39 +17,34 @@
package org.apache.logging.log4j.core.config.plugins.visitors;
-import java.util.Map;
-
-import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.config.Configuration;
-import org.apache.logging.log4j.core.config.Node;
import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
-import org.apache.logging.log4j.core.util.NameUtil;
+import org.apache.logging.log4j.plugins.inject.AbstractConfigurationInjector;
+import org.apache.logging.log4j.util.NameUtil;
import org.apache.logging.log4j.util.StringBuilders;
+import java.util.Optional;
+
/**
- * PluginVisitor for PluginBuilderAttribute. If {@code null} is returned for the
- * {@link #visit(org.apache.logging.log4j.core.config.Configuration, org.apache.logging.log4j.core.config.Node, org.apache.logging.log4j.core.LogEvent, StringBuilder)}
- * method, then the default value of the field should remain untouched.
- *
- * @see org.apache.logging.log4j.core.config.plugins.util.PluginBuilder
+ * @deprecated Provided for support for PluginBuilderAttribute.
*/
-public class PluginBuilderAttributeVisitor extends AbstractPluginVisitor<PluginBuilderAttribute> {
-
- public PluginBuilderAttributeVisitor() {
- super(PluginBuilderAttribute.class);
- }
-
+// copy of PluginBuilderAttributeInjector
+public class PluginBuilderAttributeVisitor extends AbstractConfigurationInjector<PluginBuilderAttribute, Configuration> {
@Override
- public Object visit(final Configuration configuration, final Node node, final LogEvent event,
- final StringBuilder log) {
- final String overridden = this.annotation.value();
- final String name = overridden.isEmpty() ? this.member.getName() : overridden;
- final Map<String, String> attributes = node.getAttributes();
- final String rawValue = removeAttributeValue(attributes, name, this.aliases);
- final String replacedValue = this.substitutor.replace(event, rawValue);
- final Object value = convert(replacedValue, null);
- final Object debugValue = this.annotation.sensitive() ? NameUtil.md5(value + this.getClass().getName()) : value;
- StringBuilders.appendKeyDqValue(log, name, debugValue);
- return value;
+ public void inject(final Object factory) {
+ final Optional<String> value = findAndRemoveNodeAttribute().map(stringSubstitutionStrategy);
+ if (value.isPresent()) {
+ final String str = value.get();
+ debugLog(str);
+ configurationBinder.bindString(factory, str);
+ } else {
+ debugLog.append(name).append("=null");
+ configurationBinder.bindObject(factory, null);
+ }
}
-}
+
+ private void debugLog(final Object value) {
+ final Object debugValue = annotation.sensitive() ? NameUtil.md5(value + getClass().getName()) : value;
+ StringBuilders.appendKeyDqValue(debugLog, name, debugValue);
+ }
+}
\ No newline at end of file
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginConfigurationVisitor.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginConfigurationVisitor.java
deleted file mode 100644
index a5a896f..0000000
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginConfigurationVisitor.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache license, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the license for the specific language governing permissions and
- * limitations under the license.
- */
-
-package org.apache.logging.log4j.core.config.plugins.visitors;
-
-import org.apache.logging.log4j.core.LogEvent;
-import org.apache.logging.log4j.core.config.Configuration;
-import org.apache.logging.log4j.core.config.Node;
-import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
-
-/**
- * PluginVisitor implementation for {@link PluginConfiguration}.
- */
-public class PluginConfigurationVisitor extends AbstractPluginVisitor<PluginConfiguration> {
- public PluginConfigurationVisitor() {
- super(PluginConfiguration.class);
- }
-
- @Override
- public Object visit(final Configuration configuration, final Node node, final LogEvent event,
- final StringBuilder log) {
- if (this.conversionType.isInstance(configuration)) {
- log.append("Configuration");
- if (configuration.getName() != null) {
- log.append('(').append(configuration.getName()).append(')');
- }
- return configuration;
- }
- LOGGER.warn("Variable annotated with @PluginConfiguration is not compatible with type {}.",
- configuration.getClass());
- return null;
- }
-}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginElementVisitor.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginElementVisitor.java
index 95b3ae6..90dc00e 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginElementVisitor.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginElementVisitor.java
@@ -17,96 +17,96 @@
package org.apache.logging.log4j.core.config.plugins.visitors;
+import org.apache.logging.log4j.core.config.Configuration;
+import org.apache.logging.log4j.core.config.plugins.PluginElement;
+import org.apache.logging.log4j.plugins.Node;
+import org.apache.logging.log4j.plugins.inject.AbstractConfigurationInjector;
+import org.apache.logging.log4j.plugins.util.PluginType;
+import org.apache.logging.log4j.plugins.util.TypeUtil;
+
import java.lang.reflect.Array;
+import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
-
-import org.apache.logging.log4j.core.LogEvent;
-import org.apache.logging.log4j.core.config.Configuration;
-import org.apache.logging.log4j.core.config.Node;
-import org.apache.logging.log4j.core.config.plugins.PluginElement;
-import org.apache.logging.log4j.core.config.plugins.util.PluginType;
+import java.util.Optional;
/**
- * PluginVisitor implementation for {@link PluginElement}. Supports arrays as well as singular values.
+ * @deprecated Provided to support legacy plugins.
*/
-public class PluginElementVisitor extends AbstractPluginVisitor<PluginElement> {
- public PluginElementVisitor() {
- super(PluginElement.class);
- }
-
+// copy of PluginElementInjector
+public class PluginElementVisitor extends AbstractConfigurationInjector<PluginElement, Configuration> {
@Override
- public Object visit(final Configuration configuration, final Node node, final LogEvent event,
- final StringBuilder log) {
- final String name = this.annotation.value();
- if (this.conversionType.isArray()) {
- setConversionType(this.conversionType.getComponentType());
+ public void inject(final Object factory) {
+ final Optional<Class<?>> componentType = getComponentType(conversionType);
+ if (componentType.isPresent()) {
+ final Class<?> compType = componentType.get();
final List<Object> values = new ArrayList<>();
final Collection<Node> used = new ArrayList<>();
- log.append("={");
+ debugLog.append("={");
boolean first = true;
for (final Node child : node.getChildren()) {
- final PluginType<?> childType = child.getType();
- if (name.equalsIgnoreCase(childType.getElementName()) ||
- this.conversionType.isAssignableFrom(childType.getPluginClass())) {
+ final PluginType<?> type = child.getType();
+ if (name.equalsIgnoreCase(type.getElementName()) || compType.isAssignableFrom(type.getPluginClass())) {
if (!first) {
- log.append(", ");
+ debugLog.append(", ");
}
first = false;
used.add(child);
final Object childObject = child.getObject();
if (childObject == null) {
- LOGGER.error("Null object returned for {} in {}.", child.getName(), node.getName());
- continue;
+ LOGGER.warn("Skipping null object returned for element {} in node {}", child.getName(), node.getName());
+ } else if (childObject.getClass().isArray()) {
+ Object[] children = (Object[]) childObject;
+ debugLog.append(Arrays.toString(children)).append('}');
+ node.getChildren().removeAll(used);
+ configurationBinder.bindObject(factory, children);
+ return;
+ } else {
+ debugLog.append(child.toString());
+ values.add(childObject);
}
- if (childObject.getClass().isArray()) {
- log.append(Arrays.toString((Object[]) childObject)).append('}');
- return childObject;
- }
- log.append(child.toString());
- values.add(childObject);
}
}
- log.append('}');
- // note that we need to return an empty array instead of null if the types are correct
- if (!values.isEmpty() && !this.conversionType.isAssignableFrom(values.get(0).getClass())) {
- LOGGER.error("Attempted to assign attribute {} to list of type {} which is incompatible with {}.",
- name, values.get(0).getClass(), this.conversionType);
- return null;
+ debugLog.append('}');
+ if (!values.isEmpty() && !TypeUtil.isAssignable(compType, values.get(0).getClass())) {
+ LOGGER.error("Cannot assign element {} a list of {} as it is incompatible with {}", name, values.get(0).getClass(), compType);
+ return;
}
node.getChildren().removeAll(used);
- // we need to use reflection here because values.toArray() will cause type errors at runtime
- final Object[] array = (Object[]) Array.newInstance(this.conversionType, values.size());
- for (int i = 0; i < array.length; i++) {
- array[i] = values.get(i);
+ // using List::toArray here would cause type mismatch later on
+ final Object[] vals = (Object[]) Array.newInstance(compType, values.size());
+ for (int i = 0; i < vals.length; i++) {
+ vals[i] = values.get(i);
}
- return array;
+ configurationBinder.bindObject(factory, vals);
+ } else {
+ final Optional<Node> matchingChild = node.getChildren().stream().filter(this::isRequestedNode).findAny();
+ if (matchingChild.isPresent()) {
+ final Node child = matchingChild.get();
+ debugLog.append(child.getName()).append('(').append(child.toString()).append(')');
+ node.getChildren().remove(child);
+ configurationBinder.bindObject(factory, child.getObject());
+ } else {
+ debugLog.append(name).append("=null");
+ configurationBinder.bindObject(factory, null);
+ }
}
- final Node namedNode = findNamedNode(name, node.getChildren());
- if (namedNode == null) {
- log.append(name).append("=null");
- return null;
- }
- log.append(namedNode.getName()).append('(').append(namedNode.toString()).append(')');
- node.getChildren().remove(namedNode);
- return namedNode.getObject();
}
- private Node findNamedNode(final String name, final Iterable<Node> children) {
- for (final Node child : children) {
- final PluginType<?> childType = child.getType();
- if (childType == null) {
- //System.out.println();
- }
- if (name.equalsIgnoreCase(childType.getElementName()) ||
- this.conversionType.isAssignableFrom(childType.getPluginClass())) {
- // FIXME: check child.getObject() for null?
- // doing so would be more consistent with the array version
- return child;
+ private boolean isRequestedNode(final Node child) {
+ final PluginType<?> type = child.getType();
+ return name.equalsIgnoreCase(type.getElementName()) || TypeUtil.isAssignable(conversionType, type.getPluginClass());
+ }
+
+ private static Optional<Class<?>> getComponentType(final Type type) {
+ if (type instanceof Class<?>) {
+ final Class<?> clazz = (Class<?>) type;
+ if (clazz.isArray()) {
+ return Optional.of(clazz.getComponentType());
}
}
- return null;
+ return Optional.empty();
}
}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginNodeVisitor.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginNodeVisitor.java
index 7f15392..f2f25f0 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginNodeVisitor.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginNodeVisitor.java
@@ -17,27 +17,23 @@
package org.apache.logging.log4j.core.config.plugins.visitors;
-import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.config.Configuration;
-import org.apache.logging.log4j.core.config.Node;
import org.apache.logging.log4j.core.config.plugins.PluginNode;
+import org.apache.logging.log4j.plugins.inject.AbstractConfigurationInjector;
+import org.apache.logging.log4j.plugins.util.TypeUtil;
/**
- * PluginVisitor implementation for {@link PluginNode}.
+ * @deprecated Provided to support legacy plugins.
*/
-public class PluginNodeVisitor extends AbstractPluginVisitor<PluginNode> {
- public PluginNodeVisitor() {
- super(PluginNode.class);
- }
-
+// copy of PluginNodeInjector
+public class PluginNodeVisitor extends AbstractConfigurationInjector<PluginNode, Configuration> {
@Override
- public Object visit(final Configuration configuration, final Node node, final LogEvent event,
- final StringBuilder log) {
- if (this.conversionType.isInstance(node)) {
- log.append("Node=").append(node.getName());
- return node;
+ public void inject(final Object factory) {
+ if (TypeUtil.isAssignable(conversionType, node.getClass())) {
+ debugLog.append("Node=").append(node.getName());
+ configurationBinder.bindObject(factory, node);
+ } else {
+ LOGGER.error("Element with type {} annotated with @PluginNode not compatible with type {}.", conversionType, node.getClass());
}
- LOGGER.warn("Variable annotated with @PluginNode is not compatible with the type {}.", node.getClass());
- return null;
}
}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginValueVisitor.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginValueVisitor.java
index 8544570..c2ba2ab 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginValueVisitor.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginValueVisitor.java
@@ -17,27 +17,21 @@
package org.apache.logging.log4j.core.config.plugins.visitors;
-import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.config.Configuration;
-import org.apache.logging.log4j.core.config.Node;
import org.apache.logging.log4j.core.config.plugins.PluginValue;
+import org.apache.logging.log4j.plugins.inject.AbstractConfigurationInjector;
import org.apache.logging.log4j.util.StringBuilders;
import org.apache.logging.log4j.util.Strings;
/**
- * PluginVisitor implementation for {@link PluginValue}.
+ * @deprecated Provided to support legacy plugins.
*/
-public class PluginValueVisitor extends AbstractPluginVisitor<PluginValue> {
- public PluginValueVisitor() {
- super(PluginValue.class);
- }
-
+// copy of PluginValueInjector
+public class PluginValueVisitor extends AbstractConfigurationInjector<PluginValue, Configuration> {
@Override
- public Object visit(final Configuration configuration, final Node node, final LogEvent event,
- final StringBuilder log) {
- final String name = this.annotation.value();
+ public void inject(final Object factory) {
final String elementValue = node.getValue();
- final String attributeValue = node.getAttributes().get("value");
+ final String attributeValue = node.getAttributes().get(name);
String rawValue = null; // if neither is specified, return null (LOG4J2-1313)
if (Strings.isNotEmpty(elementValue)) {
if (Strings.isNotEmpty(attributeValue)) {
@@ -47,10 +41,10 @@
}
rawValue = elementValue;
} else {
- rawValue = removeAttributeValue(node.getAttributes(), "value");
+ rawValue = findAndRemoveNodeAttribute().orElse(null);
}
- final String value = this.substitutor.replace(event, rawValue);
- StringBuilders.appendKeyDqValue(log, name, value);
- return value;
+ final String value = stringSubstitutionStrategy.apply(rawValue);
+ StringBuilders.appendKeyDqValue(debugLog, name, value);
+ configurationBinder.bindString(factory, value);
}
}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginVisitor.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginVisitor.java
deleted file mode 100644
index 34e2b78..0000000
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginVisitor.java
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache license, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the license for the specific language governing permissions and
- * limitations under the license.
- */
-
-package org.apache.logging.log4j.core.config.plugins.visitors;
-
-import java.lang.annotation.Annotation;
-import java.lang.reflect.Member;
-
-import org.apache.logging.log4j.core.LogEvent;
-import org.apache.logging.log4j.core.config.Configuration;
-import org.apache.logging.log4j.core.config.Node;
-import org.apache.logging.log4j.core.lookup.StrSubstitutor;
-
-/**
- * Visitor strategy for parsing data from a {@link Node}, doing any relevant type conversion, and returning a
- * parsed value for that variable. Implementations must be constructable using the default constructor.
- *
- * @param <A> the Annotation type.
- */
-public interface PluginVisitor<A extends Annotation> {
-
- /**
- * Sets the Annotation to be used for this. If the given Annotation is not compatible with this class's type, then
- * it is ignored.
- *
- * @param annotation the Annotation instance.
- * @return {@code this}.
- * @throws NullPointerException if the argument is {@code null}.
- */
- PluginVisitor<A> setAnnotation(Annotation annotation);
-
- /**
- * Sets the list of aliases to use for this visit. No aliases are required, however.
- *
- * @param aliases the list of aliases to use.
- * @return {@code this}.
- */
- PluginVisitor<A> setAliases(String... aliases);
-
- /**
- * Sets the class to convert the plugin value to on this visit. This should correspond with a class obtained from
- * a factory method or builder class field. Not all PluginVisitor implementations may need this value.
- *
- * @param conversionType the type to convert the plugin string to (if applicable).
- * @return {@code this}.
- * @throws NullPointerException if the argument is {@code null}.
- */
- PluginVisitor<A> setConversionType(Class<?> conversionType);
-
- /**
- * Sets the StrSubstitutor to use for converting raw strings before type conversion. Generally obtained from a
- * {@link org.apache.logging.log4j.core.config.Configuration}.
- *
- * @param substitutor the StrSubstitutor to use on plugin values.
- * @return {@code this}.
- * @throws NullPointerException if the argument is {@code null}.
- */
- PluginVisitor<A> setStrSubstitutor(StrSubstitutor substitutor);
-
- /**
- * Sets the Member that this visitor is being used for injection upon. For instance, this could be the Field
- * that is being used for injecting a value, or it could be the factory method being used to inject parameters
- * into.
- *
- * @param member the member this visitor is parsing a value for.
- * @return {@code this}.
- */
- PluginVisitor<A> setMember(Member member);
-
- /**
- * Visits a Node to obtain a value for constructing a Plugin object.
- *
- * @param configuration the current Configuration.
- * @param node the current Node corresponding to the Plugin object being created.
- * @param event the current LogEvent that caused this Plugin object to be made (optional).
- * @param log the StringBuilder being used to build a debug message.
- * @return the converted value to be used for Plugin creation.
- */
- Object visit(Configuration configuration, Node node, LogEvent event, StringBuilder log);
-}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginVisitors.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginVisitors.java
deleted file mode 100644
index 10ee0df..0000000
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/PluginVisitors.java
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache license, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the license for the specific language governing permissions and
- * limitations under the license.
- */
-
-package org.apache.logging.log4j.core.config.plugins.visitors;
-
-import java.lang.annotation.Annotation;
-
-import org.apache.logging.log4j.Logger;
-import org.apache.logging.log4j.core.config.plugins.PluginVisitorStrategy;
-import org.apache.logging.log4j.status.StatusLogger;
-
-/**
- * Utility class to locate an appropriate {@link PluginVisitor} implementation for an annotation.
- */
-public final class PluginVisitors {
-
- private static final Logger LOGGER = StatusLogger.getLogger();
-
- private PluginVisitors() {
- }
-
- /**
- * Creates a PluginVisitor instance for the given annotation class using metadata provided by the annotation's
- * {@link PluginVisitorStrategy} annotation. This instance must be further populated with
- * data to be useful. Such data is passed through both the setters and the visit method.
- *
- * @param annotation the Plugin annotation class to find a PluginVisitor for.
- * @return a PluginVisitor instance if one could be created, or {@code null} otherwise.
- */
- public static PluginVisitor<? extends Annotation> findVisitor(final Class<? extends Annotation> annotation) {
- final PluginVisitorStrategy strategy = annotation.getAnnotation(PluginVisitorStrategy.class);
- if (strategy == null) {
- return null;
- }
- try {
- return strategy.value().newInstance();
- } catch (final Exception e) {
- LOGGER.error("Error loading PluginVisitor [{}] for annotation [{}].", strategy.value(), annotation, e);
- return null;
- }
- }
-}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/package-info.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/package-info.java
index eae8603..9b747e3 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/package-info.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/plugins/visitors/package-info.java
@@ -16,9 +16,7 @@
*/
/**
- * Visitor classes for extracting values from a Configuration or Node corresponding to a plugin annotation.
- * Visitor implementations must implement {@link org.apache.logging.log4j.core.config.plugins.visitors.PluginVisitor},
- * and the corresponding annotation must be annotated with
- * {@link org.apache.logging.log4j.core.config.plugins.PluginVisitorStrategy}.
+ * Legacy injector strategies for legacy annotations. Plugins should use the updated annotations provided in
+ * {@code org.apache.logging.log4j.plugins}.
*/
package org.apache.logging.log4j.core.config.plugins.visitors;
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/properties/PropertiesConfigurationBuilder.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/properties/PropertiesConfigurationBuilder.java
index 3143259..41d274f 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/properties/PropertiesConfigurationBuilder.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/properties/PropertiesConfigurationBuilder.java
@@ -41,7 +41,7 @@
import org.apache.logging.log4j.core.config.builder.api.ScriptComponentBuilder;
import org.apache.logging.log4j.core.config.builder.api.ScriptFileComponentBuilder;
import org.apache.logging.log4j.core.filter.AbstractFilter.AbstractFilterBuilder;
-import org.apache.logging.log4j.core.util.Builder;
+import org.apache.logging.log4j.plugins.util.Builder;
import org.apache.logging.log4j.util.PropertiesUtil;
import org.apache.logging.log4j.util.Strings;
@@ -228,7 +228,7 @@
private FilterComponentBuilder createFilter(final String key, final Properties properties) {
final String type = (String) properties.remove(CONFIG_TYPE);
if (Strings.isEmpty(type)) {
- throw new ConfigurationException("No type attribute provided for Appender " + key);
+ throw new ConfigurationException("No type attribute provided for Filter " + key);
}
final String onMatch = (String) properties.remove(AbstractFilterBuilder.ATTR_ON_MATCH);
final String onMismatch = (String) properties.remove(AbstractFilterBuilder.ATTR_ON_MISMATCH);
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/properties/PropertiesConfigurationFactory.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/properties/PropertiesConfigurationFactory.java
index d5e1aa9..cb687e3 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/properties/PropertiesConfigurationFactory.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/properties/PropertiesConfigurationFactory.java
@@ -25,7 +25,7 @@
import org.apache.logging.log4j.core.config.ConfigurationFactory;
import org.apache.logging.log4j.core.config.ConfigurationSource;
import org.apache.logging.log4j.core.config.Order;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.plugins.Plugin;
/**
* Creates a PropertiesConfiguration from a properties file.
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/status/StatusConfiguration.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/status/StatusConfiguration.java
index a6f4d1f..6070d30 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/status/StatusConfiguration.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/status/StatusConfiguration.java
@@ -24,8 +24,8 @@
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Collection;
-import java.util.Collections;
-import java.util.LinkedList;
+import java.util.concurrent.LinkedBlockingQueue;
+
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.core.util.FileUtils;
@@ -44,10 +44,10 @@
private static final Level DEFAULT_STATUS = Level.ERROR;
private static final Verbosity DEFAULT_VERBOSITY = Verbosity.QUIET;
- private final Collection<String> errorMessages = Collections.synchronizedCollection(new LinkedList<String>());
+ private final Collection<String> errorMessages = new LinkedBlockingQueue<String>();
private final StatusLogger logger = StatusLogger.getLogger();
- private volatile boolean initialized = false;
+ private volatile boolean initialized;
private PrintStream destination = DEFAULT_STREAM;
private Level status = DEFAULT_STATUS;
@@ -94,7 +94,7 @@
* @param destination where status log messages should be output.
* @return {@code this}
*/
- public StatusConfiguration withDestination(final String destination) {
+ public StatusConfiguration setDestination(final String destination) {
try {
this.destination = parseStreamName(destination);
} catch (final URISyntaxException e) {
@@ -131,7 +131,7 @@
* @return {@code this}
* @see Level
*/
- public StatusConfiguration withStatus(final String status) {
+ public StatusConfiguration setStatus(final String status) {
this.status = Level.toLevel(status, null);
if (this.status == null) {
this.error("Invalid status level specified: " + status + ". Defaulting to ERROR.");
@@ -146,19 +146,19 @@
* @param status logger level to filter below.
* @return {@code this}
*/
- public StatusConfiguration withStatus(final Level status) {
+ public StatusConfiguration setStatus(final Level status) {
this.status = status;
return this;
}
/**
* Specifies the verbosity level to log at. This only applies to classes configured by
- * {@link #withVerboseClasses(String...) verboseClasses}.
+ * {@link #setVerboseClasses(String...) verboseClasses}.
*
* @param verbosity basic filter for status logger messages.
* @return {@code this}
*/
- public StatusConfiguration withVerbosity(final String verbosity) {
+ public StatusConfiguration setVerbosity(final String verbosity) {
this.verbosity = Verbosity.toVerbosity(verbosity);
return this;
}
@@ -169,7 +169,7 @@
* @param verboseClasses names of classes to filter if not using VERBOSE.
* @return {@code this}
*/
- public StatusConfiguration withVerboseClasses(final String... verboseClasses) {
+ public StatusConfiguration setVerboseClasses(final String... verboseClasses) {
this.verboseClasses = verboseClasses;
return this;
}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/xml/XmlConfiguration.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/xml/XmlConfiguration.java
index 07d5740..ca053ce 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/xml/XmlConfiguration.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/xml/XmlConfiguration.java
@@ -28,7 +28,6 @@
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
-import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
@@ -38,17 +37,15 @@
import org.apache.logging.log4j.core.config.AbstractConfiguration;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.ConfigurationSource;
-import org.apache.logging.log4j.core.config.ConfiguratonFileWatcher;
-import org.apache.logging.log4j.core.config.Node;
import org.apache.logging.log4j.core.config.Reconfigurable;
-import org.apache.logging.log4j.core.config.plugins.util.PluginType;
-import org.apache.logging.log4j.core.config.plugins.util.ResolverUtil;
import org.apache.logging.log4j.core.config.status.StatusConfiguration;
import org.apache.logging.log4j.core.util.Closer;
-import org.apache.logging.log4j.core.util.FileWatcher;
import org.apache.logging.log4j.core.util.Loader;
import org.apache.logging.log4j.core.util.Patterns;
import org.apache.logging.log4j.core.util.Throwables;
+import org.apache.logging.log4j.plugins.Node;
+import org.apache.logging.log4j.plugins.util.PluginType;
+import org.apache.logging.log4j.plugins.util.ResolverUtil;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
@@ -108,21 +105,22 @@
}
rootElement = document.getDocumentElement();
final Map<String, String> attrs = processAttributes(rootNode, rootElement);
- final StatusConfiguration statusConfig = new StatusConfiguration().withVerboseClasses(VERBOSE_CLASSES)
- .withStatus(getDefaultStatus());
+ final StatusConfiguration statusConfig = new StatusConfiguration().setVerboseClasses(VERBOSE_CLASSES)
+ .setStatus(getDefaultStatus());
+ int monitorIntervalSeconds = 0;
for (final Map.Entry<String, String> entry : attrs.entrySet()) {
final String key = entry.getKey();
final String value = getStrSubstitutor().replace(entry.getValue());
if ("status".equalsIgnoreCase(key)) {
- statusConfig.withStatus(value);
+ statusConfig.setStatus(value);
} else if ("dest".equalsIgnoreCase(key)) {
- statusConfig.withDestination(value);
+ statusConfig.setDestination(value);
} else if ("shutdownHook".equalsIgnoreCase(key)) {
isShutdownHookEnabled = !"disable".equalsIgnoreCase(value);
} else if ("shutdownTimeout".equalsIgnoreCase(key)) {
shutdownTimeoutMillis = Long.parseLong(value);
} else if ("verbose".equalsIgnoreCase(key)) {
- statusConfig.withVerbosity(value);
+ statusConfig.setVerbosity(value);
} else if ("packages".equalsIgnoreCase(key)) {
pluginPackages.addAll(Arrays.asList(value.split(Patterns.COMMA_SEPARATOR)));
} else if ("name".equalsIgnoreCase(key)) {
@@ -132,18 +130,12 @@
} else if ("schema".equalsIgnoreCase(key)) {
schemaResource = value;
} else if ("monitorInterval".equalsIgnoreCase(key)) {
- final int intervalSeconds = Integer.parseInt(value);
- if (intervalSeconds > 0) {
- getWatchManager().setIntervalSeconds(intervalSeconds);
- if (configFile != null) {
- final FileWatcher watcher = new ConfiguratonFileWatcher(this, listeners);
- getWatchManager().watchFile(configFile, watcher);
- }
- }
+ monitorIntervalSeconds = Integer.parseInt(value);
} else if ("advertiser".equalsIgnoreCase(key)) {
createAdvertiser(value, configSource, buffer, "text/xml");
}
}
+ initializeWatchers(this, configSource, monitorIntervalSeconds);
statusConfig.initialize();
} catch (final SAXException | IOException | ParserConfigurationException e) {
LOGGER.error("Error parsing " + configSource.getLocation(), e);
@@ -151,7 +143,7 @@
if (strict && schemaResource != null && buffer != null) {
try (InputStream is = Loader.getResourceAsStream(schemaResource, XmlConfiguration.class.getClassLoader())) {
if (is != null) {
- final Source src = new StreamSource(is, LOG4J_XSD);
+ final javax.xml.transform.Source src = new StreamSource(is, LOG4J_XSD);
final SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
Schema schema = null;
try {
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/xml/XmlConfigurationFactory.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/xml/XmlConfigurationFactory.java
index a37718f..29f21c2 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/xml/XmlConfigurationFactory.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/xml/XmlConfigurationFactory.java
@@ -21,7 +21,7 @@
import org.apache.logging.log4j.core.config.ConfigurationFactory;
import org.apache.logging.log4j.core.config.ConfigurationSource;
import org.apache.logging.log4j.core.config.Order;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.plugins.Plugin;
/**
* Factory to construct an XmlConfiguration.
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/yaml/YamlConfigurationFactory.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/yaml/YamlConfigurationFactory.java
index c8f4560..cc25ce4 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/config/yaml/YamlConfigurationFactory.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/config/yaml/YamlConfigurationFactory.java
@@ -21,7 +21,7 @@
import org.apache.logging.log4j.core.config.ConfigurationFactory;
import org.apache.logging.log4j.core.config.ConfigurationSource;
import org.apache.logging.log4j.core.config.Order;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.plugins.Plugin;
import org.apache.logging.log4j.core.util.Loader;
@Plugin(name = "YamlConfigurationFactory", category = ConfigurationFactory.CATEGORY)
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/AbstractFilter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/AbstractFilter.java
index 5e3c133..0df9957 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/AbstractFilter.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/AbstractFilter.java
@@ -22,7 +22,7 @@
import org.apache.logging.log4j.core.Filter;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.Logger;
-import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
+import org.apache.logging.log4j.plugins.PluginBuilderAttribute;
import org.apache.logging.log4j.message.Message;
/**
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/AbstractFilterable.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/AbstractFilterable.java
index 3bd20ce..e8f68cd 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/AbstractFilterable.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/AbstractFilterable.java
@@ -21,9 +21,11 @@
import org.apache.logging.log4j.core.AbstractLifeCycle;
import org.apache.logging.log4j.core.Filter;
+import org.apache.logging.log4j.core.LifeCycle;
import org.apache.logging.log4j.core.LifeCycle2;
import org.apache.logging.log4j.core.LogEvent;
-import org.apache.logging.log4j.core.config.plugins.PluginElement;
+import org.apache.logging.log4j.core.config.Property;
+import org.apache.logging.log4j.plugins.PluginElement;
/**
* Enhances a Class by allowing it to contain Filters.
@@ -40,41 +42,48 @@
@PluginElement("Filter")
private Filter filter;
- public Filter getFilter() {
- return filter;
- }
+ @PluginElement("Properties")
+ private Property[] propertyArray;
@SuppressWarnings("unchecked")
public B asBuilder() {
return (B) this;
}
- public B withFilter(final Filter filter) {
+ public Filter getFilter() {
+ return filter;
+ }
+
+ public Property[] getPropertyArray() {
+ return propertyArray;
+ }
+
+ public B setFilter(final Filter filter) {
this.filter = filter;
return asBuilder();
}
+ public B setPropertyArray(final Property[] properties) {
+ this.propertyArray = properties;
+ return asBuilder();
+ }
+
}
/**
* May be null.
*/
private volatile Filter filter;
-
- protected AbstractFilterable(final Filter filter) {
- this.filter = filter;
- }
+
+ private final Property[] propertyArray;
protected AbstractFilterable() {
+ this(null, null);
}
- /**
- * Returns the Filter.
- * @return the Filter or null.
- */
- @Override
- public Filter getFilter() {
- return filter;
+ protected AbstractFilterable(final Filter filter, Property[] propertyArray) {
+ this.filter = filter;
+ this.propertyArray = propertyArray == null ? Property.EMPTY_ARRAY : propertyArray;
}
/**
@@ -97,6 +106,38 @@
}
/**
+ * Returns the Filter.
+ * @return the Filter or null.
+ */
+ @Override
+ public Filter getFilter() {
+ return filter;
+ }
+
+ public Property[] getPropertyArray() {
+ return propertyArray;
+ }
+
+ /**
+ * Determines if a Filter is present.
+ * @return false if no Filter is present.
+ */
+ @Override
+ public boolean hasFilter() {
+ return filter != null;
+ }
+
+ /**
+ * Determine if the LogEvent should be processed or ignored.
+ * @param event The LogEvent.
+ * @return true if the LogEvent should be processed.
+ */
+ @Override
+ public boolean isFiltered(final LogEvent event) {
+ return filter != null && filter.filter(event) == Filter.Result.DENY;
+ }
+
+ /**
* Removes a Filter.
* @param filter The Filter to remove.
*/
@@ -122,15 +163,6 @@
}
/**
- * Determines if a Filter is present.
- * @return false if no Filter is present.
- */
- @Override
- public boolean hasFilter() {
- return filter != null;
- }
-
- /**
* Make the Filter available for use.
*/
@Override
@@ -159,12 +191,7 @@
}
boolean stopped = true;
if (filter != null) {
- if (filter instanceof LifeCycle2) {
- stopped = ((LifeCycle2) filter).stop(timeout, timeUnit);
- } else {
- filter.stop();
- stopped = true;
- }
+ stopped = ((LifeCycle) filter).stop(timeout, timeUnit);
}
if (changeLifeCycleState) {
this.setStopped();
@@ -172,14 +199,4 @@
return stopped;
}
- /**
- * Determine if the LogEvent should be processed or ignored.
- * @param event The LogEvent.
- * @return true if the LogEvent should be processed.
- */
- @Override
- public boolean isFiltered(final LogEvent event) {
- return filter != null && filter.filter(event) == Filter.Result.DENY;
- }
-
}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/BurstFilter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/BurstFilter.java
index c2326ad..b815ee4 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/BurstFilter.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/BurstFilter.java
@@ -17,22 +17,22 @@
package org.apache.logging.log4j.core.filter;
-import java.util.Queue;
-import java.util.concurrent.ConcurrentLinkedQueue;
-import java.util.concurrent.DelayQueue;
-import java.util.concurrent.Delayed;
-import java.util.concurrent.TimeUnit;
-
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.Marker;
import org.apache.logging.log4j.core.Filter;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.Logger;
-import org.apache.logging.log4j.core.config.Node;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
-import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
import org.apache.logging.log4j.message.Message;
+import org.apache.logging.log4j.plugins.Node;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginBuilderAttribute;
+import org.apache.logging.log4j.plugins.PluginFactory;
+
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.DelayQueue;
+import java.util.concurrent.Delayed;
+import java.util.concurrent.TimeUnit;
/**
* The <code>BurstFilter</code> is a logging filter that regulates logging traffic.
@@ -285,12 +285,12 @@
}
}
- @PluginBuilderFactory
+ @PluginFactory
public static Builder newBuilder() {
return new Builder();
}
- public static class Builder extends AbstractFilterBuilder<Builder> implements org.apache.logging.log4j.core.util.Builder<BurstFilter> {
+ public static class Builder extends AbstractFilterBuilder<Builder> implements org.apache.logging.log4j.plugins.util.Builder<BurstFilter> {
@PluginBuilderAttribute
private Level level = Level.WARN;
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/CompositeFilter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/CompositeFilter.java
index 5394bfb..fe40d74 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/CompositeFilter.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/CompositeFilter.java
@@ -26,13 +26,14 @@
import org.apache.logging.log4j.Marker;
import org.apache.logging.log4j.core.AbstractLifeCycle;
import org.apache.logging.log4j.core.Filter;
+import org.apache.logging.log4j.core.LifeCycle;
import org.apache.logging.log4j.core.LifeCycle2;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.Logger;
-import org.apache.logging.log4j.core.config.Node;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginElement;
-import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+import org.apache.logging.log4j.plugins.Node;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginElement;
+import org.apache.logging.log4j.plugins.PluginFactory;
import org.apache.logging.log4j.core.util.ObjectArrayIterator;
import org.apache.logging.log4j.message.Message;
import org.apache.logging.log4j.util.PerformanceSensitive;
@@ -98,17 +99,6 @@
return new ObjectArrayIterator<>(filters);
}
- /**
- * Gets a new list over the internal filter array.
- *
- * @return a new list over the internal filter array
- * @deprecated Use {@link #getFiltersArray()}
- */
- @Deprecated
- public List<Filter> getFilters() {
- return Arrays.asList(filters);
- }
-
public Filter[] getFiltersArray() {
return filters;
}
@@ -139,11 +129,7 @@
public boolean stop(final long timeout, final TimeUnit timeUnit) {
this.setStopping();
for (final Filter filter : filters) {
- if (filter instanceof LifeCycle2) {
- ((LifeCycle2) filter).stop(timeout, timeUnit);
- } else {
- filter.stop();
- }
+ ((LifeCycle) filter).stop(timeout, timeUnit);
}
setStopped();
return true;
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/DenyAllFilter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/DenyAllFilter.java
new file mode 100644
index 0000000..dbdc550
--- /dev/null
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/DenyAllFilter.java
@@ -0,0 +1,156 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.logging.log4j.core.filter;
+
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.Marker;
+import org.apache.logging.log4j.core.Filter;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.Logger;
+import org.apache.logging.log4j.message.Message;
+import org.apache.logging.log4j.plugins.Node;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginFactory;
+
+import org.apache.logging.log4j.util.PerformanceSensitive;
+
+/**
+ * This filter causes all logging events to be dropped.
+ */
+@Plugin(name = "DenyAllFilter", category = Node.CATEGORY, elementType = Filter.ELEMENT_TYPE, printObject = true)
+@PerformanceSensitive("allocation")
+public final class DenyAllFilter extends AbstractFilter {
+
+ private DenyAllFilter(final Result onMatch, final Result onMismatch) {
+ super(onMatch, onMismatch);
+ }
+
+ @Override
+ public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
+ final Object... params) {
+ return Result.DENY;
+ }
+
+ @Override
+ public Result filter(final Logger logger, final Level level, final Marker marker, final Object msg,
+ final Throwable t) {
+ return Result.DENY;
+ }
+
+ @Override
+ public Result filter(final Logger logger, final Level level, final Marker marker, final Message msg,
+ final Throwable t) {
+ return Result.DENY;
+ }
+
+ @Override
+ public Result filter(final LogEvent event) {
+ return Result.DENY;
+ }
+
+ private Result filter(final Marker marker) {
+ return Result.DENY;
+ }
+
+ @Override
+ public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
+ final Object p0) {
+ return Result.DENY;
+ }
+
+ @Override
+ public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
+ final Object p0, final Object p1) {
+ return Result.DENY;
+ }
+
+ @Override
+ public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
+ final Object p0, final Object p1, final Object p2) {
+ return filter(marker);
+ }
+
+ @Override
+ public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
+ final Object p0, final Object p1, final Object p2, final Object p3) {
+ return Result.DENY;
+ }
+
+ @Override
+ public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
+ final Object p0, final Object p1, final Object p2, final Object p3,
+ final Object p4) {
+ return Result.DENY;
+ }
+
+ @Override
+ public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
+ final Object p0, final Object p1, final Object p2, final Object p3,
+ final Object p4, final Object p5) {
+ return Result.DENY;
+ }
+
+ @Override
+ public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
+ final Object p0, final Object p1, final Object p2, final Object p3,
+ final Object p4, final Object p5, final Object p6) {
+ return Result.DENY;
+ }
+
+ @Override
+ public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
+ final Object p0, final Object p1, final Object p2, final Object p3,
+ final Object p4, final Object p5, final Object p6,
+ final Object p7) {
+ return Result.DENY;
+ }
+
+ @Override
+ public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
+ final Object p0, final Object p1, final Object p2, final Object p3,
+ final Object p4, final Object p5, final Object p6,
+ final Object p7, final Object p8) {
+ return Result.DENY;
+ }
+
+ @Override
+ public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
+ final Object p0, final Object p1, final Object p2, final Object p3,
+ final Object p4, final Object p5, final Object p6,
+ final Object p7, final Object p8, final Object p9) {
+ return Result.DENY;
+ }
+
+ @Override
+ public String toString() {
+ return "DenyAll";
+ }
+
+ @PluginFactory
+ public static DenyAllFilter.Builder newBuilder() {
+ return new DenyAllFilter.Builder();
+ }
+
+ public static class Builder extends AbstractFilterBuilder<DenyAllFilter.Builder> implements org.apache.logging.log4j.core.util.Builder<DenyAllFilter> {
+
+ @Override
+ public DenyAllFilter build() {
+ return new DenyAllFilter(this.getOnMatch(), this.getOnMismatch());
+ }
+ }
+
+}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/DynamicThresholdFilter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/DynamicThresholdFilter.java
index 923a39c..40738d3 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/DynamicThresholdFilter.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/DynamicThresholdFilter.java
@@ -26,11 +26,11 @@
import org.apache.logging.log4j.core.Filter;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.Logger;
-import org.apache.logging.log4j.core.config.Node;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
-import org.apache.logging.log4j.core.config.plugins.PluginElement;
-import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+import org.apache.logging.log4j.plugins.Node;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginAttribute;
+import org.apache.logging.log4j.plugins.PluginElement;
+import org.apache.logging.log4j.plugins.PluginFactory;
import org.apache.logging.log4j.core.ContextDataInjector;
import org.apache.logging.log4j.core.impl.ContextDataInjectorFactory;
import org.apache.logging.log4j.core.util.KeyValuePair;
@@ -59,11 +59,11 @@
// TODO Consider refactoring to use AbstractFilter.AbstractFilterBuilder
@PluginFactory
public static DynamicThresholdFilter createFilter(
- @PluginAttribute("key") final String key,
- @PluginElement("Pairs") final KeyValuePair[] pairs,
- @PluginAttribute("defaultThreshold") final Level defaultThreshold,
- @PluginAttribute("onMatch") final Result onMatch,
- @PluginAttribute("onMismatch") final Result onMismatch) {
+ @PluginAttribute final String key,
+ @PluginElement final KeyValuePair[] pairs,
+ @PluginAttribute final Level defaultThreshold,
+ @PluginAttribute final Result onMatch,
+ @PluginAttribute final Result onMismatch) {
final Map<String, Level> map = new HashMap<>();
for (final KeyValuePair pair : pairs) {
map.put(pair.getKey(), Level.toLevel(pair.getValue()));
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/LevelMatchFilter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/LevelMatchFilter.java
new file mode 100644
index 0000000..cafbac1
--- /dev/null
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/LevelMatchFilter.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.logging.log4j.core.filter;
+
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.Marker;
+import org.apache.logging.log4j.core.Filter;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.Logger;
+import org.apache.logging.log4j.message.Message;
+import org.apache.logging.log4j.plugins.Node;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginBuilderAttribute;
+import org.apache.logging.log4j.plugins.PluginFactory;
+import org.apache.logging.log4j.util.PerformanceSensitive;
+
+/**
+ * This filter returns the onMatch result if the logging level in the event matches the specified logging level
+ * exactly.
+ */
+@Plugin(name = "LevelMatchFilter", category = Node.CATEGORY, elementType = Filter.ELEMENT_TYPE, printObject = true)
+@PerformanceSensitive("allocation")
+public final class LevelMatchFilter extends AbstractFilter {
+
+ public static final String ATTR_MATCH = "match";
+ private final Level level;
+
+ private LevelMatchFilter(final Level level, final Result onMatch, final Result onMismatch) {
+ super(onMatch, onMismatch);
+ this.level = level;
+ }
+
+ @Override
+ public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
+ final Object... params) {
+ return filter(level);
+ }
+
+ @Override
+ public Result filter(final Logger logger, final Level level, final Marker marker, final Object msg,
+ final Throwable t) {
+ return filter(level);
+ }
+
+ @Override
+ public Result filter(final Logger logger, final Level level, final Marker marker, final Message msg,
+ final Throwable t) {
+ return filter(level);
+ }
+
+ @Override
+ public Result filter(final LogEvent event) {
+ return filter(event.getLevel());
+ }
+
+ private Result filter(final Level level) {
+ return level == this.level ? onMatch : onMismatch;
+ }
+
+ @Override
+ public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
+ final Object p0) {
+ return filter(level);
+ }
+
+ @Override
+ public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
+ final Object p0, final Object p1) {
+ return filter(level);
+ }
+
+ @Override
+ public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
+ final Object p0, final Object p1, final Object p2) {
+ return filter(level);
+ }
+
+ @Override
+ public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
+ final Object p0, final Object p1, final Object p2, final Object p3) {
+ return filter(level);
+ }
+
+ @Override
+ public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
+ final Object p0, final Object p1, final Object p2, final Object p3,
+ final Object p4) {
+ return filter(level);
+ }
+
+ @Override
+ public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
+ final Object p0, final Object p1, final Object p2, final Object p3,
+ final Object p4, final Object p5) {
+ return filter(level);
+ }
+
+ @Override
+ public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
+ final Object p0, final Object p1, final Object p2, final Object p3,
+ final Object p4, final Object p5, final Object p6) {
+ return filter(level);
+ }
+
+ @Override
+ public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
+ final Object p0, final Object p1, final Object p2, final Object p3,
+ final Object p4, final Object p5, final Object p6,
+ final Object p7) {
+ return filter(level);
+ }
+
+ @Override
+ public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
+ final Object p0, final Object p1, final Object p2, final Object p3,
+ final Object p4, final Object p5, final Object p6,
+ final Object p7, final Object p8) {
+ return filter(level);
+ }
+
+ @Override
+ public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
+ final Object p0, final Object p1, final Object p2, final Object p3,
+ final Object p4, final Object p5, final Object p6,
+ final Object p7, final Object p8, final Object p9) {
+ return filter(level);
+ }
+
+ @Override
+ public String toString() {
+ return level.toString();
+ }
+
+ @PluginFactory
+ public static LevelMatchFilter.Builder newBuilder() {
+ return new LevelMatchFilter.Builder();
+ }
+
+ public static class Builder extends AbstractFilterBuilder<LevelMatchFilter.Builder> implements org.apache.logging.log4j.core.util.Builder<LevelMatchFilter> {
+ @PluginBuilderAttribute
+ private Level level = Level.ERROR;
+
+ /**
+ * Sets the logging level to use.
+ * @param level the logging level to use.
+ * @return this
+ */
+ public LevelMatchFilter.Builder setLevel(final Level level) {
+ this.level = level;
+ return this;
+ }
+
+ @Override
+ public LevelMatchFilter build() {
+ return new LevelMatchFilter(this.level, this.getOnMatch(), this.getOnMismatch());
+ }
+ }
+
+}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/LevelRangeFilter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/LevelRangeFilter.java
index 0e79b85..99d80a0 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/LevelRangeFilter.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/LevelRangeFilter.java
@@ -21,10 +21,10 @@
import org.apache.logging.log4j.core.Filter;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.Logger;
-import org.apache.logging.log4j.core.config.Node;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
-import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+import org.apache.logging.log4j.plugins.Node;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginAttribute;
+import org.apache.logging.log4j.plugins.PluginFactory;
import org.apache.logging.log4j.message.Message;
import org.apache.logging.log4j.util.PerformanceSensitive;
@@ -59,10 +59,10 @@
@PluginFactory
public static LevelRangeFilter createFilter(
// @formatter:off
- @PluginAttribute("minLevel") final Level minLevel,
- @PluginAttribute("maxLevel") final Level maxLevel,
- @PluginAttribute("onMatch") final Result match,
- @PluginAttribute("onMismatch") final Result mismatch) {
+ @PluginAttribute final Level minLevel,
+ @PluginAttribute final Level maxLevel,
+ @PluginAttribute final Result match,
+ @PluginAttribute final Result mismatch) {
// @formatter:on
final Level actualMinLevel = minLevel == null ? Level.ERROR : minLevel;
final Level actualMaxLevel = maxLevel == null ? Level.ERROR : maxLevel;
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/MapFilter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/MapFilter.java
index 39f3f14..b5e9ba0 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/MapFilter.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/MapFilter.java
@@ -27,11 +27,11 @@
import org.apache.logging.log4j.core.Filter;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.Logger;
-import org.apache.logging.log4j.core.config.Node;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
-import org.apache.logging.log4j.core.config.plugins.PluginElement;
-import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+import org.apache.logging.log4j.plugins.Node;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginAttribute;
+import org.apache.logging.log4j.plugins.PluginElement;
+import org.apache.logging.log4j.plugins.PluginFactory;
import org.apache.logging.log4j.core.util.KeyValuePair;
import org.apache.logging.log4j.message.MapMessage;
import org.apache.logging.log4j.message.Message;
@@ -212,19 +212,6 @@
return isAnd;
}
- /** @deprecated use {@link #getStringMap()} instead */
- @Deprecated
- protected Map<String, List<String>> getMap() {
- final Map<String, List<String>> result = new HashMap<>(map.size());
- map.forEach(new BiConsumer<String, List<String>>() {
- @Override
- public void accept(final String key, final List<String> value) {
- result.put(key, value);
- }
- });
- return result;
- }
-
/**
* Returns the IndexedStringMap with {@code List<String>} values that this MapFilter was constructed with.
* @return the IndexedStringMap with {@code List<String>} values to match against
@@ -237,10 +224,10 @@
// TODO Consider refactoring to use AbstractFilter.AbstractFilterBuilder
@PluginFactory
public static MapFilter createFilter(
- @PluginElement("Pairs") final KeyValuePair[] pairs,
- @PluginAttribute("operator") final String oper,
- @PluginAttribute("onMatch") final Result match,
- @PluginAttribute("onMismatch") final Result mismatch) {
+ @PluginElement final KeyValuePair[] pairs,
+ @PluginAttribute final String operator,
+ @PluginAttribute final Result onMatch,
+ @PluginAttribute final Result onMismatch) {
if (pairs == null || pairs.length == 0) {
LOGGER.error("keys and values must be specified for the MapFilter");
return null;
@@ -270,7 +257,7 @@
LOGGER.error("MapFilter is not configured with any valid key value pairs");
return null;
}
- final boolean isAnd = oper == null || !oper.equalsIgnoreCase("or");
- return new MapFilter(map, isAnd, match, mismatch);
+ final boolean isAnd = operator == null || !operator.equalsIgnoreCase("or");
+ return new MapFilter(map, isAnd, onMatch, onMismatch);
}
}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/MarkerFilter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/MarkerFilter.java
index b828e31..d83eeae 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/MarkerFilter.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/MarkerFilter.java
@@ -21,10 +21,10 @@
import org.apache.logging.log4j.core.Filter;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.Logger;
-import org.apache.logging.log4j.core.config.Node;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
-import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+import org.apache.logging.log4j.plugins.Node;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginAttribute;
+import org.apache.logging.log4j.plugins.PluginFactory;
import org.apache.logging.log4j.message.Message;
import org.apache.logging.log4j.util.PerformanceSensitive;
@@ -36,7 +36,6 @@
@PerformanceSensitive("allocation")
public final class MarkerFilter extends AbstractFilter {
- public static final String ATTR_MARKER = "marker";
private final String name;
private MarkerFilter(final String name, final Result onMatch, final Result onMismatch) {
@@ -148,22 +147,22 @@
/**
* Creates the MarkerFilter.
* @param marker The Marker name to match.
- * @param match The action to take if a match occurs.
- * @param mismatch The action to take if no match occurs.
+ * @param onMatch The action to take if a match occurs.
+ * @param onMismatch The action to take if no match occurs.
* @return A MarkerFilter.
*/
// TODO Consider refactoring to use AbstractFilter.AbstractFilterBuilder
@PluginFactory
public static MarkerFilter createFilter(
- @PluginAttribute(ATTR_MARKER) final String marker,
- @PluginAttribute(AbstractFilterBuilder.ATTR_ON_MATCH) final Result match,
- @PluginAttribute(AbstractFilterBuilder.ATTR_ON_MISMATCH) final Result mismatch) {
+ @PluginAttribute final String marker,
+ @PluginAttribute final Result onMatch,
+ @PluginAttribute final Result onMismatch) {
if (marker == null) {
LOGGER.error("A marker must be provided for MarkerFilter");
return null;
}
- return new MarkerFilter(marker, match, mismatch);
+ return new MarkerFilter(marker, onMatch, onMismatch);
}
}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/NeutralFilter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/NeutralFilter.java
new file mode 100644
index 0000000..eac45dc
--- /dev/null
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/NeutralFilter.java
@@ -0,0 +1,110 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+
+package org.apache.logging.log4j.core.filter;
+
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.Marker;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.Logger;
+import org.apache.logging.log4j.message.Message;
+
+/**
+ * A neutral filter where all filter methods return {@link org.apache.logging.log4j.core.Filter.Result#NEUTRAL}.
+ */
+public class NeutralFilter extends AbstractFilter {
+
+ public static final NeutralFilter INSTANCE = new NeutralFilter();
+
+ @Override
+ public Result filter(LogEvent event) {
+ return Result.NEUTRAL;
+ }
+
+ @Override
+ public Result filter(Logger logger, Level level, Marker marker, Message msg, Throwable t) {
+ return Result.NEUTRAL;
+ }
+
+ @Override
+ public Result filter(Logger logger, Level level, Marker marker, Object msg, Throwable t) {
+ return Result.NEUTRAL;
+ }
+
+ @Override
+ public Result filter(Logger logger, Level level, Marker marker, String msg, Object... params) {
+ return Result.NEUTRAL;
+ }
+
+ @Override
+ public Result filter(Logger logger, Level level, Marker marker, String msg, Object p0) {
+ return Result.NEUTRAL;
+ }
+
+ @Override
+ public Result filter(Logger logger, Level level, Marker marker, String msg, Object p0, Object p1) {
+ return Result.NEUTRAL;
+ }
+
+ @Override
+ public Result filter(Logger logger, Level level, Marker marker, String msg, Object p0, Object p1, Object p2) {
+ return Result.NEUTRAL;
+ }
+
+ @Override
+ public Result filter(Logger logger, Level level, Marker marker, String msg, Object p0, Object p1, Object p2,
+ Object p3) {
+ return Result.NEUTRAL;
+ }
+
+ @Override
+ public Result filter(Logger logger, Level level, Marker marker, String msg, Object p0, Object p1, Object p2,
+ Object p3, Object p4) {
+ return Result.NEUTRAL;
+ }
+
+ @Override
+ public Result filter(Logger logger, Level level, Marker marker, String msg, Object p0, Object p1, Object p2,
+ Object p3, Object p4, Object p5) {
+ return Result.NEUTRAL;
+ }
+
+ @Override
+ public Result filter(Logger logger, Level level, Marker marker, String msg, Object p0, Object p1, Object p2,
+ Object p3, Object p4, Object p5, Object p6) {
+ return Result.NEUTRAL;
+ }
+
+ @Override
+ public Result filter(Logger logger, Level level, Marker marker, String msg, Object p0, Object p1, Object p2,
+ Object p3, Object p4, Object p5, Object p6, Object p7) {
+ return Result.NEUTRAL;
+ }
+
+ @Override
+ public Result filter(Logger logger, Level level, Marker marker, String msg, Object p0, Object p1, Object p2,
+ Object p3, Object p4, Object p5, Object p6, Object p7, Object p8) {
+ return Result.NEUTRAL;
+ }
+
+ @Override
+ public Result filter(Logger logger, Level level, Marker marker, String msg, Object p0, Object p1, Object p2,
+ Object p3, Object p4, Object p5, Object p6, Object p7, Object p8, Object p9) {
+ return Result.NEUTRAL;
+ }
+
+}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/NoMarkerFilter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/NoMarkerFilter.java
new file mode 100644
index 0000000..92fcf63
--- /dev/null
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/NoMarkerFilter.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.filter;
+
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.Marker;
+import org.apache.logging.log4j.core.Filter;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.Logger;
+import org.apache.logging.log4j.message.Message;
+import org.apache.logging.log4j.plugins.Node;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginFactory;
+import org.apache.logging.log4j.util.PerformanceSensitive;
+
+/**
+ * This filter returns the onMatch result if there is no marker in the LogEvent.
+ */
+@Plugin(name = "NoMarkerFilter", category = Node.CATEGORY, elementType = Filter.ELEMENT_TYPE, printObject = true)
+@PerformanceSensitive("allocation")
+public final class NoMarkerFilter extends AbstractFilter {
+
+ private NoMarkerFilter(final Result onMatch, final Result onMismatch) {
+ super(onMatch, onMismatch);
+ }
+
+ @Override
+ public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
+ final Object... params) {
+ return filter(marker);
+ }
+
+ @Override
+ public Result filter(final Logger logger, final Level level, final Marker marker, final Object msg,
+ final Throwable t) {
+ return filter(marker);
+ }
+
+ @Override
+ public Result filter(final Logger logger, final Level level, final Marker marker, final Message msg,
+ final Throwable t) {
+ return filter(marker);
+ }
+
+ @Override
+ public Result filter(final LogEvent event) {
+ return filter(event.getMarker());
+ }
+
+ @Override
+ public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
+ final Object p0) {
+ return filter(marker);
+ }
+
+ @Override
+ public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
+ final Object p0, final Object p1) {
+ return filter(marker);
+ }
+
+ @Override
+ public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
+ final Object p0, final Object p1, final Object p2) {
+ return filter(marker);
+ }
+
+ @Override
+ public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
+ final Object p0, final Object p1, final Object p2, final Object p3) {
+ return filter(marker);
+ }
+
+ @Override
+ public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
+ final Object p0, final Object p1, final Object p2, final Object p3,
+ final Object p4) {
+ return filter(marker);
+ }
+
+ @Override
+ public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
+ final Object p0, final Object p1, final Object p2, final Object p3,
+ final Object p4, final Object p5) {
+ return filter(marker);
+ }
+
+ @Override
+ public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
+ final Object p0, final Object p1, final Object p2, final Object p3,
+ final Object p4, final Object p5, final Object p6) {
+ return filter(marker);
+ }
+
+ @Override
+ public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
+ final Object p0, final Object p1, final Object p2, final Object p3,
+ final Object p4, final Object p5, final Object p6,
+ final Object p7) {
+ return filter(marker);
+ }
+
+ @Override
+ public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
+ final Object p0, final Object p1, final Object p2, final Object p3,
+ final Object p4, final Object p5, final Object p6,
+ final Object p7, final Object p8) {
+ return filter(marker);
+ }
+
+ @Override
+ public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
+ final Object p0, final Object p1, final Object p2, final Object p3,
+ final Object p4, final Object p5, final Object p6,
+ final Object p7, final Object p8, final Object p9) {
+ return filter(marker);
+ }
+
+ private Result filter(final Marker marker) {
+ return null == marker ? onMatch : onMismatch;
+ }
+
+
+ @PluginFactory
+ public static Builder newBuilder() {
+ return new Builder();
+ }
+
+ public static class Builder extends AbstractFilterBuilder<Builder> implements org.apache.logging.log4j.core.util.Builder<NoMarkerFilter> {
+
+ @Override
+ public NoMarkerFilter build() {
+ return new NoMarkerFilter(this.getOnMatch(), this.getOnMismatch());
+ }
+ }
+}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/RegexFilter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/RegexFilter.java
index 6b18b6b..f5c87ac 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/RegexFilter.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/RegexFilter.java
@@ -27,11 +27,11 @@
import org.apache.logging.log4j.core.Filter;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.Logger;
-import org.apache.logging.log4j.core.config.Node;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
-import org.apache.logging.log4j.core.config.plugins.PluginElement;
-import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+import org.apache.logging.log4j.plugins.Node;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginAttribute;
+import org.apache.logging.log4j.plugins.PluginElement;
+import org.apache.logging.log4j.plugins.PluginFactory;
import org.apache.logging.log4j.message.Message;
/**
@@ -107,12 +107,12 @@
* @param regex
* The regular expression to match.
* @param patternFlags
- * An array of Stirngs where each String is a {@link Pattern#compile(String, int)} compilation flag.
+ * An array of Strings where each String is a {@link Pattern#compile(String, int)} compilation flag.
* @param useRawMsg
* If true, the raw message will be used, otherwise the formatted message will be used.
- * @param match
+ * @param onMatch
* The action to perform when a match occurs.
- * @param mismatch
+ * @param onMismatch
* The action to perform when a mismatch occurs.
* @return The RegexFilter.
* @throws IllegalAccessException
@@ -122,18 +122,18 @@
@PluginFactory
public static RegexFilter createFilter(
//@formatter:off
- @PluginAttribute("regex") final String regex,
- @PluginElement("PatternFlags") final String[] patternFlags,
- @PluginAttribute("useRawMsg") final Boolean useRawMsg,
- @PluginAttribute("onMatch") final Result match,
- @PluginAttribute("onMismatch") final Result mismatch)
+ @PluginAttribute final String regex,
+ @PluginElement final String[] patternFlags,
+ @PluginAttribute final Boolean useRawMsg,
+ @PluginAttribute final Result onMatch,
+ @PluginAttribute final Result onMismatch)
//@formatter:on
throws IllegalArgumentException, IllegalAccessException {
if (regex == null) {
LOGGER.error("A regular expression must be provided for RegexFilter");
return null;
}
- return new RegexFilter(useRawMsg, Pattern.compile(regex, toPatternFlags(patternFlags)), match, mismatch);
+ return new RegexFilter(useRawMsg, Pattern.compile(regex, toPatternFlags(patternFlags)), onMatch, onMismatch);
}
private static int toPatternFlags(final String[] patternFlags) throws IllegalArgumentException,
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/ScriptFilter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/ScriptFilter.java
index 1e47d4e..98cf189 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/ScriptFilter.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/ScriptFilter.java
@@ -24,12 +24,12 @@
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.Logger;
import org.apache.logging.log4j.core.config.Configuration;
-import org.apache.logging.log4j.core.config.Node;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
+import org.apache.logging.log4j.plugins.Node;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.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.plugins.PluginElement;
+import org.apache.logging.log4j.plugins.PluginFactory;
import org.apache.logging.log4j.core.script.AbstractScript;
import org.apache.logging.log4j.core.script.ScriptRef;
import org.apache.logging.log4j.message.Message;
@@ -43,7 +43,7 @@
@Plugin(name = "ScriptFilter", category = Node.CATEGORY, elementType = Filter.ELEMENT_TYPE, printObject = true)
public final class ScriptFilter extends AbstractFilter {
- private static org.apache.logging.log4j.Logger logger = StatusLogger.getLogger();
+ private static final org.apache.logging.log4j.Logger logger = StatusLogger.getLogger();
private final AbstractScript script;
private final Configuration configuration;
@@ -125,17 +125,17 @@
* Creates the ScriptFilter.
* @param script The script to run. The script must return a boolean value. Either script or scriptFile must be
* provided.
- * @param match The action to take if a match occurs.
- * @param mismatch The action to take if no match occurs.
+ * @param onMatch The action to take if a match occurs.
+ * @param onMismatch The action to take if no match occurs.
* @param configuration the configuration
* @return A ScriptFilter.
*/
// TODO Consider refactoring to use AbstractFilter.AbstractFilterBuilder
@PluginFactory
public static ScriptFilter createFilter(
- @PluginElement("Script") final AbstractScript script,
- @PluginAttribute("onMatch") final Result match,
- @PluginAttribute("onMismatch") final Result mismatch,
+ @PluginElement final AbstractScript script,
+ @PluginAttribute final Result onMatch,
+ @PluginAttribute final Result onMismatch,
@PluginConfiguration final Configuration configuration) {
if (script == null) {
@@ -149,7 +149,7 @@
}
}
- return new ScriptFilter(script, configuration, match, mismatch);
+ return new ScriptFilter(script, configuration, onMatch, onMismatch);
}
}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/StringMatchFilter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/StringMatchFilter.java
new file mode 100644
index 0000000..a273726
--- /dev/null
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/StringMatchFilter.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.filter;
+
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.Marker;
+import org.apache.logging.log4j.core.Filter;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.Logger;
+import org.apache.logging.log4j.message.Message;
+import org.apache.logging.log4j.plugins.Node;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginBuilderAttribute;
+import org.apache.logging.log4j.plugins.PluginFactory;
+import org.apache.logging.log4j.util.PerformanceSensitive;
+
+/**
+ * This filter returns the onMatch result if the logging level in the event matches the specified logging level
+ * exactly.
+ */
+@Plugin(name = "StringMatchFilter", category = Node.CATEGORY, elementType = Filter.ELEMENT_TYPE, printObject = true)
+@PerformanceSensitive("allocation")
+public final class StringMatchFilter extends AbstractFilter {
+
+ public static final String ATTR_MATCH = "match";
+ private final String text;
+
+ private StringMatchFilter(final String text, final Result onMatch, final Result onMismatch) {
+ super(onMatch, onMismatch);
+ this.text = text;
+ }
+
+ @Override
+ public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
+ final Object... params) {
+ return filter(logger.getMessageFactory().newMessage(msg, params).getFormattedMessage());
+ }
+
+ @Override
+ public Result filter(final Logger logger, final Level level, final Marker marker, final Object msg,
+ final Throwable t) {
+ return filter(logger.getMessageFactory().newMessage(msg).getFormattedMessage());
+ }
+
+ @Override
+ public Result filter(final Logger logger, final Level level, final Marker marker, final Message msg,
+ final Throwable t) {
+ return filter(msg.getFormattedMessage());
+ }
+
+ @Override
+ public Result filter(final LogEvent event) {
+ return filter(event.getMessage().getFormattedMessage());
+ }
+
+ private Result filter(final String msg) {
+ return msg.contains(this.text) ? onMatch : onMismatch;
+ }
+
+ @Override
+ public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
+ final Object p0) {
+ return filter(logger.getMessageFactory().newMessage(msg, p0).getFormattedMessage());
+ }
+
+ @Override
+ public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
+ final Object p0, final Object p1) {
+ return filter(logger.getMessageFactory().newMessage(msg, p0, p1).getFormattedMessage());
+ }
+
+ @Override
+ public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
+ final Object p0, final Object p1, final Object p2) {
+ return filter(logger.getMessageFactory().newMessage(msg, p0, p1, p2).getFormattedMessage());
+ }
+
+ @Override
+ public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
+ final Object p0, final Object p1, final Object p2, final Object p3) {
+ return filter(logger.getMessageFactory().newMessage(msg, p0, p1, p2, p3).getFormattedMessage());
+ }
+
+ @Override
+ public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
+ final Object p0, final Object p1, final Object p2, final Object p3,
+ final Object p4) {
+ return filter(logger.getMessageFactory().newMessage(msg, p0, p1, p2, p3, p4).getFormattedMessage());
+ }
+
+ @Override
+ public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
+ final Object p0, final Object p1, final Object p2, final Object p3,
+ final Object p4, final Object p5) {
+ return filter(logger.getMessageFactory().newMessage(msg, p0, p1, p2, p3, p4, p5).getFormattedMessage());
+ }
+
+ @Override
+ public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
+ final Object p0, final Object p1, final Object p2, final Object p3,
+ final Object p4, final Object p5, final Object p6) {
+ return filter(logger.getMessageFactory().newMessage(msg, p0, p1, p2, p3, p4, p5, p6).getFormattedMessage());
+ }
+
+ @Override
+ public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
+ final Object p0, final Object p1, final Object p2, final Object p3,
+ final Object p4, final Object p5, final Object p6,
+ final Object p7) {
+ return filter(logger.getMessageFactory().newMessage(msg, p0, p1, p2, p3, p4, p5, p6, p7).getFormattedMessage());
+ }
+
+ @Override
+ public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
+ final Object p0, final Object p1, final Object p2, final Object p3,
+ final Object p4, final Object p5, final Object p6,
+ final Object p7, final Object p8) {
+ return filter(logger.getMessageFactory().newMessage(msg, p0, p1, p2, p3, p4, p5, p6, p7, p8)
+ .getFormattedMessage());
+ }
+
+ @Override
+ public Result filter(final Logger logger, final Level level, final Marker marker, final String msg,
+ final Object p0, final Object p1, final Object p2, final Object p3,
+ final Object p4, final Object p5, final Object p6,
+ final Object p7, final Object p8, final Object p9) {
+ return filter(logger.getMessageFactory().newMessage(msg, p0, p1, p2, p3, p4, p5, p6, p7, p8, p9)
+ .getFormattedMessage());
+ }
+
+ @Override
+ public String toString() {
+ return text;
+ }
+
+ @PluginFactory
+ public static StringMatchFilter.Builder newBuilder() {
+ return new StringMatchFilter.Builder();
+ }
+
+ public static class Builder extends AbstractFilterBuilder<StringMatchFilter.Builder> implements org.apache.logging.log4j.core.util.Builder<StringMatchFilter> {
+ @PluginBuilderAttribute
+ private String text = "";
+
+ /**
+ * Sets the logging level to use.
+ * @param level the logging level to use.
+ * @return this
+ */
+ public StringMatchFilter.Builder setMatchString(final String text) {
+ this.text = text;
+ return this;
+ }
+
+ @Override
+ public StringMatchFilter build() {
+ return new StringMatchFilter(this.text, this.getOnMatch(), this.getOnMismatch());
+ }
+ }
+
+}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/StructuredDataFilter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/StructuredDataFilter.java
index 91a9924..d28c346 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/StructuredDataFilter.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/StructuredDataFilter.java
@@ -26,11 +26,11 @@
import org.apache.logging.log4j.core.Filter;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.Logger;
-import org.apache.logging.log4j.core.config.Node;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
-import org.apache.logging.log4j.core.config.plugins.PluginElement;
-import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+import org.apache.logging.log4j.plugins.Node;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginAttribute;
+import org.apache.logging.log4j.plugins.PluginElement;
+import org.apache.logging.log4j.plugins.PluginFactory;
import org.apache.logging.log4j.core.util.KeyValuePair;
import org.apache.logging.log4j.message.Message;
import org.apache.logging.log4j.message.StructuredDataMessage;
@@ -46,7 +46,7 @@
public final class StructuredDataFilter extends MapFilter {
private static final int MAX_BUFFER_SIZE = 2048;
- private static ThreadLocal<StringBuilder> threadLocalStringBuilder = new ThreadLocal<>();
+ private static final ThreadLocal<StringBuilder> threadLocalStringBuilder = new ThreadLocal<>();
private StructuredDataFilter(final Map<String, List<String>> map, final boolean oper, final Result onMatch,
final Result onMismatch) {
@@ -149,18 +149,18 @@
/**
* Creates the StructuredDataFilter.
* @param pairs Key and value pairs.
- * @param oper The operator to perform. If not "or" the operation will be an "and".
- * @param match The action to perform on a match.
- * @param mismatch The action to perform on a mismatch.
+ * @param operator The operator to perform. If not "or" the operation will be an "and".
+ * @param onMatch The action to perform on a match.
+ * @param onMismatch The action to perform on a mismatch.
* @return The StructuredDataFilter.
*/
// TODO Consider refactoring to use AbstractFilter.AbstractFilterBuilder
@PluginFactory
public static StructuredDataFilter createFilter(
- @PluginElement("Pairs") final KeyValuePair[] pairs,
- @PluginAttribute("operator") final String oper,
- @PluginAttribute("onMatch") final Result match,
- @PluginAttribute("onMismatch") final Result mismatch) {
+ @PluginElement final KeyValuePair[] pairs,
+ @PluginAttribute final String operator,
+ @PluginAttribute final Result onMatch,
+ @PluginAttribute final Result onMismatch) {
if (pairs == null || pairs.length == 0) {
LOGGER.error("keys and values must be specified for the StructuredDataFilter");
return null;
@@ -190,7 +190,7 @@
LOGGER.error("StructuredDataFilter is not configured with any valid key value pairs");
return null;
}
- final boolean isAnd = oper == null || !oper.equalsIgnoreCase("or");
- return new StructuredDataFilter(map, isAnd, match, mismatch);
+ final boolean isAnd = operator == null || !operator.equalsIgnoreCase("or");
+ return new StructuredDataFilter(map, isAnd, onMatch, onMismatch);
}
}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/ThreadContextMapFilter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/ThreadContextMapFilter.java
index 7f11e32..5f4beb7 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/ThreadContextMapFilter.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/ThreadContextMapFilter.java
@@ -28,12 +28,12 @@
import org.apache.logging.log4j.core.Filter;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.Logger;
-import org.apache.logging.log4j.core.config.Node;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginAliases;
-import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
-import org.apache.logging.log4j.core.config.plugins.PluginElement;
-import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+import org.apache.logging.log4j.plugins.Node;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginAliases;
+import org.apache.logging.log4j.plugins.PluginAttribute;
+import org.apache.logging.log4j.plugins.PluginElement;
+import org.apache.logging.log4j.plugins.PluginFactory;
import org.apache.logging.log4j.core.impl.ContextDataInjectorFactory;
import org.apache.logging.log4j.core.util.KeyValuePair;
import org.apache.logging.log4j.message.Message;
@@ -197,10 +197,10 @@
// TODO Consider refactoring to use AbstractFilter.AbstractFilterBuilder
@PluginFactory
public static ThreadContextMapFilter createFilter(
- @PluginElement("Pairs") final KeyValuePair[] pairs,
- @PluginAttribute("operator") final String oper,
- @PluginAttribute("onMatch") final Result match,
- @PluginAttribute("onMismatch") final Result mismatch) {
+ @PluginElement final KeyValuePair[] pairs,
+ @PluginAttribute final String operator,
+ @PluginAttribute final Result onMatch,
+ @PluginAttribute final Result onMismatch) {
if (pairs == null || pairs.length == 0) {
LOGGER.error("key and value pairs must be specified for the ThreadContextMapFilter");
return null;
@@ -230,7 +230,7 @@
LOGGER.error("ThreadContextMapFilter is not configured with any valid key value pairs");
return null;
}
- final boolean isAnd = oper == null || !oper.equalsIgnoreCase("or");
- return new ThreadContextMapFilter(map, isAnd, match, mismatch);
+ final boolean isAnd = operator == null || !operator.equalsIgnoreCase("or");
+ return new ThreadContextMapFilter(map, isAnd, onMatch, onMismatch);
}
}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/ThresholdFilter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/ThresholdFilter.java
index ae9d126..76befe0 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/ThresholdFilter.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/ThresholdFilter.java
@@ -21,10 +21,10 @@
import org.apache.logging.log4j.core.Filter;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.Logger;
-import org.apache.logging.log4j.core.config.Node;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
-import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+import org.apache.logging.log4j.plugins.Node;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginAttribute;
+import org.apache.logging.log4j.plugins.PluginFactory;
import org.apache.logging.log4j.message.Message;
import org.apache.logging.log4j.util.PerformanceSensitive;
@@ -162,7 +162,7 @@
// TODO Consider refactoring to use AbstractFilter.AbstractFilterBuilder
@PluginFactory
public static ThresholdFilter createFilter(
- @PluginAttribute("level") final Level level,
+ @PluginAttribute final Level level,
@PluginAttribute("onMatch") final Result match,
@PluginAttribute("onMismatch") final Result mismatch) {
final Level actualLevel = level == null ? Level.ERROR : level;
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/TimeFilter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/TimeFilter.java
index 491960b..c6f0799 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/TimeFilter.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/TimeFilter.java
@@ -16,23 +16,26 @@
*/
package org.apache.logging.log4j.core.filter;
-import java.text.ParseException;
-import java.text.SimpleDateFormat;
-import java.util.Calendar;
-import java.util.TimeZone;
+import java.time.Duration;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalTime;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.Marker;
import org.apache.logging.log4j.core.Filter;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.Logger;
-import org.apache.logging.log4j.core.config.Node;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
import org.apache.logging.log4j.core.config.plugins.PluginFactory;
import org.apache.logging.log4j.core.time.Clock;
import org.apache.logging.log4j.core.time.ClockFactory;
import org.apache.logging.log4j.message.Message;
+import org.apache.logging.log4j.plugins.Node;
+import org.apache.logging.log4j.plugins.Plugin;
import org.apache.logging.log4j.util.PerformanceSensitive;
/**
@@ -42,63 +45,81 @@
@PerformanceSensitive("allocation")
public final class TimeFilter extends AbstractFilter {
private static final Clock CLOCK = ClockFactory.getClock();
+ private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("HH:mm:ss");
/**
* Length of hour in milliseconds.
*/
private static final long HOUR_MS = 3600000;
- /**
- * Length of minute in milliseconds.
- */
- private static final long MINUTE_MS = 60000;
-
- /**
- * Length of second in milliseconds.
- */
- private static final long SECOND_MS = 1000;
+ private static final long DAY_MS = HOUR_MS * 24;
/**
* Starting offset from midnight in milliseconds.
*/
- private final long start;
+ private volatile long start;
+ private final LocalTime startTime;
+
/**
* Ending offset from midnight in milliseconds.
*/
- private final long end;
+ private volatile long end;
+ private final LocalTime endTime;
+
+ private final long duration;
+
/**
* Timezone.
*/
- private final TimeZone timezone;
+ private final ZoneId timeZone;
- private long midnightToday;
- private long midnightTomorrow;
-
-
- private TimeFilter(final long start, final long end, final TimeZone tz, final Result onMatch,
- final Result onMismatch) {
+ /*
+ * Expose for unit testing.
+ */
+ TimeFilter(final LocalTime start, final LocalTime end, final ZoneId timeZone, final Result onMatch,
+ final Result onMismatch, LocalDate now) {
super(onMatch, onMismatch);
- this.start = start;
- this.end = end;
- timezone = tz;
- initMidnight(start);
+ this.startTime = start;
+ this.endTime = end;
+ this.timeZone = timeZone;
+ this.start = ZonedDateTime.of(now, startTime, timeZone).withEarlierOffsetAtOverlap().toInstant().toEpochMilli();
+ long endMillis = ZonedDateTime.of(now, endTime, timeZone).withEarlierOffsetAtOverlap().toInstant().toEpochMilli();
+ if (end.isBefore(start)) {
+ // End time must be tomorrow.
+ endMillis += DAY_MS;
+ }
+ duration = startTime.isBefore(endTime) ? Duration.between(startTime, endTime).toMillis() :
+ Duration.between(startTime, endTime).plusHours(24).toMillis();
+ long difference = (endMillis - this.start) - duration;
+ if (difference != 0) {
+ // Handle switch from standard time to daylight time and daylight time to standard time.
+ endMillis -= difference;
+ }
+ this.end = endMillis;
}
- /**
- * Initializes the midnight boundaries to midnight in the specified time zone.
- * @param now a time in milliseconds since the epoch, used to pinpoint the current date
- */
- void initMidnight(final long now) {
- final Calendar calendar = Calendar.getInstance(timezone);
- calendar.setTimeInMillis(now);
- calendar.set(Calendar.HOUR_OF_DAY, 0);
- calendar.set(Calendar.MINUTE, 0);
- calendar.set(Calendar.SECOND, 0);
- calendar.set(Calendar.MILLISECOND, 0);
- midnightToday = calendar.getTimeInMillis();
+ private TimeFilter(final LocalTime start, final LocalTime end, final ZoneId timeZone, final Result onMatch,
+ final Result onMismatch) {
+ this(start, end, timeZone, onMatch, onMismatch, LocalDate.now(timeZone));
+ }
- calendar.add(Calendar.DATE, 1);
- midnightTomorrow = calendar.getTimeInMillis();
+ private synchronized void adjustTimes(long currentTimeMillis) {
+ if (currentTimeMillis <= end) {
+ return;
+ }
+ LocalDate date = Instant.ofEpochMilli(currentTimeMillis).atZone(timeZone).toLocalDate();
+ this.start = ZonedDateTime.of(date, startTime, timeZone).withEarlierOffsetAtOverlap().toInstant().toEpochMilli();
+ long endMillis = ZonedDateTime.of(date, endTime, timeZone).withEarlierOffsetAtOverlap().toInstant().toEpochMilli();
+ if (endTime.isBefore(startTime)) {
+ // End time must be tomorrow.
+ endMillis += DAY_MS;
+ }
+ long difference = (endMillis - this.start) - duration;
+ if (difference != 0) {
+ // Handle switch from standard time to daylight time and daylight time to standard time.
+ endMillis -= difference;
+ }
+ this.end = endMillis;
}
/**
@@ -109,12 +130,10 @@
* @return the action to perform
*/
Result filter(final long currentTimeMillis) {
- if (currentTimeMillis >= midnightTomorrow || currentTimeMillis < midnightToday) {
- initMidnight(currentTimeMillis);
+ if (currentTimeMillis > end) {
+ adjustTimes(currentTimeMillis);
}
- return currentTimeMillis >= midnightToday + start && currentTimeMillis <= midnightToday + end //
- ? onMatch // within window
- : onMismatch;
+ return currentTimeMillis >= start && currentTimeMillis <= end ? onMatch : onMismatch;
}
@Override
@@ -213,7 +232,7 @@
final StringBuilder sb = new StringBuilder();
sb.append("start=").append(start);
sb.append(", end=").append(end);
- sb.append(", timezone=").append(timezone.toString());
+ sb.append(", timezone=").append(timeZone.toString());
return sb.toString();
}
@@ -234,23 +253,22 @@
@PluginAttribute("timezone") final String tz,
@PluginAttribute("onMatch") final Result match,
@PluginAttribute("onMismatch") final Result mismatch) {
- final long s = parseTimestamp(start, 0);
- final long e = parseTimestamp(end, Long.MAX_VALUE);
- final TimeZone timezone = tz == null ? TimeZone.getDefault() : TimeZone.getTimeZone(tz);
+ final LocalTime startTime = parseTimestamp(start, LocalTime.MIN);
+ final LocalTime endTime = parseTimestamp(end, LocalTime.MAX);
+ final ZoneId timeZone = tz == null ? ZoneId.systemDefault() : ZoneId.of(tz);
final Result onMatch = match == null ? Result.NEUTRAL : match;
final Result onMismatch = mismatch == null ? Result.DENY : mismatch;
- return new TimeFilter(s, e, timezone, onMatch, onMismatch);
+ return new TimeFilter(startTime, endTime, timeZone, onMatch, onMismatch);
}
- private static long parseTimestamp(final String timestamp, final long defaultValue) {
+ private static LocalTime parseTimestamp(final String timestamp, final LocalTime defaultValue) {
if (timestamp == null) {
return defaultValue;
}
- final SimpleDateFormat stf = new SimpleDateFormat("HH:mm:ss");
- stf.setTimeZone(TimeZone.getTimeZone("UTC"));
+
try {
- return stf.parse(timestamp).getTime();
- } catch (final ParseException e) {
+ return LocalTime.parse(timestamp, FORMATTER);
+ } catch (final Exception e) {
LOGGER.warn("Error parsing TimeFilter timestamp value {}", timestamp, e);
return defaultValue;
}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/package-info.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/package-info.java
index 8c4d417..07d1cee 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/package-info.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/filter/package-info.java
@@ -16,9 +16,9 @@
*/
/**
* Log4j 2 Filter support. {@link org.apache.logging.log4j.core.Filter} plugins should use the
- * {@linkplain org.apache.logging.log4j.core.config.plugins.Plugin#category() plugin category}
- * {@link org.apache.logging.log4j.core.config.Node#CATEGORY Core} and the
- * {@linkplain org.apache.logging.log4j.core.config.plugins.Plugin#elementType() element type}
+ * {@linkplain org.apache.logging.log4j.plugins.Plugin#category() plugin category}
+ * {@link org.apache.logging.log4j.plugins.Node#CATEGORY Core} and the
+ * {@linkplain org.apache.logging.log4j.plugins.Plugin#elementType() element type}
* {@link org.apache.logging.log4j.core.Filter#ELEMENT_TYPE filter}.
*/
package org.apache.logging.log4j.core.filter;
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ContextDataFactory.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ContextDataFactory.java
index b1adb78..2a152d7 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ContextDataFactory.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ContextDataFactory.java
@@ -16,16 +16,14 @@
*/
package org.apache.logging.log4j.core.impl;
-import java.lang.invoke.MethodHandle;
-import java.lang.invoke.MethodHandles;
-import java.lang.invoke.MethodType;
+import java.lang.reflect.Constructor;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.logging.log4j.core.ContextDataInjector;
import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.util.Loader;
import org.apache.logging.log4j.util.IndexedStringMap;
-import org.apache.logging.log4j.util.LoaderUtil;
import org.apache.logging.log4j.util.PropertiesUtil;
import org.apache.logging.log4j.util.ReadOnlyStringMap;
import org.apache.logging.log4j.util.SortedArrayStringMap;
@@ -48,11 +46,17 @@
* @since 2.7
*/
public class ContextDataFactory {
- private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup();
private static final String CLASS_NAME = PropertiesUtil.getProperties().getStringProperty("log4j2.ContextData");
private static final Class<? extends StringMap> CACHED_CLASS = createCachedClass(CLASS_NAME);
- private static final MethodHandle DEFAULT_CONSTRUCTOR = createDefaultConstructor(CACHED_CLASS);
- private static final MethodHandle INITIAL_CAPACITY_CONSTRUCTOR = createInitialCapacityConstructor(CACHED_CLASS);
+
+ /**
+ * In LOG4J2-2649 (https://issues.apache.org/jira/browse/LOG4J2-2649),
+ * the reporter said some reason about using graalvm to static compile.
+ * In graalvm doc (https://github.com/oracle/graal/blob/master/substratevm/LIMITATIONS.md),
+ * graalvm is not support MethodHandle now, so the Constructor need not to return MethodHandle.
+ */
+ private static final Constructor<?> DEFAULT_CONSTRUCTOR = createDefaultConstructor(CACHED_CLASS);
+ private static final Constructor<?> INITIAL_CAPACITY_CONSTRUCTOR = createInitialCapacityConstructor(CACHED_CLASS);
private static final StringMap EMPTY_STRING_MAP = createContextData(0);
@@ -65,30 +69,30 @@
return null;
}
try {
- return LoaderUtil.loadClass(className).asSubclass(IndexedStringMap.class);
+ return Loader.loadClass(className).asSubclass(IndexedStringMap.class);
} catch (final Exception any) {
return null;
}
}
- private static MethodHandle createDefaultConstructor(final Class<? extends StringMap> cachedClass) {
+ private static Constructor<?> createDefaultConstructor(final Class<? extends StringMap> cachedClass){
if (cachedClass == null) {
return null;
}
try {
- return LOOKUP.findConstructor(cachedClass, MethodType.methodType(void.class));
- } catch (final NoSuchMethodException | IllegalAccessException ignored) {
+ return cachedClass.getConstructor();
+ } catch (final NoSuchMethodException | IllegalAccessError ignored) {
return null;
}
}
- private static MethodHandle createInitialCapacityConstructor(final Class<? extends StringMap> cachedClass) {
+ private static Constructor<?> createInitialCapacityConstructor(final Class<? extends StringMap> cachedClass){
if (cachedClass == null) {
return null;
}
try {
- return LOOKUP.findConstructor(cachedClass, MethodType.methodType(void.class, int.class));
- } catch (final NoSuchMethodException | IllegalAccessException ignored) {
+ return cachedClass.getConstructor(int.class);
+ } catch (final NoSuchMethodException | IllegalAccessError ignored) {
return null;
}
}
@@ -98,7 +102,7 @@
return new SortedArrayStringMap();
}
try {
- return (IndexedStringMap) DEFAULT_CONSTRUCTOR.invoke();
+ return (IndexedStringMap) DEFAULT_CONSTRUCTOR.newInstance();
} catch (final Throwable ignored) {
return new SortedArrayStringMap();
}
@@ -109,7 +113,7 @@
return new SortedArrayStringMap(initialCapacity);
}
try {
- return (IndexedStringMap) INITIAL_CAPACITY_CONSTRUCTOR.invoke(initialCapacity);
+ return (IndexedStringMap) INITIAL_CAPACITY_CONSTRUCTOR.newInstance(initialCapacity);
} catch (final Throwable ignored) {
return new SortedArrayStringMap(initialCapacity);
}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ContextDataInjectorFactory.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ContextDataInjectorFactory.java
index fb1c330..74925f6 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ContextDataInjectorFactory.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ContextDataInjectorFactory.java
@@ -19,11 +19,11 @@
import org.apache.logging.log4j.ThreadContext;
import org.apache.logging.log4j.core.ContextDataInjector;
import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.util.Loader;
import org.apache.logging.log4j.spi.CopyOnWrite;
import org.apache.logging.log4j.spi.DefaultThreadContextMap;
import org.apache.logging.log4j.spi.ReadOnlyThreadContextMap;
import org.apache.logging.log4j.status.StatusLogger;
-import org.apache.logging.log4j.util.LoaderUtil;
import org.apache.logging.log4j.util.PropertiesUtil;
import org.apache.logging.log4j.util.ReadOnlyStringMap;
@@ -48,6 +48,10 @@
* {@code ContextDataInjector} classes defined in {@link ThreadContextDataInjector} which is most appropriate for
* the ThreadContext implementation.
* <p>
+ * <b>Note:</b> It is no longer recommended that users provide a custom implementation of the ContextDataInjector.
+ * Instead, provide a {@code ContextDataProvider}.
+ * </p>
+ * <p>
* Users may use this system property to specify the fully qualified class name of a class that implements the
* {@code ContextDataInjector} interface.
* </p><p>
@@ -67,7 +71,7 @@
return createDefaultInjector();
}
try {
- final Class<? extends ContextDataInjector> cls = LoaderUtil.loadClass(className).asSubclass(
+ final Class<? extends ContextDataInjector> cls = Loader.loadClass(className).asSubclass(
ContextDataInjector.class);
return cls.newInstance();
} catch (final Exception dynamicFailed) {
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/DefaultLogEventFactory.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/DefaultLogEventFactory.java
index 127b02a..719a932 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/DefaultLogEventFactory.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/DefaultLogEventFactory.java
@@ -53,4 +53,24 @@
final List<Property> properties, final Throwable t) {
return new Log4jLogEvent(loggerName, marker, fqcn, level, data, properties, t);
}
+
+ /**
+ * Creates a log event.
+ *
+ * @param loggerName The name of the Logger.
+ * @param marker An optional Marker.
+ * @param fqcn The fully qualified class name of the caller.
+ * @param location The location of the caller
+ * @param level The event Level.
+ * @param data The Message.
+ * @param properties Properties to be added to the log event.
+ * @param t An optional Throwable.
+ * @return The LogEvent.
+ */
+ @Override
+ public LogEvent createEvent(final String loggerName, final Marker marker, final String fqcn,
+ final StackTraceElement location, final Level level, final Message data,
+ final List<Property> properties, final Throwable t) {
+ return new Log4jLogEvent(loggerName, marker, fqcn, location, level, data, properties, t);
+ }
}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ExtendedClassInfo.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ExtendedClassInfo.java
index 44d42f9..0ab9a8d 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ExtendedClassInfo.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ExtendedClassInfo.java
@@ -120,4 +120,4 @@
return sb.toString();
}
-}
\ No newline at end of file
+}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/JdkMapAdapterStringMap.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/JdkMapAdapterStringMap.java
index 4091276..f1cd4bc 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/JdkMapAdapterStringMap.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/JdkMapAdapterStringMap.java
@@ -30,7 +30,7 @@
/**
* Provides a read-only {@code StringMap} view of a {@code Map<String, String>}.
*/
-class JdkMapAdapterStringMap implements StringMap {
+public class JdkMapAdapterStringMap implements StringMap {
private static final long serialVersionUID = -7348247784983193612L;
private static final String FROZEN = "Frozen collection cannot be modified";
private static final Comparator<? super String> NULL_FIRST_COMPARATOR = new Comparator<String>() {
@@ -143,7 +143,7 @@
sortedKeys = null;
}
- private static TriConsumer<String, String, Map<String, String>> PUT_ALL = new TriConsumer<String, String, Map<String, String>>() {
+ private static final TriConsumer<String, String, Map<String, String>> PUT_ALL = new TriConsumer<String, String, Map<String, String>>() {
@Override
public void accept(final String key, final String value, final Map<String, String> stringStringMap) {
stringStringMap.put(key, value);
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/Log4jContextFactory.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/Log4jContextFactory.java
index bfd6df2..1ca8280 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/Log4jContextFactory.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/Log4jContextFactory.java
@@ -33,8 +33,10 @@
import org.apache.logging.log4j.core.util.Cancellable;
import org.apache.logging.log4j.core.util.Constants;
import org.apache.logging.log4j.core.util.DefaultShutdownCallbackRegistry;
+import org.apache.logging.log4j.core.util.Loader;
import org.apache.logging.log4j.core.util.ShutdownCallbackRegistry;
import org.apache.logging.log4j.spi.LoggerContextFactory;
+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;
@@ -95,7 +97,7 @@
private static ContextSelector createContextSelector() {
try {
- final ContextSelector selector = LoaderUtil.newCheckedInstanceOfProperty(Constants.LOG4J_CONTEXT_SELECTOR,
+ final ContextSelector selector = Loader.newCheckedInstanceOfProperty(Constants.LOG4J_CONTEXT_SELECTOR,
ContextSelector.class);
if (selector != null) {
return selector;
@@ -108,7 +110,7 @@
private static ShutdownCallbackRegistry createShutdownCallbackRegistry() {
try {
- final ShutdownCallbackRegistry registry = LoaderUtil.newCheckedInstanceOfProperty(
+ final ShutdownCallbackRegistry registry = Loader.newCheckedInstanceOfProperty(
ShutdownCallbackRegistry.SHUTDOWN_CALLBACK_REGISTRY, ShutdownCallbackRegistry.class
);
if (registry != null) {
@@ -283,6 +285,27 @@
return ctx;
}
+ @Override
+ public void shutdown(String fqcn, ClassLoader loader, boolean currentContext, boolean allContexts) {
+ if (selector.hasContext(fqcn, loader, currentContext)) {
+ selector.shutdown(fqcn, loader, currentContext, allContexts);
+ }
+ }
+
+ /**
+ * Checks to see if a LoggerContext is installed.
+ * @param fqcn The fully qualified class name of the caller.
+ * @param loader The ClassLoader to use or null.
+ * @param currentContext If true returns the current Context, if false returns the Context appropriate
+ * for the caller if a more appropriate Context can be determined.
+ * @return true if a LoggerContext has been installed, false otherwise.
+ * @since 2.13.0
+ */
+ @Override
+ public boolean hasContext(String fqcn, ClassLoader loader, boolean currentContext) {
+ return selector.hasContext(fqcn, loader, currentContext);
+ }
+
/**
* Returns the ContextSelector.
* @return The ContextSelector.
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/Log4jLogEvent.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/Log4jLogEvent.java
index 06c8c1d..f00ab66 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/Log4jLogEvent.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/Log4jLogEvent.java
@@ -77,7 +77,7 @@
private final transient long nanoTime;
/** LogEvent Builder helper class. */
- public static class Builder implements org.apache.logging.log4j.core.util.Builder<LogEvent> {
+ public static class Builder implements org.apache.logging.log4j.plugins.util.Builder<LogEvent> {
private String loggerFqcn;
private Marker marker;
@@ -85,7 +85,7 @@
private String loggerName;
private Message message;
private Throwable thrown;
- private MutableInstant instant = new MutableInstant();
+ private final MutableInstant instant = new MutableInstant();
private ThrowableProxy thrownProxy;
private StringMap contextData = createContextData((List<Property>) null);
private ThreadContext.ContextStack contextStack = ThreadContext.getImmutableStack();
@@ -196,17 +196,6 @@
return this;
}
- @Deprecated
- public Builder setContextMap(final Map<String, String> contextMap) {
- contextData = ContextDataFactory.createContextData(); // replace with new instance
- if (contextMap != null) {
- for (final Map.Entry<String, String> entry : contextMap.entrySet()) {
- contextData.putValue(entry.getKey(), entry.getValue());
- }
- }
- return this;
- }
-
public Builder setContextData(final StringMap contextData) {
this.contextData = contextData;
return this;
@@ -290,32 +279,6 @@
}
/**
- *
- * @deprecated use {@link Log4jLogEvent.Builder} instead. This constructor will be removed in an upcoming release.
- */
- @Deprecated
- public Log4jLogEvent(final long timestamp) {
- this(Strings.EMPTY, null, Strings.EMPTY, null, null, (Throwable) null, null, null, null, 0, null,
- 0, null, timestamp, 0, nanoClock.nanoTime());
- }
-
- /**
- * Constructor.
- * @param loggerName The name of the Logger.
- * @param marker The Marker or null.
- * @param loggerFQCN The fully qualified class name of the caller.
- * @param level The logging Level.
- * @param message The Message.
- * @param t A Throwable or null.
- * @deprecated use {@link Log4jLogEvent.Builder} instead. This constructor will be removed in an upcoming release.
- */
- @Deprecated
- public Log4jLogEvent(final String loggerName, final Marker marker, final String loggerFQCN, final Level level,
- final Message message, final Throwable t) {
- this(loggerName, marker, loggerFQCN, level, message, null, t);
- }
-
- /**
* Constructor.
* @param loggerName The name of the Logger.
* @param marker The Marker or null.
@@ -338,60 +301,31 @@
nanoClock.nanoTime());
}
- /**
- * Constructor.
- * @param loggerName The name of the Logger.
- * @param marker The Marker or null.
- * @param loggerFQCN The fully qualified class name of the caller.
- * @param level The logging Level.
- * @param message The Message.
- * @param t A Throwable or null.
- * @param mdc The mapped diagnostic context.
- * @param ndc the nested diagnostic context.
- * @param threadName The name of the thread.
- * @param location The locations of the caller.
- * @param timestampMillis The timestamp of the event.
- * @deprecated use {@link Log4jLogEvent.Builder} instead. This constructor will be removed in an upcoming release.
- */
- @Deprecated
- public Log4jLogEvent(final String loggerName, final Marker marker, final String loggerFQCN, final Level level,
- final Message message, final Throwable t, final Map<String, String> mdc,
- final ThreadContext.ContextStack ndc, final String threadName,
- final StackTraceElement location, final long timestampMillis) {
- this(loggerName, marker, loggerFQCN, level, message, t, null, createContextData(mdc), ndc, 0,
- threadName, 0, location, timestampMillis, 0, nanoClock.nanoTime());
- }
-
- /**
- * Create a new LogEvent.
- * @param loggerName The name of the Logger.
- * @param marker The Marker or null.
- * @param loggerFQCN The fully qualified class name of the caller.
- * @param level The logging Level.
- * @param message The Message.
- * @param thrown A Throwable or null.
- * @param thrownProxy A ThrowableProxy or null.
- * @param mdc The mapped diagnostic context.
- * @param ndc the nested diagnostic context.
- * @param threadName The name of the thread.
- * @param location The locations of the caller.
- * @param timestamp The timestamp of the event.
- * @return a new LogEvent
- * @deprecated use {@link Log4jLogEvent.Builder} instead. This method will be removed in an upcoming release.
- */
- @Deprecated
- public static Log4jLogEvent createEvent(final String loggerName, final Marker marker, final String loggerFQCN,
- final Level level, final Message message, final Throwable thrown,
- final ThrowableProxy thrownProxy,
- final Map<String, String> mdc, final ThreadContext.ContextStack ndc,
- final String threadName, final StackTraceElement location,
- final long timestamp) {
- final Log4jLogEvent result = new Log4jLogEvent(loggerName, marker, loggerFQCN, level, message, thrown,
- thrownProxy, createContextData(mdc), ndc, 0, threadName, 0, location, timestamp, 0, nanoClock.nanoTime());
- return result;
+ /**
+ * Constructor.
+ * @param loggerName The name of the Logger.
+ * @param marker The Marker or null.
+ * @param source The location of the caller.
+ * @param level The logging Level.
+ * @param message The Message.
+ * @param properties the properties to be merged with ThreadContext key-value pairs into the event's ReadOnlyStringMap.
+ * @param t A Throwable or null.
+ */
+ // This constructor is called from LogEventFactories.
+ public Log4jLogEvent(final String loggerName, final Marker marker, final String fqcn, final StackTraceElement source,
+ final Level level, final Message message, final List<Property> properties, final Throwable t) {
+ this(loggerName, marker,fqcn, level, message, t,
+ null, createContextData(properties),
+ ThreadContext.getDepth() == 0 ? null : ThreadContext.cloneStack(), // mutable copy
+ 0, // thread id
+ null, // thread name
+ 0, // thread priority
+ source, // StackTraceElement source
+ CLOCK, //
+ nanoClock.nanoTime());
}
- /**
+ /**
* Constructor.
* @param loggerName The name of the Logger.
* @param marker The Marker or null.
@@ -635,14 +569,6 @@
public ReadOnlyStringMap getContextData() {
return contextData;
}
- /**
- * Returns the immutable copy of the ThreadContext Map.
- * @return The context Map.
- */
- @Override
- public Map<String, String> getContextMap() {
- return contextData.toMap();
- }
/**
* Returns an immutable copy of the ThreadContext stack.
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/LogEventFactory.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/LogEventFactory.java
index ad9128c..b2af73a 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/LogEventFactory.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/LogEventFactory.java
@@ -32,4 +32,9 @@
LogEvent createEvent(String loggerName, Marker marker, String fqcn, Level level, Message data,
List<Property> properties, Throwable t);
+
+ default LogEvent createEvent(String loggerName, Marker marker, String fqcn, StackTraceElement location, Level level,
+ Message data, List<Property> properties, Throwable t) {
+ return createEvent(loggerName, marker, fqcn, level, data, properties, t);
+ }
}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/MutableLogEvent.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/MutableLogEvent.java
index 80c086a..c26f34a 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/MutableLogEvent.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/MutableLogEvent.java
@@ -47,7 +47,7 @@
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;
@@ -69,7 +69,8 @@
transient boolean reserved = false;
public MutableLogEvent() {
- this(new StringBuilder(Constants.INITIAL_REUSABLE_MESSAGE_SIZE), new Object[10]);
+ // messageText and the parameter array are lazily initialized
+ this(null, null);
}
public MutableLogEvent(final StringBuilder msgText, final Object[] replacementParameters) {
@@ -148,9 +149,7 @@
StringBuilders.trimToMaxSize(messageText, Constants.MAX_REUSABLE_MESSAGE_SIZE);
if (parameters != null) {
- for (int i = 0; i < parameters.length; i++) {
- parameters[i] = null;
- }
+ Arrays.fill(parameters, null);
}
// primitive fields that cannot be cleared:
@@ -214,10 +213,8 @@
final ReusableMessage reusable = (ReusableMessage) msg;
reusable.formatTo(getMessageTextForWriting());
this.messageFormat = msg.getFormat();
- if (parameters != null) {
- parameters = reusable.swapParameters(parameters);
- parameterCount = reusable.getParameterCount();
- }
+ parameters = reusable.swapParameters(parameters == null ? new Object[10] : parameters);
+ parameterCount = reusable.getParameterCount();
} else {
this.message = InternalAsyncUtil.makeMessageImmutable(msg);
}
@@ -225,8 +222,7 @@
private StringBuilder getMessageTextForWriting() {
if (messageText == null) {
- // Should never happen:
- // only happens if user logs a custom reused message when Constants.ENABLE_THREADLOCALS is false
+ // Happens the first time messageText is requested
messageText = new StringBuilder(Constants.INITIAL_REUSABLE_MESSAGE_SIZE);
}
messageText.setLength(0);
@@ -355,6 +351,10 @@
return thrownProxy;
}
+ public void setSource(StackTraceElement source) {
+ this.source = source;
+ }
+
/**
* Returns the StackTraceElement for the caller. This will be the entry that occurs right
* before the first occurrence of FQCN as a class name.
@@ -378,11 +378,6 @@
return contextData;
}
- @Override
- public Map<String, String> getContextMap() {
- return contextData.toMap();
- }
-
public void setContextData(final StringMap mutableContextData) {
this.contextData = mutableContextData;
}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ReusableLogEventFactory.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ReusableLogEventFactory.java
index 300b1b9..ee8a0cd 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ReusableLogEventFactory.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ReusableLogEventFactory.java
@@ -38,7 +38,7 @@
private static final ThreadNameCachingStrategy THREAD_NAME_CACHING_STRATEGY = ThreadNameCachingStrategy.create();
private static final Clock CLOCK = ClockFactory.getClock();
- private static ThreadLocal<MutableLogEvent> mutableLogEventThreadLocal = new ThreadLocal<>();
+ private static final ThreadLocal<MutableLogEvent> mutableLogEventThreadLocal = new ThreadLocal<>();
private final ContextDataInjector injector = ContextDataInjectorFactory.createInjector();
/**
@@ -55,7 +55,26 @@
*/
@Override
public LogEvent createEvent(final String loggerName, final Marker marker,
- final String fqcn, final Level level, final Message message,
+ final String fqcn, final Level level, final Message message,
+ final List<Property> properties, final Throwable t) {
+ return createEvent(loggerName, marker, fqcn, null, level, message, properties, t);
+ }
+
+ /**
+ * Creates a log event.
+ *
+ * @param loggerName The name of the Logger.
+ * @param marker An optional Marker.
+ * @param fqcn The fully qualified class name of the caller.
+ * @param level The event Level.
+ * @param message The Message.
+ * @param properties Properties to be added to the log event.
+ * @param t An optional Throwable.
+ * @return The LogEvent.
+ */
+ @Override
+ public LogEvent createEvent(final String loggerName, final Marker marker,
+ final String fqcn, StackTraceElement location, final Level level, final Message message,
final List<Property> properties, final Throwable t) {
MutableLogEvent result = mutableLogEventThreadLocal.get();
if (result == null || result.reserved) {
@@ -80,6 +99,7 @@
result.setMessage(message);
result.initTime(CLOCK, Log4jLogEvent.getNanoClock());
result.setThrown(t);
+ result.setSource(location);
result.setContextData(injector.injectContextData(properties, (StringMap) result.getContextData()));
result.setContextStack(ThreadContext.getDepth() == 0 ? ThreadContext.EMPTY_STACK : ThreadContext.cloneStack());// mutable copy
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ThreadContextDataInjector.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ThreadContextDataInjector.java
index 25f1d03..ace66ef 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ThreadContextDataInjector.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ThreadContextDataInjector.java
@@ -16,14 +16,24 @@
*/
package org.apache.logging.log4j.core.impl;
+import java.util.ArrayList;
+import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.ServiceLoader;
+import java.util.concurrent.ConcurrentLinkedDeque;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.ThreadContext;
import org.apache.logging.log4j.core.ContextDataInjector;
import org.apache.logging.log4j.core.config.Property;
+import org.apache.logging.log4j.core.util.ContextDataProvider;
import org.apache.logging.log4j.spi.ReadOnlyThreadContextMap;
+import org.apache.logging.log4j.status.StatusLogger;
+import org.apache.logging.log4j.util.LoaderUtil;
import org.apache.logging.log4j.util.ReadOnlyStringMap;
import org.apache.logging.log4j.util.StringMap;
@@ -44,6 +54,47 @@
*/
public class ThreadContextDataInjector {
+ private static Logger LOGGER = StatusLogger.getLogger();
+
+ /**
+ * ContextDataProviders loaded via OSGi.
+ */
+ public static Collection<ContextDataProvider> contextDataProviders =
+ new ConcurrentLinkedDeque<>();
+
+ private static volatile List<ContextDataProvider> serviceProviders = null;
+ private static final Lock providerLock = new ReentrantLock();
+
+ public static void initServiceProviders() {
+ if (serviceProviders == null) {
+ providerLock.lock();
+ try {
+ if (serviceProviders == null) {
+ serviceProviders = getServiceProviders();
+ }
+ } finally {
+ providerLock.unlock();
+ }
+ }
+ }
+
+ private static List<ContextDataProvider> getServiceProviders() {
+ List<ContextDataProvider> providers = new ArrayList<>();
+ for (final ClassLoader classLoader : LoaderUtil.getClassLoaders()) {
+ try {
+ for (final ContextDataProvider provider : ServiceLoader.load(ContextDataProvider.class, classLoader)) {
+ if (providers.stream().noneMatch((p) -> p.getClass().isAssignableFrom(provider.getClass()))) {
+ providers.add(provider);
+ }
+ }
+ } catch (final Throwable ex) {
+ LOGGER.debug("Unable to access Context Data Providers {}", ex.getMessage());
+ }
+ }
+ return providers;
+ }
+
+
/**
* Default {@code ContextDataInjector} for the legacy {@code Map<String, String>}-based ThreadContext (which is
* also the ThreadContext implementation used for web applications).
@@ -52,24 +103,39 @@
*/
public static class ForDefaultThreadContextMap implements ContextDataInjector {
+ private final List<ContextDataProvider> providers;
+
+ public ForDefaultThreadContextMap() {
+ providers = getProviders();
+ }
+
/**
* Puts key-value pairs from both the specified list of properties as well as the thread context into the
* specified reusable StringMap.
*
* @param props list of configuration properties, may be {@code null}
- * @param ignore a {@code StringMap} instance from the log event
+ * @param contextData a {@code StringMap} instance from the log event
* @return a {@code StringMap} combining configuration properties with thread context data
*/
@Override
- public StringMap injectContextData(final List<Property> props, final StringMap ignore) {
+ public StringMap injectContextData(final List<Property> props, final StringMap contextData) {
- final Map<String, String> copy = ThreadContext.getImmutableContext();
+ final Map<String, String> copy;
+
+ if (providers.size() == 1) {
+ copy = providers.get(0).supplyContextData();
+ } else {
+ copy = new HashMap<>();
+ for (ContextDataProvider provider : providers) {
+ copy.putAll(provider.supplyContextData());
+ }
+ }
// The DefaultThreadContextMap stores context data in a Map<String, String>.
// This is a copy-on-write data structure so we are sure ThreadContext changes will not affect our copy.
- // If there are no configuration properties returning a thin wrapper around the copy
+ // If there are no configuration properties or providers returning a thin wrapper around the copy
// is faster than copying the elements into the LogEvent's reusable StringMap.
- if (props == null || props.isEmpty()) {
+ if ((props == null || props.isEmpty())) {
// this will replace the LogEvent's context data with the returned instance.
// NOTE: must mark as frozen or downstream components may attempt to modify (UnsupportedOperationEx)
return copy.isEmpty() ? ContextDataFactory.emptyFrozenContextData() : frozenStringMap(copy);
@@ -114,6 +180,12 @@
* This injector always puts key-value pairs into the specified reusable StringMap.
*/
public static class ForGarbageFreeThreadContextMap implements ContextDataInjector {
+ private final List<ContextDataProvider> providers;
+
+ public ForGarbageFreeThreadContextMap() {
+ this.providers = getProviders();
+ }
+
/**
* Puts key-value pairs from both the specified list of properties as well as the thread context into the
* specified reusable StringMap.
@@ -128,9 +200,9 @@
// StringMap. We cannot return the ThreadContext's internal data structure because it may be modified later
// and such modifications should not be reflected in the log event.
copyProperties(props, reusable);
-
- final ReadOnlyStringMap immutableCopy = ThreadContext.getThreadContextMap().getReadOnlyContextData();
- reusable.putAll(immutableCopy);
+ for (int i = 0; i < providers.size(); ++i) {
+ reusable.putAll(providers.get(i).supplyStringMap());
+ }
return reusable;
}
@@ -149,6 +221,11 @@
* specified reusable StringMap.
*/
public static class ForCopyOnWriteThreadContextMap implements ContextDataInjector {
+ private final List<ContextDataProvider> providers;
+
+ public ForCopyOnWriteThreadContextMap() {
+ this.providers = getProviders();
+ }
/**
* If there are no configuration properties, this injector will return the thread context's internal data
* structure. Otherwise the configuration properties are combined with the thread context key-value pairs into the
@@ -162,17 +239,25 @@
public StringMap injectContextData(final List<Property> props, final StringMap ignore) {
// If there are no configuration properties we want to just return the ThreadContext's StringMap:
// it is a copy-on-write data structure so we are sure ThreadContext changes will not affect our copy.
- final StringMap immutableCopy = ThreadContext.getThreadContextMap().getReadOnlyContextData();
- if (props == null || props.isEmpty()) {
- return immutableCopy; // this will replace the LogEvent's context data with the returned instance
+ if (providers.size() == 1 && (props == null || props.isEmpty())) {
+ // this will replace the LogEvent's context data with the returned instance
+ return providers.get(0).supplyStringMap();
+ }
+ int count = props == null ? 0 : props.size();
+ StringMap[] maps = new StringMap[providers.size()];
+ for (int i = 0; i < providers.size(); ++i) {
+ maps[i] = providers.get(i).supplyStringMap();
+ count += maps[i].size();
}
// However, if the list of Properties is non-empty we need to combine the properties and the ThreadContext
// data. Note that we cannot reuse the specified StringMap: some Loggers may have properties defined
// and others not, so the LogEvent's context data may have been replaced with an immutable copy from
// the ThreadContext - this will throw an UnsupportedOperationException if we try to modify it.
- final StringMap result = ContextDataFactory.createContextData(props.size() + immutableCopy.size());
+ final StringMap result = ContextDataFactory.createContextData(count);
copyProperties(props, result);
- result.putAll(immutableCopy);
+ for (StringMap map : maps) {
+ result.putAll(map);
+ }
return result;
}
@@ -196,4 +281,13 @@
}
}
}
+
+ private static List<ContextDataProvider> getProviders() {
+ initServiceProviders();
+ final List<ContextDataProvider> providers = new ArrayList<>(contextDataProviders);
+ if (serviceProviders != null) {
+ providers.addAll(serviceProviders);
+ }
+ return providers;
+ }
}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ThreadContextDataProvider.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ThreadContextDataProvider.java
new file mode 100644
index 0000000..230123e
--- /dev/null
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ThreadContextDataProvider.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.logging.log4j.core.impl;
+
+import org.apache.logging.log4j.ThreadContext;
+import org.apache.logging.log4j.core.util.ContextDataProvider;
+import org.apache.logging.log4j.util.StringMap;
+
+import java.util.Map;
+
+/**
+ * ContextDataProvider for ThreadContext data.
+ */
+public class ThreadContextDataProvider implements ContextDataProvider {
+
+ @Override
+ public Map<String, String> supplyContextData() {
+ return ThreadContext.getImmutableContext();
+ }
+
+ @Override
+ public StringMap supplyStringMap() {
+ return ThreadContext.getThreadContextMap().getReadOnlyContextData();
+ }
+}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ThrowableProxy.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ThrowableProxy.java
index 42ed186..c948a0c 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ThrowableProxy.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ThrowableProxy.java
@@ -17,9 +17,6 @@
package org.apache.logging.log4j.core.impl;
import java.io.Serializable;
-import java.net.URL;
-import java.security.CodeSource;
-import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
@@ -30,9 +27,6 @@
import org.apache.logging.log4j.core.pattern.PlainTextRenderer;
import org.apache.logging.log4j.core.pattern.TextRenderer;
-import org.apache.logging.log4j.core.util.Loader;
-import org.apache.logging.log4j.status.StatusLogger;
-import org.apache.logging.log4j.util.LoaderUtil;
import org.apache.logging.log4j.util.StackLocatorUtil;
import org.apache.logging.log4j.util.Strings;
@@ -54,29 +48,6 @@
*/
public class ThrowableProxy implements Serializable {
- private static final String TAB = "\t";
- private static final String CAUSED_BY_LABEL = "Caused by: ";
- private static final String SUPPRESSED_LABEL = "Suppressed: ";
- private static final String WRAPPED_BY_LABEL = "Wrapped by: ";
-
- /**
- * Cached StackTracePackageElement and ClassLoader.
- * <p>
- * Consider this class private.
- * </p>
- */
- static class CacheEntry {
- private final ExtendedClassInfo element;
- private final ClassLoader loader;
-
- public CacheEntry(final ExtendedClassInfo element, final ClassLoader loader) {
- this.element = element;
- this.loader = loader;
- }
- }
-
- private static final ThrowableProxy[] EMPTY_THROWABLE_PROXY_ARRAY = new ThrowableProxy[0];
-
private static final char EOL = '\n';
private static final String EOL_STR = String.valueOf(EOL);
@@ -110,7 +81,7 @@
this.causeProxy = null;
this.message = null;
this.localizedMessage = null;
- this.suppressedProxies = EMPTY_THROWABLE_PROXY_ARRAY;
+ this.suppressedProxies = ThrowableProxyHelper.EMPTY_THROWABLE_PROXY_ARRAY;
}
/**
@@ -128,19 +99,19 @@
* @param throwable The Throwable to wrap, must not be null.
* @param visited The set of visited suppressed exceptions.
*/
- private ThrowableProxy(final Throwable throwable, final Set<Throwable> visited) {
+ ThrowableProxy(final Throwable throwable, final Set<Throwable> visited) {
this.throwable = throwable;
this.name = throwable.getClass().getName();
this.message = throwable.getMessage();
this.localizedMessage = throwable.getLocalizedMessage();
- final Map<String, CacheEntry> map = new HashMap<>();
+ final Map<String, ThrowableProxyHelper.CacheEntry> map = new HashMap<>();
final Stack<Class<?>> stack = StackLocatorUtil.getCurrentStackTrace();
- this.extendedStackTrace = this.toExtendedStackTrace(stack, map, null, throwable.getStackTrace());
+ this.extendedStackTrace = ThrowableProxyHelper.toExtendedStackTrace(this, stack, map, null, throwable.getStackTrace());
final Throwable throwableCause = throwable.getCause();
final Set<Throwable> causeVisited = new HashSet<>(1);
this.causeProxy = throwableCause == null ? null : new ThrowableProxy(throwable, stack, map, throwableCause,
visited, causeVisited);
- this.suppressedProxies = this.toSuppressedProxies(throwable, visited);
+ this.suppressedProxies = ThrowableProxyHelper.toSuppressedProxies(throwable, visited);
}
/**
@@ -153,7 +124,8 @@
* @param suppressedVisited TODO
* @param causeVisited TODO
*/
- private ThrowableProxy(final Throwable parent, final Stack<Class<?>> stack, final Map<String, CacheEntry> map,
+ private ThrowableProxy(final Throwable parent, final Stack<Class<?>> stack,
+ final Map<String, ThrowableProxyHelper.CacheEntry> map,
final Throwable cause, final Set<Throwable> suppressedVisited,
final Set<Throwable> causeVisited) {
causeVisited.add(cause);
@@ -161,11 +133,11 @@
this.name = cause.getClass().getName();
this.message = this.throwable.getMessage();
this.localizedMessage = this.throwable.getLocalizedMessage();
- this.extendedStackTrace = this.toExtendedStackTrace(stack, map, parent.getStackTrace(), cause.getStackTrace());
+ this.extendedStackTrace = ThrowableProxyHelper.toExtendedStackTrace(this, stack, map, parent.getStackTrace(), cause.getStackTrace());
final Throwable causeCause = cause.getCause();
this.causeProxy = causeCause == null || causeVisited.contains(causeCause) ? null : new ThrowableProxy(parent,
stack, map, causeCause, suppressedVisited, causeVisited);
- this.suppressedProxies = this.toSuppressedProxies(cause, suppressedVisited);
+ this.suppressedProxies = ThrowableProxyHelper.toSuppressedProxies(cause, suppressedVisited);
}
@Override
@@ -206,111 +178,6 @@
return true;
}
- private void formatCause(final StringBuilder sb, final String prefix, final ThrowableProxy cause,
- final List<String> ignorePackages, final TextRenderer textRenderer, final String suffix, String lineSeparator) {
- formatThrowableProxy(sb, prefix, CAUSED_BY_LABEL, cause, ignorePackages, textRenderer, suffix, lineSeparator);
- }
-
- private void formatThrowableProxy(final StringBuilder sb, final String prefix, final String causeLabel,
- final ThrowableProxy throwableProxy, final List<String> ignorePackages,
- final TextRenderer textRenderer, final String suffix, String lineSeparator) {
- if (throwableProxy == null) {
- return;
- }
- textRenderer.render(prefix, sb, "Prefix");
- textRenderer.render(causeLabel, sb, "CauseLabel");
- throwableProxy.renderOn(sb, textRenderer);
- renderSuffix(suffix, sb, textRenderer);
- textRenderer.render(lineSeparator, sb, "Text");
- this.formatElements(sb, prefix, throwableProxy.commonElementCount,
- throwableProxy.getStackTrace(), throwableProxy.extendedStackTrace, ignorePackages, textRenderer, suffix, lineSeparator);
- this.formatSuppressed(sb, prefix + TAB, throwableProxy.suppressedProxies, ignorePackages, textRenderer, suffix, lineSeparator);
- this.formatCause(sb, prefix, throwableProxy.causeProxy, ignorePackages, textRenderer, suffix, lineSeparator);
- }
-
- void renderOn(final StringBuilder output, final TextRenderer textRenderer) {
- final String msg = this.message;
- textRenderer.render(this.name, output, "Name");
- if (msg != null) {
- textRenderer.render(": ", output, "NameMessageSeparator");
- textRenderer.render(msg, output, "Message");
- }
- }
-
- private void formatSuppressed(final StringBuilder sb, final String prefix, final ThrowableProxy[] suppressedProxies,
- final List<String> ignorePackages, final TextRenderer textRenderer, final String suffix, String lineSeparator) {
- if (suppressedProxies == null) {
- return;
- }
- for (final ThrowableProxy suppressedProxy : suppressedProxies) {
- formatThrowableProxy(sb, prefix, SUPPRESSED_LABEL, suppressedProxy, ignorePackages, textRenderer, suffix, lineSeparator);
- }
- }
-
- private void formatElements(final StringBuilder sb, final String prefix, final int commonCount,
- final StackTraceElement[] causedTrace, final ExtendedStackTraceElement[] extStackTrace,
- final List<String> ignorePackages, final TextRenderer textRenderer, final String suffix, String lineSeparator) {
- if (ignorePackages == null || ignorePackages.isEmpty()) {
- for (final ExtendedStackTraceElement element : extStackTrace) {
- this.formatEntry(element, sb, prefix, textRenderer, suffix, lineSeparator);
- }
- } else {
- int count = 0;
- for (int i = 0; i < extStackTrace.length; ++i) {
- if (!this.ignoreElement(causedTrace[i], ignorePackages)) {
- if (count > 0) {
- appendSuppressedCount(sb, prefix, count, textRenderer, suffix, lineSeparator);
- count = 0;
- }
- this.formatEntry(extStackTrace[i], sb, prefix, textRenderer, suffix, lineSeparator);
- } else {
- ++count;
- }
- }
- if (count > 0) {
- appendSuppressedCount(sb, prefix, count, textRenderer, suffix, lineSeparator);
- }
- }
- if (commonCount != 0) {
- textRenderer.render(prefix, sb, "Prefix");
- textRenderer.render("\t... ", sb, "More");
- textRenderer.render(Integer.toString(commonCount), sb, "More");
- textRenderer.render(" more", sb, "More");
- renderSuffix(suffix, sb, textRenderer);
- textRenderer.render(lineSeparator, sb, "Text");
- }
- }
-
- private void renderSuffix(final String suffix, final StringBuilder sb, final TextRenderer textRenderer) {
- if (!suffix.isEmpty()) {
- textRenderer.render(" ", sb, "Suffix");
- textRenderer.render(suffix, sb, "Suffix");
- }
- }
-
- private void appendSuppressedCount(final StringBuilder sb, final String prefix, final int count,
- final TextRenderer textRenderer, final String suffix, String lineSeparator) {
- textRenderer.render(prefix, sb, "Prefix");
- if (count == 1) {
- textRenderer.render("\t... ", sb, "Suppressed");
- } else {
- textRenderer.render("\t... suppressed ", sb, "Suppressed");
- textRenderer.render(Integer.toString(count), sb, "Suppressed");
- textRenderer.render(" lines", sb, "Suppressed");
- }
- renderSuffix(suffix, sb, textRenderer);
- textRenderer.render(lineSeparator, sb, "Text");
- }
-
- private void formatEntry(final ExtendedStackTraceElement extStackTraceElement, final StringBuilder sb,
- final String prefix, final TextRenderer textRenderer, final String suffix, String lineSeparator) {
- textRenderer.render(prefix, sb, "Prefix");
- textRenderer.render("\tat ", sb, "At");
- extStackTraceElement.renderOn(sb, textRenderer);
- renderSuffix(suffix, sb, textRenderer);
- textRenderer.render(lineSeparator, sb, "Text");
- }
-
/**
* Formats the specified Throwable.
* @param sb StringBuilder to contain the formatted Throwable.
@@ -359,17 +226,7 @@
@SuppressWarnings("ThrowableResultOfMethodCallIgnored")
public void formatWrapper(final StringBuilder sb, final ThrowableProxy cause, final List<String> ignorePackages,
final TextRenderer textRenderer, final String suffix, final String lineSeparator) {
- final Throwable caused = cause.getCauseProxy() != null ? cause.getCauseProxy().getThrowable() : null;
- if (caused != null) {
- this.formatWrapper(sb, cause.causeProxy, ignorePackages, textRenderer, suffix, lineSeparator);
- sb.append(WRAPPED_BY_LABEL);
- renderSuffix(suffix, sb, textRenderer);
- }
- cause.renderOn(sb, textRenderer);
- renderSuffix(suffix, sb, textRenderer);
- textRenderer.render(lineSeparator, sb, "Text");
- this.formatElements(sb, Strings.EMPTY, cause.commonElementCount,
- cause.getThrowable().getStackTrace(), cause.extendedStackTrace, ignorePackages, textRenderer, suffix, lineSeparator);
+ ThrowableProxyRenderer.formatWrapper(sb, cause, ignorePackages, textRenderer, suffix, lineSeparator);
}
public ThrowableProxy getCauseProxy() {
@@ -420,16 +277,7 @@
*/
public String getCauseStackTraceAsString(final List<String> ignorePackages, final TextRenderer textRenderer, final String suffix, final String lineSeparator) {
final StringBuilder sb = new StringBuilder();
- if (this.causeProxy != null) {
- this.formatWrapper(sb, this.causeProxy, ignorePackages, textRenderer, suffix, lineSeparator);
- sb.append(WRAPPED_BY_LABEL);
- renderSuffix(suffix, sb, textRenderer);
- }
- this.renderOn(sb, textRenderer);
- renderSuffix(suffix, sb, textRenderer);
- textRenderer.render(lineSeparator, sb, "Text");
- this.formatElements(sb, Strings.EMPTY, 0, this.throwable.getStackTrace(), this.extendedStackTrace,
- ignorePackages, textRenderer, suffix, lineSeparator);
+ ThrowableProxyRenderer.formatCauseStackTrace(this, sb, ignorePackages, textRenderer, suffix, lineSeparator);
return sb.toString();
}
@@ -444,6 +292,17 @@
}
/**
+ * Set the value of {@link ThrowableProxy#commonElementCount}.
+ *
+ * Method is package-private, to be used internally for initialization.
+ *
+ * @param value New value of commonElementCount.
+ */
+ void setCommonElementCount(int value) {
+ this.commonElementCount = value;
+ }
+
+ /**
* Gets the stack trace including packaging information.
*
* @return The stack trace including packaging information.
@@ -519,15 +378,7 @@
* @param lineSeparator The end-of-line separator.
*/
public void formatExtendedStackTraceTo(final StringBuilder sb, final List<String> ignorePackages, final TextRenderer textRenderer, final String suffix, final String lineSeparator) {
- textRenderer.render(name, sb, "Name");
- textRenderer.render(": ", sb, "NameMessageSeparator");
- textRenderer.render(this.message, sb, "Message");
- renderSuffix(suffix, sb, textRenderer);
- textRenderer.render(lineSeparator, sb, "Text");
- final StackTraceElement[] causedTrace = this.throwable != null ? this.throwable.getStackTrace() : null;
- this.formatElements(sb, Strings.EMPTY, 0, causedTrace, this.extendedStackTrace, ignorePackages, textRenderer, suffix, lineSeparator);
- this.formatSuppressed(sb, TAB, this.suppressedProxies, ignorePackages, textRenderer, suffix, lineSeparator);
- this.formatCause(sb, Strings.EMPTY, this.causeProxy, ignorePackages, textRenderer, suffix, lineSeparator);
+ ThrowableProxyRenderer.formatExtendedStackTraceTo(this, sb, ignorePackages, textRenderer, suffix, lineSeparator);
}
public String getLocalizedMessage() {
@@ -599,193 +450,9 @@
return result;
}
- private boolean ignoreElement(final StackTraceElement element, final List<String> ignorePackages) {
- if (ignorePackages != null) {
- final String className = element.getClassName();
- for (final String pkg : ignorePackages) {
- if (className.startsWith(pkg)) {
- return true;
- }
- }
- }
- return false;
- }
-
- /**
- * Loads classes not located via Reflection.getCallerClass.
- *
- * @param lastLoader The ClassLoader that loaded the Class that called this Class.
- * @param className The name of the Class.
- * @return The Class object for the Class or null if it could not be located.
- */
- private Class<?> loadClass(final ClassLoader lastLoader, final String className) {
- // XXX: this is overly complicated
- Class<?> clazz;
- if (lastLoader != null) {
- try {
- clazz = lastLoader.loadClass(className);
- if (clazz != null) {
- return clazz;
- }
- } catch (final Throwable ignore) {
- // Ignore exception.
- }
- }
- try {
- clazz = LoaderUtil.loadClass(className);
- } catch (final ClassNotFoundException | NoClassDefFoundError e) {
- return loadClass(className);
- } catch (final SecurityException e) {
- return null;
- }
- return clazz;
- }
-
- private Class<?> loadClass(final String className) {
- try {
- return Loader.loadClass(className, this.getClass().getClassLoader());
- } catch (final ClassNotFoundException | NoClassDefFoundError | SecurityException e) {
- return null;
- }
- }
-
- /**
- * Construct the CacheEntry from the Class's information.
- *
- * @param stackTraceElement The stack trace element
- * @param callerClass The Class.
- * @param exact True if the class was obtained via Reflection.getCallerClass.
- * @return The CacheEntry.
- */
- private CacheEntry toCacheEntry(final Class<?> callerClass, final boolean exact) {
- String location = "?";
- String version = "?";
- ClassLoader lastLoader = null;
- if (callerClass != null) {
- try {
- final CodeSource source = callerClass.getProtectionDomain().getCodeSource();
- if (source != null) {
- final URL locationURL = source.getLocation();
- if (locationURL != null) {
- final String str = locationURL.toString().replace('\\', '/');
- int index = str.lastIndexOf("/");
- if (index >= 0 && index == str.length() - 1) {
- index = str.lastIndexOf("/", index - 1);
- location = str.substring(index + 1);
- } else {
- location = str.substring(index + 1);
- }
- }
- }
- } catch (final Exception ex) {
- // Ignore the exception.
- }
- final Package pkg = callerClass.getPackage();
- if (pkg != null) {
- final String ver = pkg.getImplementationVersion();
- if (ver != null) {
- version = ver;
- }
- }
- try {
- lastLoader = callerClass.getClassLoader();
- } catch (final SecurityException e) {
- lastLoader = null;
- }
- }
- return new CacheEntry(new ExtendedClassInfo(exact, location, version), lastLoader);
- }
-
- /**
- * Resolve all the stack entries in this stack trace that are not common with the parent.
- *
- * @param stack The callers Class stack.
- * @param map The cache of CacheEntry objects.
- * @param rootTrace The first stack trace resolve or null.
- * @param stackTrace The stack trace being resolved.
- * @return The StackTracePackageElement array.
- */
- ExtendedStackTraceElement[] toExtendedStackTrace(final Stack<Class<?>> stack, final Map<String, CacheEntry> map,
- final StackTraceElement[] rootTrace,
- final StackTraceElement[] stackTrace) {
- int stackLength;
- if (rootTrace != null) {
- int rootIndex = rootTrace.length - 1;
- int stackIndex = stackTrace.length - 1;
- while (rootIndex >= 0 && stackIndex >= 0 && rootTrace[rootIndex].equals(stackTrace[stackIndex])) {
- --rootIndex;
- --stackIndex;
- }
- this.commonElementCount = stackTrace.length - 1 - stackIndex;
- stackLength = stackIndex + 1;
- } else {
- this.commonElementCount = 0;
- stackLength = stackTrace.length;
- }
- final ExtendedStackTraceElement[] extStackTrace = new ExtendedStackTraceElement[stackLength];
- Class<?> clazz = stack.isEmpty() ? null : stack.peek();
- ClassLoader lastLoader = null;
- for (int i = stackLength - 1; i >= 0; --i) {
- final StackTraceElement stackTraceElement = stackTrace[i];
- final String className = stackTraceElement.getClassName();
- // The stack returned from getCurrentStack may be missing entries for java.lang.reflect.Method.invoke()
- // and its implementation. The Throwable might also contain stack entries that are no longer
- // present as those methods have returned.
- ExtendedClassInfo extClassInfo;
- if (clazz != null && className.equals(clazz.getName())) {
- final CacheEntry entry = this.toCacheEntry(clazz, true);
- extClassInfo = entry.element;
- lastLoader = entry.loader;
- stack.pop();
- clazz = stack.isEmpty() ? null : stack.peek();
- } else {
- final CacheEntry cacheEntry = map.get(className);
- if (cacheEntry != null) {
- final CacheEntry entry = cacheEntry;
- extClassInfo = entry.element;
- if (entry.loader != null) {
- lastLoader = entry.loader;
- }
- } else {
- final CacheEntry entry = this.toCacheEntry(this.loadClass(lastLoader, className), false);
- extClassInfo = entry.element;
- map.put(className, entry);
- if (entry.loader != null) {
- lastLoader = entry.loader;
- }
- }
- }
- extStackTrace[i] = new ExtendedStackTraceElement(stackTraceElement, extClassInfo);
- }
- return extStackTrace;
- }
-
@Override
public String toString() {
final String msg = this.message;
return msg != null ? this.name + ": " + msg : this.name;
}
-
- private ThrowableProxy[] toSuppressedProxies(final Throwable thrown, Set<Throwable> suppressedVisited) {
- try {
- final Throwable[] suppressed = thrown.getSuppressed();
- if (suppressed == null || suppressed.length == 0) {
- return EMPTY_THROWABLE_PROXY_ARRAY;
- }
- final List<ThrowableProxy> proxies = new ArrayList<>(suppressed.length);
- if (suppressedVisited == null) {
- suppressedVisited = new HashSet<>(suppressed.length);
- }
- for (int i = 0; i < suppressed.length; i++) {
- final Throwable candidate = suppressed[i];
- if (suppressedVisited.add(candidate)) {
- proxies.add(new ThrowableProxy(candidate, suppressedVisited));
- }
- }
- return proxies.toArray(new ThrowableProxy[proxies.size()]);
- } catch (final Exception e) {
- StatusLogger.getLogger().error(e);
- }
- return null;
- }
}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ThrowableProxyHelper.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ThrowableProxyHelper.java
new file mode 100644
index 0000000..547ca09
--- /dev/null
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ThrowableProxyHelper.java
@@ -0,0 +1,234 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.logging.log4j.core.impl;
+
+import org.apache.logging.log4j.core.util.Loader;
+import org.apache.logging.log4j.status.StatusLogger;
+import org.apache.logging.log4j.util.LoaderUtil;
+
+import java.net.URL;
+import java.security.CodeSource;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.Stack;
+
+/**
+ * {@link ThrowableProxyHelper} provides utilities required to initialize a new {@link ThrowableProxy}
+ * instance.
+ */
+class ThrowableProxyHelper {
+
+ static final ThrowableProxy[] EMPTY_THROWABLE_PROXY_ARRAY = new ThrowableProxy[0];
+
+ private ThrowableProxyHelper() {
+ // Utility Class
+ }
+
+ /**
+ * Cached StackTracePackageElement and ClassLoader.
+ * <p>
+ * Consider this class private.
+ * </p>
+ */
+ static final class CacheEntry {
+ private final ExtendedClassInfo element;
+ private final ClassLoader loader;
+
+ private CacheEntry(final ExtendedClassInfo element, final ClassLoader loader) {
+ this.element = element;
+ this.loader = loader;
+ }
+ }
+
+ /**
+ * Resolve all the stack entries in this stack trace that are not common with the parent.
+ *
+ * @param src Instance for which to build an extended stack trace.
+ * @param stack The callers Class stack.
+ * @param map The cache of CacheEntry objects.
+ * @param rootTrace The first stack trace resolve or null.
+ * @param stackTrace The stack trace being resolved.
+ * @return The StackTracePackageElement array.
+ */
+ static ExtendedStackTraceElement[] toExtendedStackTrace(
+ final ThrowableProxy src,
+ final Stack<Class<?>> stack, final Map<String, CacheEntry> map,
+ final StackTraceElement[] rootTrace,
+ final StackTraceElement[] stackTrace) {
+ int stackLength;
+ if (rootTrace != null) {
+ int rootIndex = rootTrace.length - 1;
+ int stackIndex = stackTrace.length - 1;
+ while (rootIndex >= 0 && stackIndex >= 0 && rootTrace[rootIndex].equals(stackTrace[stackIndex])) {
+ --rootIndex;
+ --stackIndex;
+ }
+ src.setCommonElementCount(stackTrace.length - 1 - stackIndex);
+ stackLength = stackIndex + 1;
+ } else {
+ src.setCommonElementCount(0);
+ stackLength = stackTrace.length;
+ }
+ final ExtendedStackTraceElement[] extStackTrace = new ExtendedStackTraceElement[stackLength];
+ Class<?> clazz = stack.isEmpty() ? null : stack.peek();
+ ClassLoader lastLoader = null;
+ for (int i = stackLength - 1; i >= 0; --i) {
+ final StackTraceElement stackTraceElement = stackTrace[i];
+ final String className = stackTraceElement.getClassName();
+ // The stack returned from getCurrentStack may be missing entries for java.lang.reflect.Method.invoke()
+ // and its implementation. The Throwable might also contain stack entries that are no longer
+ // present as those methods have returned.
+ ExtendedClassInfo extClassInfo;
+ if (clazz != null && className.equals(clazz.getName())) {
+ final CacheEntry entry = toCacheEntry(clazz, true);
+ extClassInfo = entry.element;
+ lastLoader = entry.loader;
+ stack.pop();
+ clazz = stack.isEmpty() ? null : stack.peek();
+ } else {
+ final CacheEntry cacheEntry = map.get(className);
+ if (cacheEntry != null) {
+ final CacheEntry entry = cacheEntry;
+ extClassInfo = entry.element;
+ if (entry.loader != null) {
+ lastLoader = entry.loader;
+ }
+ } else {
+ final CacheEntry entry = toCacheEntry(ThrowableProxyHelper.loadClass(lastLoader, className), false);
+ extClassInfo = entry.element;
+ map.put(className, entry);
+ if (entry.loader != null) {
+ lastLoader = entry.loader;
+ }
+ }
+ }
+ extStackTrace[i] = new ExtendedStackTraceElement(stackTraceElement, extClassInfo);
+ }
+ return extStackTrace;
+ }
+
+ static ThrowableProxy[] toSuppressedProxies(final Throwable thrown, Set<Throwable> suppressedVisited) {
+ try {
+ final Throwable[] suppressed = thrown.getSuppressed();
+ if (suppressed == null || suppressed.length == 0) {
+ return EMPTY_THROWABLE_PROXY_ARRAY;
+ }
+ final List<ThrowableProxy> proxies = new ArrayList<>(suppressed.length);
+ if (suppressedVisited == null) {
+ suppressedVisited = new HashSet<>(suppressed.length);
+ }
+ for (int i = 0; i < suppressed.length; i++) {
+ final Throwable candidate = suppressed[i];
+ if (suppressedVisited.add(candidate)) {
+ proxies.add(new ThrowableProxy(candidate, suppressedVisited));
+ }
+ }
+ return proxies.toArray(new ThrowableProxy[proxies.size()]);
+ } catch (final Exception e) {
+ StatusLogger.getLogger().error(e);
+ }
+ return null;
+ }
+
+ /**
+ * Construct the CacheEntry from the Class's information.
+ *
+ * @param callerClass The Class.
+ * @param exact True if the class was obtained via Reflection.getCallerClass.
+ * @return The CacheEntry.
+ */
+ private static CacheEntry toCacheEntry(final Class<?> callerClass, final boolean exact) {
+ String location = "?";
+ String version = "?";
+ ClassLoader lastLoader = null;
+ if (callerClass != null) {
+ try {
+ final CodeSource source = callerClass.getProtectionDomain().getCodeSource();
+ if (source != null) {
+ final URL locationURL = source.getLocation();
+ if (locationURL != null) {
+ final String str = locationURL.toString().replace('\\', '/');
+ int index = str.lastIndexOf("/");
+ if (index >= 0 && index == str.length() - 1) {
+ index = str.lastIndexOf("/", index - 1);
+ location = str.substring(index + 1);
+ } else {
+ location = str.substring(index + 1);
+ }
+ }
+ }
+ } catch (final Exception ex) {
+ // Ignore the exception.
+ }
+ final Package pkg = callerClass.getPackage();
+ if (pkg != null) {
+ final String ver = pkg.getImplementationVersion();
+ if (ver != null) {
+ version = ver;
+ }
+ }
+ try {
+ lastLoader = callerClass.getClassLoader();
+ } catch (final SecurityException e) {
+ lastLoader = null;
+ }
+ }
+ return new CacheEntry(new ExtendedClassInfo(exact, location, version), lastLoader);
+ }
+
+
+ /**
+ * Loads classes not located via Reflection.getCallerClass.
+ *
+ * @param lastLoader The ClassLoader that loaded the Class that called this Class.
+ * @param className The name of the Class.
+ * @return The Class object for the Class or null if it could not be located.
+ */
+ private static Class<?> loadClass(final ClassLoader lastLoader, final String className) {
+ // XXX: this is overly complicated
+ Class<?> clazz;
+ if (lastLoader != null) {
+ try {
+ clazz = lastLoader.loadClass(className);
+ if (clazz != null) {
+ return clazz;
+ }
+ } catch (final Throwable ignore) {
+ // Ignore exception.
+ }
+ }
+ try {
+ clazz = LoaderUtil.loadClass(className);
+ } catch (final ClassNotFoundException | NoClassDefFoundError e) {
+ return loadClass(className);
+ } catch (final SecurityException e) {
+ return null;
+ }
+ return clazz;
+ }
+
+ private static Class<?> loadClass(final String className) {
+ try {
+ return Loader.loadClass(className, ThrowableProxyHelper.class.getClassLoader());
+ } catch (final ClassNotFoundException | NoClassDefFoundError | SecurityException e) {
+ return null;
+ }
+ }
+}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ThrowableProxyRenderer.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ThrowableProxyRenderer.java
new file mode 100644
index 0000000..d86d2bb
--- /dev/null
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/impl/ThrowableProxyRenderer.java
@@ -0,0 +1,217 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.logging.log4j.core.impl;
+
+import org.apache.logging.log4j.core.pattern.TextRenderer;
+import org.apache.logging.log4j.util.Strings;
+
+import java.util.List;
+
+/**
+ * {@link ThrowableProxyRenderer} is an internal utility providing the code to render a {@link ThrowableProxy}
+ * to a {@link StringBuilder}.
+ */
+class ThrowableProxyRenderer {
+
+ private static final String TAB = "\t";
+ private static final String CAUSED_BY_LABEL = "Caused by: ";
+ private static final String SUPPRESSED_LABEL = "Suppressed: ";
+ private static final String WRAPPED_BY_LABEL = "Wrapped by: ";
+
+ private ThrowableProxyRenderer() {
+ // Utility Class
+ }
+
+ @SuppressWarnings("ThrowableResultOfMethodCallIgnored")
+ static void formatWrapper(final StringBuilder sb, final ThrowableProxy cause, final List<String> ignorePackages,
+ final TextRenderer textRenderer, final String suffix, final String lineSeparator) {
+ final Throwable caused = cause.getCauseProxy() != null ? cause.getCauseProxy().getThrowable() : null;
+ if (caused != null) {
+ formatWrapper(sb, cause.getCauseProxy(), ignorePackages, textRenderer, suffix, lineSeparator);
+ sb.append(WRAPPED_BY_LABEL);
+ renderSuffix(suffix, sb, textRenderer);
+ }
+ renderOn(cause, sb, textRenderer);
+ renderSuffix(suffix, sb, textRenderer);
+ textRenderer.render(lineSeparator, sb, "Text");
+ formatElements(sb, Strings.EMPTY, cause.getCommonElementCount(),
+ cause.getThrowable().getStackTrace(), cause.getExtendedStackTrace(), ignorePackages, textRenderer, suffix, lineSeparator);
+ }
+
+ private static void formatCause(final StringBuilder sb, final String prefix, final ThrowableProxy cause,
+ final List<String> ignorePackages, final TextRenderer textRenderer, final String suffix, String lineSeparator) {
+ formatThrowableProxy(sb, prefix, CAUSED_BY_LABEL, cause, ignorePackages, textRenderer, suffix, lineSeparator);
+ }
+
+ private static void formatThrowableProxy(final StringBuilder sb, final String prefix, final String causeLabel,
+ final ThrowableProxy throwableProxy, final List<String> ignorePackages,
+ final TextRenderer textRenderer, final String suffix, String lineSeparator) {
+ if (throwableProxy == null) {
+ return;
+ }
+ textRenderer.render(prefix, sb, "Prefix");
+ textRenderer.render(causeLabel, sb, "CauseLabel");
+ renderOn(throwableProxy, sb, textRenderer);
+ renderSuffix(suffix, sb, textRenderer);
+ textRenderer.render(lineSeparator, sb, "Text");
+ formatElements(sb, prefix, throwableProxy.getCommonElementCount(),
+ throwableProxy.getStackTrace(), throwableProxy.getExtendedStackTrace(), ignorePackages, textRenderer, suffix, lineSeparator);
+ formatSuppressed(sb, prefix + TAB, throwableProxy.getSuppressedProxies(), ignorePackages, textRenderer, suffix, lineSeparator);
+ formatCause(sb, prefix, throwableProxy.getCauseProxy(), ignorePackages, textRenderer, suffix, lineSeparator);
+ }
+
+ private static void formatSuppressed(final StringBuilder sb, final String prefix, final ThrowableProxy[] suppressedProxies,
+ final List<String> ignorePackages, final TextRenderer textRenderer, final String suffix, String lineSeparator) {
+ if (suppressedProxies == null) {
+ return;
+ }
+ for (final ThrowableProxy suppressedProxy : suppressedProxies) {
+ formatThrowableProxy(sb, prefix, SUPPRESSED_LABEL, suppressedProxy, ignorePackages, textRenderer, suffix, lineSeparator);
+ }
+ }
+
+ private static void formatElements(final StringBuilder sb, final String prefix, final int commonCount,
+ final StackTraceElement[] causedTrace, final ExtendedStackTraceElement[] extStackTrace,
+ final List<String> ignorePackages, final TextRenderer textRenderer, final String suffix, String lineSeparator) {
+ if (ignorePackages == null || ignorePackages.isEmpty()) {
+ for (final ExtendedStackTraceElement element : extStackTrace) {
+ formatEntry(element, sb, prefix, textRenderer, suffix, lineSeparator);
+ }
+ } else {
+ int count = 0;
+ for (int i = 0; i < extStackTrace.length; ++i) {
+ if (!ignoreElement(causedTrace[i], ignorePackages)) {
+ if (count > 0) {
+ appendSuppressedCount(sb, prefix, count, textRenderer, suffix, lineSeparator);
+ count = 0;
+ }
+ formatEntry(extStackTrace[i], sb, prefix, textRenderer, suffix, lineSeparator);
+ } else {
+ ++count;
+ }
+ }
+ if (count > 0) {
+ appendSuppressedCount(sb, prefix, count, textRenderer, suffix, lineSeparator);
+ }
+ }
+ if (commonCount != 0) {
+ textRenderer.render(prefix, sb, "Prefix");
+ textRenderer.render("\t... ", sb, "More");
+ textRenderer.render(Integer.toString(commonCount), sb, "More");
+ textRenderer.render(" more", sb, "More");
+ renderSuffix(suffix, sb, textRenderer);
+ textRenderer.render(lineSeparator, sb, "Text");
+ }
+ }
+
+ private static void renderSuffix(final String suffix, final StringBuilder sb, final TextRenderer textRenderer) {
+ if (!suffix.isEmpty()) {
+ textRenderer.render(" ", sb, "Suffix");
+ textRenderer.render(suffix, sb, "Suffix");
+ }
+ }
+
+ private static void appendSuppressedCount(final StringBuilder sb, final String prefix, final int count,
+ final TextRenderer textRenderer, final String suffix, String lineSeparator) {
+ textRenderer.render(prefix, sb, "Prefix");
+ if (count == 1) {
+ textRenderer.render("\t... ", sb, "Suppressed");
+ } else {
+ textRenderer.render("\t... suppressed ", sb, "Suppressed");
+ textRenderer.render(Integer.toString(count), sb, "Suppressed");
+ textRenderer.render(" lines", sb, "Suppressed");
+ }
+ renderSuffix(suffix, sb, textRenderer);
+ textRenderer.render(lineSeparator, sb, "Text");
+ }
+
+ private static void formatEntry(final ExtendedStackTraceElement extStackTraceElement, final StringBuilder sb,
+ final String prefix, final TextRenderer textRenderer, final String suffix, String lineSeparator) {
+ textRenderer.render(prefix, sb, "Prefix");
+ textRenderer.render("\tat ", sb, "At");
+ extStackTraceElement.renderOn(sb, textRenderer);
+ renderSuffix(suffix, sb, textRenderer);
+ textRenderer.render(lineSeparator, sb, "Text");
+ }
+
+ private static boolean ignoreElement(final StackTraceElement element, final List<String> ignorePackages) {
+ if (ignorePackages != null) {
+ final String className = element.getClassName();
+ for (final String pkg : ignorePackages) {
+ if (className.startsWith(pkg)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Formats the stack trace including packaging information.
+ *
+ * @param src ThrowableProxy instance to format
+ * @param sb Destination.
+ * @param ignorePackages List of packages to be ignored in the trace.
+ * @param textRenderer The message renderer.
+ * @param suffix Append this to the end of each stack frame.
+ * @param lineSeparator The end-of-line separator.
+ */
+ static void formatExtendedStackTraceTo(ThrowableProxy src, final StringBuilder sb, final List<String> ignorePackages, final TextRenderer textRenderer, final String suffix, final String lineSeparator) {
+ textRenderer.render(src.getName(), sb, "Name");
+ textRenderer.render(": ", sb, "NameMessageSeparator");
+ textRenderer.render(src.getMessage(), sb, "Message");
+ renderSuffix(suffix, sb, textRenderer);
+ textRenderer.render(lineSeparator, sb, "Text");
+ final StackTraceElement[] causedTrace = src.getThrowable() != null ? src.getThrowable().getStackTrace() : null;
+ formatElements(sb, Strings.EMPTY, 0, causedTrace, src.getExtendedStackTrace(), ignorePackages, textRenderer, suffix, lineSeparator);
+ formatSuppressed(sb, TAB, src.getSuppressedProxies(), ignorePackages, textRenderer, suffix, lineSeparator);
+ formatCause(sb, Strings.EMPTY, src.getCauseProxy(), ignorePackages, textRenderer, suffix, lineSeparator);
+ }
+
+ /**
+ * Formats the Throwable that is the cause of the <pre>src</pre> Throwable.
+ *
+ * @param src Throwable whose cause to render
+ * @param sb Destination to render the formatted Throwable that caused this Throwable onto.
+ * @param ignorePackages The List of packages to be suppressed from the stack trace.
+ * @param textRenderer The text renderer.
+ * @param suffix Append this to the end of each stack frame.
+ * @param lineSeparator The end-of-line separator.
+ */
+ static void formatCauseStackTrace(final ThrowableProxy src, final StringBuilder sb, final List<String> ignorePackages, final TextRenderer textRenderer, final String suffix, final String lineSeparator) {
+ ThrowableProxy causeProxy = src.getCauseProxy();
+ if (causeProxy != null) {
+ formatWrapper(sb, causeProxy, ignorePackages, textRenderer, suffix, lineSeparator);
+ sb.append(WRAPPED_BY_LABEL);
+ ThrowableProxyRenderer.renderSuffix(suffix, sb, textRenderer);
+ }
+ renderOn(src, sb, textRenderer);
+ ThrowableProxyRenderer.renderSuffix(suffix, sb, textRenderer);
+ textRenderer.render(lineSeparator, sb, "Text");
+ ThrowableProxyRenderer.formatElements(sb, Strings.EMPTY, 0, src.getStackTrace(), src.getExtendedStackTrace(),
+ ignorePackages, textRenderer, suffix, lineSeparator);
+ }
+
+ private static void renderOn(final ThrowableProxy src, final StringBuilder output, final TextRenderer textRenderer) {
+ final String msg = src.getMessage();
+ textRenderer.render(src.getName(), output, "Name");
+ if (msg != null) {
+ textRenderer.render(": ", output, "NameMessageSeparator");
+ textRenderer.render(msg, output, "Message");
+ }
+ }
+}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/AbstractLayout.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/AbstractLayout.java
index c8bb1be..65bcc52 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/AbstractLayout.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/AbstractLayout.java
@@ -24,7 +24,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.plugins.PluginBuilderAttribute;
+import org.apache.logging.log4j.plugins.PluginBuilderAttribute;
import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
import org.apache.logging.log4j.status.StatusLogger;
@@ -114,20 +114,6 @@
/**
* Constructs a layout with an optional header and footer.
*
- * @param header
- * The header to include when the stream is opened. May be null.
- * @param footer
- * The footer to add when the stream is closed. May be null.
- * @deprecated Use {@link #AbstractLayout(Configuration, byte[], byte[])}
- */
- @Deprecated
- public AbstractLayout(final byte[] header, final byte[] footer) {
- this(null, header, footer);
- }
-
- /**
- * Constructs a layout with an optional header and footer.
- *
* @param configuration
* The configuration
* @param header
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/AbstractStringLayout.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/AbstractStringLayout.java
index d8403b6..0bb2b94 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/AbstractStringLayout.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/AbstractStringLayout.java
@@ -24,8 +24,8 @@
import org.apache.logging.log4j.core.StringLayout;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.LoggerConfig;
-import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
-import org.apache.logging.log4j.core.config.plugins.PluginElement;
+import org.apache.logging.log4j.plugins.PluginBuilderAttribute;
+import org.apache.logging.log4j.plugins.PluginElement;
import org.apache.logging.log4j.core.impl.DefaultLogEventFactory;
import org.apache.logging.log4j.core.util.Constants;
import org.apache.logging.log4j.core.util.StringEncoder;
@@ -89,13 +89,23 @@
public interface Serializer {
String toSerializable(final LogEvent event);
+
+ default boolean requiresLocation() {
+ return false;
+ }
+
+ default StringBuilder toSerializable(final LogEvent event, final StringBuilder builder) {
+ builder.append(toSerializable(event));
+ return builder;
+ }
}
/**
* Variation of {@link Serializer} that avoids allocating temporary objects.
+ * As of 2.13 this interface was merged into the Serializer interface.
* @since 2.6
*/
- public interface Serializer2 {
+ public interface Serializer2 {
StringBuilder toSerializable(final LogEvent event, final StringBuilder builder);
}
@@ -131,14 +141,7 @@
// LOG4J2-1151: If the built-in JDK 8 encoders are available we should use them.
private static boolean isPreJava8() {
- final String version = System.getProperty("java.version");
- final String[] parts = version.split("\\.");
- try {
- final int major = Integer.parseInt(parts[1]);
- return major < 8;
- } catch (final Exception ex) {
- return true;
- }
+ return org.apache.logging.log4j.util.Constants.JAVA_MAJOR_VERSION < 8;
}
private static int size(final String property, final int defaultValue) {
@@ -276,7 +279,7 @@
protected byte[] serializeToBytes(final Serializer serializer, final byte[] defaultValue) {
final String serializable = serializeToString(serializer);
- if (serializer == null) {
+ if (serializable == null) {
return defaultValue;
}
return StringEncoder.toBytes(serializable, getCharset());
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/GelfLayout.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/GelfLayout.java
index 7f01f8e..dca3ff8 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/GelfLayout.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/GelfLayout.java
@@ -16,38 +16,44 @@
*/
package org.apache.logging.log4j.core.layout;
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.core.Layout;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.config.Configuration;
+import org.apache.logging.log4j.core.layout.internal.ExcludeChecker;
+import org.apache.logging.log4j.core.layout.internal.IncludeChecker;
+import org.apache.logging.log4j.core.layout.internal.ListChecker;
+import org.apache.logging.log4j.core.lookup.StrSubstitutor;
+import org.apache.logging.log4j.core.net.Severity;
+import org.apache.logging.log4j.core.util.JsonUtils;
+import org.apache.logging.log4j.core.util.KeyValuePair;
+import org.apache.logging.log4j.core.util.NetUtils;
+import org.apache.logging.log4j.core.util.Patterns;
+import org.apache.logging.log4j.message.Message;
+import org.apache.logging.log4j.plugins.Node;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginBuilderAttribute;
+import org.apache.logging.log4j.plugins.PluginElement;
+import org.apache.logging.log4j.plugins.PluginFactory;
+import org.apache.logging.log4j.status.StatusLogger;
+import org.apache.logging.log4j.util.StringBuilderFormattable;
+import org.apache.logging.log4j.util.Strings;
+import org.apache.logging.log4j.util.TriConsumer;
+
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
import java.util.Collections;
+import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.zip.DeflaterOutputStream;
import java.util.zip.GZIPOutputStream;
-import org.apache.logging.log4j.Level;
-import org.apache.logging.log4j.core.Layout;
-import org.apache.logging.log4j.core.LogEvent;
-import org.apache.logging.log4j.core.config.Configuration;
-import org.apache.logging.log4j.core.config.Node;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
-import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
-import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
-import org.apache.logging.log4j.core.config.plugins.PluginElement;
-import org.apache.logging.log4j.core.lookup.StrSubstitutor;
-import org.apache.logging.log4j.core.net.Severity;
-import org.apache.logging.log4j.core.util.JsonUtils;
-import org.apache.logging.log4j.core.util.KeyValuePair;
-import org.apache.logging.log4j.core.util.NetUtils;
-import org.apache.logging.log4j.message.Message;
-import org.apache.logging.log4j.status.StatusLogger;
-import org.apache.logging.log4j.util.StringBuilderFormattable;
-import org.apache.logging.log4j.util.Strings;
-import org.apache.logging.log4j.util.TriConsumer;
-
/**
* Lays out events in the Graylog Extended Log Format (GELF) 1.1.
* <p>
@@ -98,9 +104,11 @@
private final boolean includeStacktrace;
private final boolean includeThreadContext;
private final boolean includeNullDelimiter;
+ private final PatternLayout layout;
+ private final FieldWriter fieldWriter;
public static class Builder<B extends Builder<B>> extends AbstractStringLayout.Builder<B>
- implements org.apache.logging.log4j.core.util.Builder<GelfLayout> {
+ implements org.apache.logging.log4j.plugins.util.Builder<GelfLayout> {
@PluginBuilderAttribute
private String host;
@@ -123,6 +131,15 @@
@PluginBuilderAttribute
private boolean includeNullDelimiter = false;
+ @PluginBuilderAttribute
+ private String threadContextIncludes = null;
+
+ @PluginBuilderAttribute
+ private String threadContextExcludes = null;
+
+ @PluginBuilderAttribute
+ private String messagePattern = null;
+
public Builder() {
super();
setCharset(StandardCharsets.UTF_8);
@@ -130,8 +147,39 @@
@Override
public GelfLayout build() {
+ ListChecker checker = null;
+ if (threadContextExcludes != null) {
+ final String[] array = threadContextExcludes.split(Patterns.COMMA_SEPARATOR);
+ if (array.length > 0) {
+ List<String> excludes = new ArrayList<>(array.length);
+ for (final String str : array) {
+ excludes.add(str.trim());
+ }
+ checker = new ExcludeChecker(excludes);
+ }
+ }
+ if (threadContextIncludes != null) {
+ final String[] array = threadContextIncludes.split(Patterns.COMMA_SEPARATOR);
+ if (array.length > 0) {
+ List<String> includes = new ArrayList<>(array.length);
+ for (final String str : array) {
+ includes.add(str.trim());
+ }
+ checker = new IncludeChecker(includes);
+ }
+ }
+ if (checker == null) {
+ checker = ListChecker.NOOP_CHECKER;
+ }
+ PatternLayout patternLayout = null;
+ if (messagePattern != null) {
+ patternLayout = PatternLayout.newBuilder().setPattern(messagePattern)
+ .setAlwaysWriteExceptions(includeStacktrace)
+ .setConfiguration(getConfiguration())
+ .build();
+ }
return new GelfLayout(getConfiguration(), host, additionalFields, compressionType, compressionThreshold,
- includeStacktrace, includeThreadContext, includeNullDelimiter);
+ includeStacktrace, includeThreadContext, includeNullDelimiter, checker, patternLayout);
}
public String getHost() {
@@ -231,19 +279,42 @@
this.additionalFields = additionalFields;
return asBuilder();
}
+
+ /**
+ * The pattern to use to format the message.
+ * @param pattern the pattern string.
+ * @return this builder
+ */
+ public B setMessagePattern(final String pattern) {
+ this.messagePattern = pattern;
+ return asBuilder();
+ }
+
+ /**
+ * A comma separated list of thread context keys to include;
+ * @param mdcIncludes the list of keys.
+ * @return this builder
+ */
+ public B setMdcIncludes(final String mdcIncludes) {
+ this.threadContextIncludes = mdcIncludes;
+ return asBuilder();
+ }
+
+ /**
+ * A comma separated list of thread context keys to include;
+ * @param mdcExcludes the list of keys.
+ * @return this builder
+ */
+ public B setMdcExcludes(final String mdcExcludes) {
+ this.threadContextExcludes = mdcExcludes;
+ return asBuilder();
+ }
}
- /**
- * @deprecated Use {@link #newBuilder()} instead
- */
- @Deprecated
- public GelfLayout(final String host, final KeyValuePair[] additionalFields, final CompressionType compressionType,
- final int compressionThreshold, final boolean includeStacktrace) {
- this(null, host, additionalFields, compressionType, compressionThreshold, includeStacktrace, true, false);
- }
-
- private GelfLayout(final Configuration config, final String host, final KeyValuePair[] additionalFields, final CompressionType compressionType,
- final int compressionThreshold, final boolean includeStacktrace, final boolean includeThreadContext, final boolean includeNullDelimiter) {
+ private GelfLayout(final Configuration config, final String host, final KeyValuePair[] additionalFields,
+ final CompressionType compressionType, final int compressionThreshold, final boolean includeStacktrace,
+ final boolean includeThreadContext, final boolean includeNullDelimiter, final ListChecker listChecker,
+ final PatternLayout patternLayout) {
super(config, StandardCharsets.UTF_8, null, null);
this.host = host != null ? host : NetUtils.getLocalHostname();
this.additionalFields = additionalFields != null ? additionalFields : new KeyValuePair[0];
@@ -262,27 +333,30 @@
if (includeNullDelimiter && compressionType != CompressionType.OFF) {
throw new IllegalArgumentException("null delimiter cannot be used with compression");
}
+ this.fieldWriter = new FieldWriter(listChecker);
+ this.layout = patternLayout;
}
- /**
- * @deprecated Use {@link #newBuilder()} instead
- */
- @Deprecated
- public static GelfLayout createLayout(
- //@formatter:off
- @PluginAttribute("host") final String host,
- @PluginElement("AdditionalField") final KeyValuePair[] additionalFields,
- @PluginAttribute(value = "compressionType",
- defaultString = "GZIP") final CompressionType compressionType,
- @PluginAttribute(value = "compressionThreshold",
- defaultInt = COMPRESSION_THRESHOLD) final int compressionThreshold,
- @PluginAttribute(value = "includeStacktrace",
- defaultBoolean = true) final boolean includeStacktrace) {
- // @formatter:on
- return new GelfLayout(null, host, additionalFields, compressionType, compressionThreshold, includeStacktrace, true, false);
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("host=").append(host);
+ sb.append(", compressionType=").append(compressionType.toString());
+ sb.append(", compressionThreshold=").append(compressionThreshold);
+ sb.append(", includeStackTrace=").append(includeStacktrace);
+ sb.append(", includeThreadContext=").append(includeThreadContext);
+ sb.append(", includeNullDelimiter=").append(includeNullDelimiter);
+ String threadVars = fieldWriter.getChecker().toString();
+ if (threadVars.length() > 0) {
+ sb.append(", ").append(threadVars);
+ }
+ if (layout != null) {
+ sb.append(", PatternLayout{").append(layout.toString()).append("}");
+ }
+ return sb.toString();
}
- @PluginBuilderFactory
+ @PluginFactory
public static <B extends Builder<B>> B newBuilder() {
return new Builder<B>().asBuilder();
}
@@ -315,6 +389,11 @@
helper.encode(text, destination);
}
+ @Override
+ public boolean requiresLocation() {
+ return Objects.nonNull(layout) && layout.requiresLocation();
+ }
+
private byte[] compress(final byte[] bytes) {
try {
final ByteArrayOutputStream baos = new ByteArrayOutputStream(compressionThreshold / 8);
@@ -370,14 +449,21 @@
}
}
if (includeThreadContext) {
- event.getContextData().forEach(WRITE_KEY_VALUES_INTO, builder);
+ event.getContextData().forEach(fieldWriter, builder);
}
- if (event.getThrown() != null) {
+
+ if (event.getThrown() != null || layout != null) {
builder.append("\"full_message\":\"");
- if (includeStacktrace) {
- JsonUtils.quoteAsString(formatThrowable(event.getThrown()), builder);
+ if (layout != null) {
+ final StringBuilder messageBuffer = getMessageStringBuilder();
+ layout.serialize(event, messageBuffer);
+ JsonUtils.quoteAsString(messageBuffer, builder);
} else {
- JsonUtils.quoteAsString(event.getThrown().toString(), builder);
+ if (includeStacktrace) {
+ JsonUtils.quoteAsString(formatThrowable(event.getThrown()), builder);
+ } else {
+ JsonUtils.quoteAsString(event.getThrown().toString(), builder);
+ }
}
builder.append(QC);
}
@@ -385,7 +471,7 @@
builder.append("\"short_message\":\"");
final Message message = event.getMessage();
if (message instanceof CharSequence) {
- JsonUtils.quoteAsString(((CharSequence)message), builder);
+ JsonUtils.quoteAsString(((CharSequence) message), builder);
} else if (gcFree && message instanceof StringBuilderFormattable) {
final StringBuilder messageBuffer = getMessageStringBuilder();
try {
@@ -409,14 +495,26 @@
return value != null && value.contains("${");
}
- private static final TriConsumer<String, Object, StringBuilder> WRITE_KEY_VALUES_INTO = new TriConsumer<String, Object, StringBuilder>() {
+ private static class FieldWriter implements TriConsumer<String, Object, StringBuilder> {
+ private final ListChecker checker;
+
+ FieldWriter(ListChecker checker) {
+ this.checker = checker;
+ }
+
@Override
public void accept(final String key, final Object value, final StringBuilder stringBuilder) {
- stringBuilder.append(QU);
- JsonUtils.quoteAsString(key, stringBuilder);
- stringBuilder.append("\":\"");
- JsonUtils.quoteAsString(toNullSafeString(String.valueOf(value)), stringBuilder);
- stringBuilder.append(QC);
+ if (checker.check(key)) {
+ stringBuilder.append(QU);
+ JsonUtils.quoteAsString(key, stringBuilder);
+ stringBuilder.append("\":\"");
+ JsonUtils.quoteAsString(toNullSafeString(String.valueOf(value)), stringBuilder);
+ stringBuilder.append(QC);
+ }
+ }
+
+ public ListChecker getChecker() {
+ return checker;
}
};
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/HtmlLayout.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/HtmlLayout.java
index 8a09e4a..cbfa59a 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/HtmlLayout.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/HtmlLayout.java
@@ -16,6 +16,18 @@
*/
package org.apache.logging.log4j.core.layout;
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.core.Layout;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.config.LoggerConfig;
+import org.apache.logging.log4j.core.util.Transform;
+import org.apache.logging.log4j.plugins.Node;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginAttribute;
+import org.apache.logging.log4j.plugins.PluginBuilderAttribute;
+import org.apache.logging.log4j.plugins.PluginFactory;
+import org.apache.logging.log4j.util.Strings;
+
import java.io.IOException;
import java.io.InterruptedIOException;
import java.io.LineNumberReader;
@@ -27,19 +39,6 @@
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
-import org.apache.logging.log4j.Level;
-import org.apache.logging.log4j.core.Layout;
-import org.apache.logging.log4j.core.LogEvent;
-import org.apache.logging.log4j.core.config.LoggerConfig;
-import org.apache.logging.log4j.core.config.Node;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
-import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
-import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
-import org.apache.logging.log4j.core.config.plugins.PluginFactory;
-import org.apache.logging.log4j.core.util.Transform;
-import org.apache.logging.log4j.util.Strings;
-
/**
* Outputs events as rows in an HTML table on an HTML page.
* <p>
@@ -124,6 +123,11 @@
return locationInfo;
}
+ @Override
+ public boolean requiresLocation() {
+ return locationInfo;
+ }
+
private String addCharsetToContentType(final String contentType) {
if (contentType == null) {
return DEFAULT_CONTENT_TYPE + "; charset=" + getCharset();
@@ -336,11 +340,11 @@
*/
@PluginFactory
public static HtmlLayout createLayout(
- @PluginAttribute(value = "locationInfo") final boolean locationInfo,
- @PluginAttribute(value = "title", defaultString = DEFAULT_TITLE) final String title,
- @PluginAttribute("contentType") String contentType,
- @PluginAttribute(value = "charset", defaultString = "UTF-8") final Charset charset,
- @PluginAttribute("fontSize") String fontSize,
+ @PluginAttribute final boolean locationInfo,
+ @PluginAttribute(defaultString = DEFAULT_TITLE) final String title,
+ @PluginAttribute String contentType,
+ @PluginAttribute(defaultString = "UTF-8") final Charset charset,
+ @PluginAttribute String fontSize,
@PluginAttribute(value = "fontName", defaultString = DEFAULT_FONT_FAMILY) final String font) {
final FontSize fs = FontSize.getFontSize(fontSize);
fontSize = fs.getFontSize();
@@ -360,12 +364,12 @@
return newBuilder().build();
}
- @PluginBuilderFactory
+ @PluginFactory
public static Builder newBuilder() {
return new Builder();
}
- public static class Builder implements org.apache.logging.log4j.core.util.Builder<HtmlLayout> {
+ public static class Builder implements org.apache.logging.log4j.plugins.util.Builder<HtmlLayout> {
@PluginBuilderAttribute
private boolean locationInfo = false;
@@ -388,32 +392,32 @@
private Builder() {
}
- public Builder withLocationInfo(final boolean locationInfo) {
+ public Builder setLocationInfo(final boolean locationInfo) {
this.locationInfo = locationInfo;
return this;
}
- public Builder withTitle(final String title) {
+ public Builder setTitle(final String title) {
this.title = title;
return this;
}
- public Builder withContentType(final String contentType) {
+ public Builder setContentType(final String contentType) {
this.contentType = contentType;
return this;
}
- public Builder withCharset(final Charset charset) {
+ public Builder setCharset(final Charset charset) {
this.charset = charset;
return this;
}
- public Builder withFontSize(final FontSize fontSize) {
+ public Builder setFontSize(final FontSize fontSize) {
this.fontSize = fontSize;
return this;
}
- public Builder withFontName(final String fontName) {
+ public Builder setFontName(final String fontName) {
this.fontName = fontName;
return this;
}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/LevelPatternSelector.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/LevelPatternSelector.java
new file mode 100644
index 0000000..7b2b67b
--- /dev/null
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/LevelPatternSelector.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.logging.log4j.core.layout;
+
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.config.Configuration;
+import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
+import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
+import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
+import org.apache.logging.log4j.core.config.plugins.PluginElement;
+import org.apache.logging.log4j.core.pattern.PatternFormatter;
+import org.apache.logging.log4j.core.pattern.PatternParser;
+import org.apache.logging.log4j.plugins.Node;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.status.StatusLogger;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Selects the pattern to use based on the Level in the LogEvent.
+ */
+@Plugin(name = "LevelPatternSelector", category = Node.CATEGORY, elementType = PatternSelector.ELEMENT_TYPE, printObject = true)
+public class LevelPatternSelector implements PatternSelector{
+
+ /**
+ * Custom MarkerPatternSelector builder. Use the {@link LevelPatternSelector#newBuilder() builder factory method} to create this.
+ */
+ public static class Builder implements org.apache.logging.log4j.core.util.Builder<LevelPatternSelector> {
+
+ @PluginElement("PatternMatch")
+ private PatternMatch[] properties;
+
+ @PluginBuilderAttribute("defaultPattern")
+ private String defaultPattern;
+
+ @PluginBuilderAttribute(value = "alwaysWriteExceptions")
+ private boolean alwaysWriteExceptions = true;
+
+ @PluginBuilderAttribute(value = "disableAnsi")
+ private boolean disableAnsi;
+
+ @PluginBuilderAttribute(value = "noConsoleNoAnsi")
+ private boolean noConsoleNoAnsi;
+
+ @PluginConfiguration
+ private Configuration configuration;
+
+ @Override
+ public LevelPatternSelector build() {
+ if (defaultPattern == null) {
+ defaultPattern = PatternLayout.DEFAULT_CONVERSION_PATTERN;
+ }
+ if (properties == null || properties.length == 0) {
+ LOGGER.warn("No marker patterns were provided with PatternMatch");
+ return null;
+ }
+ return new LevelPatternSelector(properties, defaultPattern, alwaysWriteExceptions, disableAnsi,
+ noConsoleNoAnsi, configuration);
+ }
+
+ public Builder setProperties(final PatternMatch[] properties) {
+ this.properties = properties;
+ return this;
+ }
+
+ public Builder setDefaultPattern(final String defaultPattern) {
+ this.defaultPattern = defaultPattern;
+ return this;
+ }
+
+ public Builder setAlwaysWriteExceptions(final boolean alwaysWriteExceptions) {
+ this.alwaysWriteExceptions = alwaysWriteExceptions;
+ return this;
+ }
+
+ public Builder setDisableAnsi(final boolean disableAnsi) {
+ this.disableAnsi = disableAnsi;
+ return this;
+ }
+
+ public Builder setNoConsoleNoAnsi(final boolean noConsoleNoAnsi) {
+ this.noConsoleNoAnsi = noConsoleNoAnsi;
+ return this;
+ }
+
+ public Builder setConfiguration(final Configuration configuration) {
+ this.configuration = configuration;
+ return this;
+ }
+
+ }
+
+ private final Map<String, PatternFormatter[]> formatterMap = new HashMap<>();
+
+ private final Map<String, String> patternMap = new HashMap<>();
+
+ private final PatternFormatter[] defaultFormatters;
+
+ private final String defaultPattern;
+
+ private static final Logger LOGGER = StatusLogger.getLogger();
+
+ private final boolean requiresLocation;
+
+ /**
+ * @deprecated Use {@link #newBuilder()} instead. This will be private in a future version.
+ */
+ @Deprecated
+ public LevelPatternSelector(final PatternMatch[] properties, final String defaultPattern,
+ final boolean alwaysWriteExceptions, final boolean noConsoleNoAnsi,
+ final Configuration config) {
+ this(properties, defaultPattern, alwaysWriteExceptions, false, noConsoleNoAnsi, config);
+ }
+
+ private LevelPatternSelector(final PatternMatch[] properties, final String defaultPattern,
+ final boolean alwaysWriteExceptions, final boolean disableAnsi,
+ final boolean noConsoleNoAnsi, final Configuration config) {
+ boolean needsLocation = false;
+ final PatternParser parser = PatternLayout.createPatternParser(config);
+ for (final PatternMatch property : properties) {
+ try {
+ final List<PatternFormatter> list = parser.parse(property.getPattern(), alwaysWriteExceptions,
+ disableAnsi, noConsoleNoAnsi);
+ PatternFormatter[] formatters = list.toArray(new PatternFormatter[0]);
+ formatterMap.put(property.getKey(), formatters);
+ for (int i = 0; !needsLocation && i < formatters.length; ++i) {
+ needsLocation = formatters[i].requiresLocation();
+ }
+
+ patternMap.put(property.getKey(), property.getPattern());
+ } catch (final RuntimeException ex) {
+ throw new IllegalArgumentException("Cannot parse pattern '" + property.getPattern() + "'", ex);
+ }
+ }
+ try {
+ final List<PatternFormatter> list = parser.parse(defaultPattern, alwaysWriteExceptions, disableAnsi,
+ noConsoleNoAnsi);
+ defaultFormatters = list.toArray(new PatternFormatter[0]);
+ this.defaultPattern = defaultPattern;
+ for (int i = 0; !needsLocation && i < defaultFormatters.length; ++i) {
+ needsLocation = defaultFormatters[i].requiresLocation();
+ }
+ } catch (final RuntimeException ex) {
+ throw new IllegalArgumentException("Cannot parse pattern '" + defaultPattern + "'", ex);
+ }
+ requiresLocation = needsLocation;
+ }
+
+ @Override
+ public boolean requiresLocation() {
+ return requiresLocation;
+ }
+
+ @Override
+ public PatternFormatter[] getFormatters(final LogEvent event) {
+ final Level level = event.getLevel();
+ if (level == null) {
+ return defaultFormatters;
+ }
+ for (final String key : formatterMap.keySet()) {
+ if (level.name().equalsIgnoreCase(key)) {
+ return formatterMap.get(key);
+ }
+ }
+ return defaultFormatters;
+ }
+
+ /**
+ * Creates a builder for a custom ScriptPatternSelector.
+ *
+ * @return a ScriptPatternSelector builder.
+ */
+ @PluginBuilderFactory
+ public static Builder newBuilder() {
+ return new Builder();
+ }
+
+ /**
+ * Deprecated, use {@link #newBuilder()} instead.
+ * @param properties
+ * @param defaultPattern
+ * @param alwaysWriteExceptions
+ * @param noConsoleNoAnsi
+ * @param configuration
+ * @return a new MarkerPatternSelector.
+ * @deprecated Use {@link #newBuilder()} instead.
+ */
+ @Deprecated
+ public static LevelPatternSelector createSelector(
+ final PatternMatch[] properties,
+ final String defaultPattern,
+ final boolean alwaysWriteExceptions,
+ final boolean noConsoleNoAnsi,
+ final Configuration configuration) {
+ final Builder builder = newBuilder();
+ builder.setProperties(properties);
+ builder.setDefaultPattern(defaultPattern);
+ builder.setAlwaysWriteExceptions(alwaysWriteExceptions);
+ builder.setNoConsoleNoAnsi(noConsoleNoAnsi);
+ builder.setConfiguration(configuration);
+ return builder.build();
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder();
+ boolean first = true;
+ for (final Map.Entry<String, String> entry : patternMap.entrySet()) {
+ if (!first) {
+ sb.append(", ");
+ }
+ sb.append("key=\"").append(entry.getKey()).append("\", pattern=\"").append(entry.getValue()).append("\"");
+ first = false;
+ }
+ if (!first) {
+ sb.append(", ");
+ }
+ sb.append("default=\"").append(defaultPattern).append("\"");
+ return sb.toString();
+ }
+}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/LoggerFields.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/LoggerFields.java
index b01dc5e..6218bab 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/LoggerFields.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/LoggerFields.java
@@ -20,11 +20,11 @@
import java.util.HashMap;
import java.util.Map;
-import org.apache.logging.log4j.core.config.Node;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
-import org.apache.logging.log4j.core.config.plugins.PluginElement;
-import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+import org.apache.logging.log4j.plugins.Node;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginAttribute;
+import org.apache.logging.log4j.plugins.PluginElement;
+import org.apache.logging.log4j.plugins.PluginFactory;
import org.apache.logging.log4j.core.util.KeyValuePair;
import org.apache.logging.log4j.message.StructuredDataId;
@@ -71,10 +71,10 @@
*/
@PluginFactory
public static LoggerFields createLoggerFields(
- @PluginElement("LoggerFields") final KeyValuePair[] keyValuePairs,
- @PluginAttribute("sdId") final String sdId,
- @PluginAttribute("enterpriseId") final String enterpriseId,
- @PluginAttribute(value = "discardIfAllFieldsAreEmpty") final boolean discardIfAllFieldsAreEmpty) {
+ @PluginElement final KeyValuePair[] keyValuePairs,
+ @PluginAttribute final String sdId,
+ @PluginAttribute final String enterpriseId,
+ @PluginAttribute final boolean discardIfAllFieldsAreEmpty) {
final Map<String, String> map = new HashMap<>();
for (final KeyValuePair keyValuePair : keyValuePairs) {
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/MarkerPatternSelector.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/MarkerPatternSelector.java
index 719af9d..651cffe 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/MarkerPatternSelector.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/MarkerPatternSelector.java
@@ -16,24 +16,24 @@
*/
package org.apache.logging.log4j.core.layout;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.Marker;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.config.Configuration;
-import org.apache.logging.log4j.core.config.Node;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
-import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
-import org.apache.logging.log4j.core.config.plugins.PluginElement;
import org.apache.logging.log4j.core.pattern.PatternFormatter;
import org.apache.logging.log4j.core.pattern.PatternParser;
+import org.apache.logging.log4j.plugins.Node;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginBuilderAttribute;
+import org.apache.logging.log4j.plugins.PluginElement;
+import org.apache.logging.log4j.plugins.PluginFactory;
import org.apache.logging.log4j.status.StatusLogger;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
/**
* Selects the pattern to use based on the Marker in the LogEvent.
*/
@@ -43,7 +43,7 @@
/**
* Custom MarkerPatternSelector builder. Use the {@link MarkerPatternSelector#newBuilder() builder factory method} to create this.
*/
- public static class Builder implements org.apache.logging.log4j.core.util.Builder<MarkerPatternSelector> {
+ public static class Builder implements org.apache.logging.log4j.plugins.util.Builder<MarkerPatternSelector> {
@PluginElement("PatternMatch")
private PatternMatch[] properties;
@@ -116,28 +116,24 @@
private final String defaultPattern;
- private static Logger LOGGER = StatusLogger.getLogger();
+ private static final Logger LOGGER = StatusLogger.getLogger();
-
- /**
- * @deprecated Use {@link #newBuilder()} instead. This will be private in a future version.
- */
- @Deprecated
- public MarkerPatternSelector(final PatternMatch[] properties, final String defaultPattern,
- final boolean alwaysWriteExceptions, final boolean noConsoleNoAnsi,
- final Configuration config) {
- this(properties, defaultPattern, alwaysWriteExceptions, false, noConsoleNoAnsi, config);
- }
+ private final boolean requiresLocation;
private MarkerPatternSelector(final PatternMatch[] properties, final String defaultPattern,
final boolean alwaysWriteExceptions, final boolean disableAnsi,
final boolean noConsoleNoAnsi, final Configuration config) {
final PatternParser parser = PatternLayout.createPatternParser(config);
+ boolean needsLocation = false;
for (final PatternMatch property : properties) {
try {
final List<PatternFormatter> list = parser.parse(property.getPattern(), alwaysWriteExceptions,
disableAnsi, noConsoleNoAnsi);
- formatterMap.put(property.getKey(), list.toArray(new PatternFormatter[list.size()]));
+ PatternFormatter[] formatters = list.toArray(new PatternFormatter[list.size()]);
+ formatterMap.put(property.getKey(), formatters);
+ for (int i = 0; !needsLocation && i < formatters.length; ++i) {
+ needsLocation = formatters[i].requiresLocation();
+ }
patternMap.put(property.getKey(), property.getPattern());
} catch (final RuntimeException ex) {
throw new IllegalArgumentException("Cannot parse pattern '" + property.getPattern() + "'", ex);
@@ -148,9 +144,18 @@
noConsoleNoAnsi);
defaultFormatters = list.toArray(new PatternFormatter[list.size()]);
this.defaultPattern = defaultPattern;
+ for (int i = 0; !needsLocation && i < defaultFormatters.length; ++i) {
+ needsLocation = defaultFormatters[i].requiresLocation();
+ }
} catch (final RuntimeException ex) {
throw new IllegalArgumentException("Cannot parse pattern '" + defaultPattern + "'", ex);
}
+ requiresLocation = needsLocation;
+ }
+
+ @Override
+ public boolean requiresLocation() {
+ return requiresLocation;
}
@Override
@@ -172,37 +177,11 @@
*
* @return a ScriptPatternSelector builder.
*/
- @PluginBuilderFactory
+ @PluginFactory
public static Builder newBuilder() {
return new Builder();
}
- /**
- * Deprecated, use {@link #newBuilder()} instead.
- * @param properties
- * @param defaultPattern
- * @param alwaysWriteExceptions
- * @param noConsoleNoAnsi
- * @param configuration
- * @return a new MarkerPatternSelector.
- * @deprecated Use {@link #newBuilder()} instead.
- */
- @Deprecated
- public static MarkerPatternSelector createSelector(
- final PatternMatch[] properties,
- final String defaultPattern,
- final boolean alwaysWriteExceptions,
- final boolean noConsoleNoAnsi,
- final Configuration configuration) {
- final Builder builder = newBuilder();
- builder.setProperties(properties);
- builder.setDefaultPattern(defaultPattern);
- builder.setAlwaysWriteExceptions(alwaysWriteExceptions);
- builder.setNoConsoleNoAnsi(noConsoleNoAnsi);
- builder.setConfiguration(configuration);
- return builder.build();
- }
-
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/MessageLayout.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/MessageLayout.java
index 2a8296c..64d116e 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/MessageLayout.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/MessageLayout.java
@@ -20,9 +20,9 @@
import org.apache.logging.log4j.core.Layout;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.config.Configuration;
-import org.apache.logging.log4j.core.config.Node;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+import org.apache.logging.log4j.plugins.Node;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginFactory;
import org.apache.logging.log4j.message.Message;
/**
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/PatternLayout.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/PatternLayout.java
index 57cbb9c..10a36c5 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/PatternLayout.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/PatternLayout.java
@@ -16,31 +16,29 @@
*/
package org.apache.logging.log4j.core.layout;
+import org.apache.logging.log4j.core.Layout;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.config.Configuration;
+import org.apache.logging.log4j.core.config.DefaultConfiguration;
+import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
+import org.apache.logging.log4j.core.pattern.LogEventPatternConverter;
+import org.apache.logging.log4j.core.pattern.PatternFormatter;
+import org.apache.logging.log4j.core.pattern.PatternParser;
+import org.apache.logging.log4j.core.pattern.RegexReplacement;
+import org.apache.logging.log4j.plugins.Node;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginBuilderAttribute;
+import org.apache.logging.log4j.plugins.PluginElement;
+import org.apache.logging.log4j.plugins.PluginFactory;
+import org.apache.logging.log4j.util.PropertiesUtil;
+import org.apache.logging.log4j.util.Strings;
+
import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import org.apache.logging.log4j.core.Layout;
-import org.apache.logging.log4j.core.LogEvent;
-import org.apache.logging.log4j.core.config.Configuration;
-import org.apache.logging.log4j.core.config.DefaultConfiguration;
-import org.apache.logging.log4j.core.config.Node;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.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.pattern.LogEventPatternConverter;
-import org.apache.logging.log4j.core.pattern.PatternFormatter;
-import org.apache.logging.log4j.core.pattern.PatternParser;
-import org.apache.logging.log4j.core.pattern.RegexReplacement;
-import org.apache.logging.log4j.util.PropertiesUtil;
-import org.apache.logging.log4j.util.Strings;
-
/**
* A flexible layout configurable with pattern string.
* <p>
@@ -142,32 +140,9 @@
return new SerializerBuilder();
}
- /**
- * Deprecated, use {@link #newSerializerBuilder()} instead.
- *
- * @param configuration
- * @param replace
- * @param pattern
- * @param defaultPattern
- * @param patternSelector
- * @param alwaysWriteExceptions
- * @param noConsoleNoAnsi
- * @return a new Serializer.
- * @deprecated Use {@link #newSerializerBuilder()} instead.
- */
- @Deprecated
- public static Serializer createSerializer(final Configuration configuration, final RegexReplacement replace,
- final String pattern, final String defaultPattern, final PatternSelector patternSelector,
- final boolean alwaysWriteExceptions, final boolean noConsoleNoAnsi) {
- final SerializerBuilder builder = newSerializerBuilder();
- builder.setAlwaysWriteExceptions(alwaysWriteExceptions);
- builder.setConfiguration(configuration);
- builder.setDefaultPattern(defaultPattern);
- builder.setNoConsoleNoAnsi(noConsoleNoAnsi);
- builder.setPattern(pattern);
- builder.setPatternSelector(patternSelector);
- builder.setReplace(replace);
- return builder.build();
+ @Override
+ public boolean requiresLocation() {
+ return eventSerializer.requiresLocation();
}
/**
@@ -209,6 +184,10 @@
return eventSerializer.toSerializable(event);
}
+ public void serialize(final LogEvent event, StringBuilder stringBuilder) {
+ eventSerializer.toSerializable(event, stringBuilder);
+ }
+
@Override
public void encode(final LogEvent event, final ByteBufferDestination destination) {
if (!(eventSerializer instanceof Serializer2)) {
@@ -256,56 +235,6 @@
return patternSelector == null ? conversionPattern : patternSelector.toString();
}
- /**
- * Creates a pattern layout.
- *
- * @param pattern
- * The pattern. If not specified, defaults to DEFAULT_CONVERSION_PATTERN.
- * @param patternSelector
- * Allows different patterns to be used based on some selection criteria.
- * @param config
- * The Configuration. Some Converters require access to the Interpolator.
- * @param replace
- * A Regex replacement String.
- * @param charset
- * The character set. The platform default is used if not specified.
- * @param alwaysWriteExceptions
- * If {@code "true"} (default) exceptions are always written even if the pattern contains no exception tokens.
- * @param noConsoleNoAnsi
- * If {@code "true"} (default is false) and {@link System#console()} is null, do not output ANSI escape codes
- * @param headerPattern
- * The footer to place at the top of the document, once.
- * @param footerPattern
- * The footer to place at the bottom of the document, once.
- * @return The PatternLayout.
- * @deprecated Use {@link #newBuilder()} instead. This will be private in a future version.
- */
- @PluginFactory
- @Deprecated
- public static PatternLayout createLayout(
- @PluginAttribute(value = "pattern", defaultString = DEFAULT_CONVERSION_PATTERN) final String pattern,
- @PluginElement("PatternSelector") final PatternSelector patternSelector,
- @PluginConfiguration final Configuration config,
- @PluginElement("Replace") final RegexReplacement replace,
- // LOG4J2-783 use platform default by default, so do not specify defaultString for charset
- @PluginAttribute(value = "charset") final Charset charset,
- @PluginAttribute(value = "alwaysWriteExceptions", defaultBoolean = true) final boolean alwaysWriteExceptions,
- @PluginAttribute(value = "noConsoleNoAnsi") final boolean noConsoleNoAnsi,
- @PluginAttribute("header") final String headerPattern,
- @PluginAttribute("footer") final String footerPattern) {
- return newBuilder()
- .withPattern(pattern)
- .withPatternSelector(patternSelector)
- .withConfiguration(config)
- .withRegexReplacement(replace)
- .withCharset(charset)
- .withAlwaysWriteExceptions(alwaysWriteExceptions)
- .withNoConsoleNoAnsi(noConsoleNoAnsi)
- .withHeader(headerPattern)
- .withFooter(footerPattern)
- .build();
- }
-
private static class PatternSerializer implements Serializer, Serializer2 {
private final PatternFormatter[] formatters;
@@ -353,9 +282,19 @@
builder.append("]");
return builder.toString();
}
+
+ @Override
+ public boolean requiresLocation() {
+ for (PatternFormatter formatter : formatters) {
+ if (formatter.requiresLocation()) {
+ return true;
+ }
+ }
+ return false;
+ }
}
- public static class SerializerBuilder implements org.apache.logging.log4j.core.util.Builder<Serializer> {
+ public static class SerializerBuilder implements org.apache.logging.log4j.plugins.util.Builder<Serializer> {
private Configuration configuration;
private RegexReplacement replace;
@@ -465,6 +404,11 @@
}
@Override
+ public boolean requiresLocation() {
+ return patternSelector.requiresLocation();
+ }
+
+ @Override
public String toString() {
final StringBuilder builder = new StringBuilder();
builder.append(super.toString());
@@ -498,7 +442,7 @@
* @see #DEFAULT_CONVERSION_PATTERN Default conversion pattern
*/
public static PatternLayout createDefaultLayout(final Configuration configuration) {
- return newBuilder().withConfiguration(configuration).build();
+ return newBuilder().setConfiguration(configuration).build();
}
/**
@@ -506,7 +450,7 @@
*
* @return a PatternLayout builder.
*/
- @PluginBuilderFactory
+ @PluginFactory
public static Builder newBuilder() {
return new Builder();
}
@@ -514,7 +458,7 @@
/**
* Custom PatternLayout builder. Use the {@link PatternLayout#newBuilder() builder factory method} to create this.
*/
- public static class Builder implements org.apache.logging.log4j.core.util.Builder<PatternLayout> {
+ public static class Builder implements org.apache.logging.log4j.plugins.util.Builder<PatternLayout> {
@PluginBuilderAttribute
private String pattern = PatternLayout.DEFAULT_CONVERSION_PATTERN;
@@ -561,7 +505,7 @@
* @param pattern
* The pattern. If not specified, defaults to DEFAULT_CONVERSION_PATTERN.
*/
- public Builder withPattern(final String pattern) {
+ public Builder setPattern(final String pattern) {
this.pattern = pattern;
return this;
}
@@ -570,7 +514,7 @@
* @param patternSelector
* Allows different patterns to be used based on some selection criteria.
*/
- public Builder withPatternSelector(final PatternSelector patternSelector) {
+ public Builder setPatternSelector(final PatternSelector patternSelector) {
this.patternSelector = patternSelector;
return this;
}
@@ -579,7 +523,7 @@
* @param configuration
* The Configuration. Some Converters require access to the Interpolator.
*/
- public Builder withConfiguration(final Configuration configuration) {
+ public Builder setConfiguration(final Configuration configuration) {
this.configuration = configuration;
return this;
}
@@ -588,7 +532,7 @@
* @param regexReplacement
* A Regex replacement
*/
- public Builder withRegexReplacement(final RegexReplacement regexReplacement) {
+ public Builder setRegexReplacement(final RegexReplacement regexReplacement) {
this.regexReplacement = regexReplacement;
return this;
}
@@ -597,7 +541,7 @@
* @param charset
* The character set. The platform default is used if not specified.
*/
- public Builder withCharset(final Charset charset) {
+ public Builder setCharset(final Charset charset) {
// LOG4J2-783 if null, use platform default by default
if (charset != null) {
this.charset = charset;
@@ -609,7 +553,7 @@
* @param alwaysWriteExceptions
* If {@code "true"} (default) exceptions are always written even if the pattern contains no exception tokens.
*/
- public Builder withAlwaysWriteExceptions(final boolean alwaysWriteExceptions) {
+ public Builder setAlwaysWriteExceptions(final boolean alwaysWriteExceptions) {
this.alwaysWriteExceptions = alwaysWriteExceptions;
return this;
}
@@ -619,7 +563,7 @@
* If {@code "true"} (default is value of system property `log4j.skipJansi`, or `true` if undefined),
* do not output ANSI escape codes
*/
- public Builder withDisableAnsi(final boolean disableAnsi) {
+ public Builder setDisableAnsi(final boolean disableAnsi) {
this.disableAnsi = disableAnsi;
return this;
}
@@ -628,7 +572,7 @@
* @param noConsoleNoAnsi
* If {@code "true"} (default is false) and {@link System#console()} is null, do not output ANSI escape codes
*/
- public Builder withNoConsoleNoAnsi(final boolean noConsoleNoAnsi) {
+ public Builder setNoConsoleNoAnsi(final boolean noConsoleNoAnsi) {
this.noConsoleNoAnsi = noConsoleNoAnsi;
return this;
}
@@ -637,7 +581,7 @@
* @param header
* The footer to place at the top of the document, once.
*/
- public Builder withHeader(final String header) {
+ public Builder setHeader(final String header) {
this.header = header;
return this;
}
@@ -646,7 +590,7 @@
* @param footer
* The footer to place at the bottom of the document, once.
*/
- public Builder withFooter(final String footer) {
+ public Builder setFooter(final String footer) {
this.footer = footer;
return this;
}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/PatternMatch.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/PatternMatch.java
index 2c6803d..bb6ff55 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/PatternMatch.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/PatternMatch.java
@@ -17,14 +17,14 @@
package org.apache.logging.log4j.core.layout;
+import org.apache.logging.log4j.plugins.Node;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginBuilderAttribute;
+import org.apache.logging.log4j.plugins.PluginFactory;
+
import java.io.ObjectStreamException;
import java.io.Serializable;
-import org.apache.logging.log4j.core.config.Node;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
-import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
-
/**
* PatternMatch configuration item.
*
@@ -67,12 +67,12 @@
return key + '=' + pattern;
}
- @PluginBuilderFactory
+ @PluginFactory
public static Builder newBuilder() {
return new Builder();
}
- public static class Builder implements org.apache.logging.log4j.core.util.Builder<PatternMatch>, Serializable {
+ public static class Builder implements org.apache.logging.log4j.plugins.util.Builder<PatternMatch>, Serializable {
private static final long serialVersionUID = 1L;
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/PatternSelector.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/PatternSelector.java
index 78c9b49..cd890b7 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/PatternSelector.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/PatternSelector.java
@@ -27,4 +27,8 @@
String ELEMENT_TYPE = "patternSelector";
PatternFormatter[] getFormatters(LogEvent event);
+
+ default boolean requiresLocation() {
+ return false;
+ }
}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/Rfc5424Layout.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/Rfc5424Layout.java
index bd5fb95..969a769 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/Rfc5424Layout.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/Rfc5424Layout.java
@@ -35,12 +35,15 @@
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.appender.TlsSyslogFrame;
import org.apache.logging.log4j.core.config.Configuration;
-import org.apache.logging.log4j.core.config.Node;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
+import org.apache.logging.log4j.core.layout.internal.ExcludeChecker;
+import org.apache.logging.log4j.core.layout.internal.IncludeChecker;
+import org.apache.logging.log4j.core.layout.internal.ListChecker;
+import org.apache.logging.log4j.plugins.Node;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.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.plugins.PluginElement;
+import org.apache.logging.log4j.plugins.PluginFactory;
import org.apache.logging.log4j.core.net.Facility;
import org.apache.logging.log4j.core.net.Priority;
import org.apache.logging.log4j.core.pattern.LogEventPatternConverter;
@@ -55,7 +58,7 @@
import org.apache.logging.log4j.message.StructuredDataCollectionMessage;
import org.apache.logging.log4j.message.StructuredDataId;
import org.apache.logging.log4j.message.StructuredDataMessage;
-import org.apache.logging.log4j.util.ProcessIdUtil;
+import org.apache.logging.log4j.core.util.ProcessIdUtil;
import org.apache.logging.log4j.util.StringBuilders;
import org.apache.logging.log4j.util.Strings;
@@ -112,7 +115,6 @@
private final List<String> mdcIncludes;
private final List<String> mdcRequired;
private final ListChecker listChecker;
- private final ListChecker noopChecker = new NoopChecker();
private final boolean includeNewLine;
private final String escapeNewLine;
private final boolean useTlsMessageFormat;
@@ -138,20 +140,20 @@
this.includeMdc = includeMDC;
this.includeNewLine = includeNL;
this.escapeNewLine = escapeNL == null ? null : Matcher.quoteReplacement(escapeNL);
- this.mdcId = id == null ? DEFAULT_MDCID : id;
- this.mdcSdId = new StructuredDataId(mdcId, enterpriseNumber, null, null);
+ this.mdcId = mdcId != null ? mdcId : id == null ? DEFAULT_MDCID : id;
+ this.mdcSdId = new StructuredDataId(this.mdcId, enterpriseNumber, null, null);
this.mdcPrefix = mdcPrefix;
this.eventPrefix = eventPrefix;
this.appName = appName;
this.messageId = messageId;
this.useTlsMessageFormat = useTLSMessageFormat;
this.localHostName = NetUtils.getLocalHostname();
- ListChecker c = null;
+ ListChecker checker = null;
if (excludes != null) {
final String[] array = excludes.split(Patterns.COMMA_SEPARATOR);
if (array.length > 0) {
- c = new ExcludeChecker();
mdcExcludes = new ArrayList<>(array.length);
+ checker = new ExcludeChecker(mdcExcludes);
for (final String str : array) {
mdcExcludes.add(str.trim());
}
@@ -164,8 +166,8 @@
if (includes != null) {
final String[] array = includes.split(Patterns.COMMA_SEPARATOR);
if (array.length > 0) {
- c = new IncludeChecker();
mdcIncludes = new ArrayList<>(array.length);
+ checker = new IncludeChecker(mdcIncludes);
for (final String str : array) {
mdcIncludes.add(str.trim());
}
@@ -189,7 +191,7 @@
} else {
mdcRequired = null;
}
- this.listChecker = c != null ? c : noopChecker;
+ this.listChecker = checker != null ? checker : ListChecker.NOOP_CHECKER;
final String name = config == null ? null : config.getName();
configName = Strings.isNotEmpty(name) ? name : null;
this.fieldFormatters = createFieldFormatters(loggerFields, config);
@@ -513,7 +515,7 @@
sb.append('[');
sb.append(id);
if (!mdcSdId.toString().equals(id)) {
- appendMap(data.getPrefix(), data.getFields(), sb, noopChecker);
+ appendMap(data.getPrefix(), data.getFields(), sb, ListChecker.NOOP_CHECKER);
} else {
appendMap(data.getPrefix(), data.getFields(), sb, checker);
}
@@ -566,43 +568,6 @@
return PARAM_VALUE_ESCAPE_PATTERN.matcher(value).replaceAll("\\\\$0");
}
- /**
- * Interface used to check keys in a Map.
- */
- private interface ListChecker {
- boolean check(String key);
- }
-
- /**
- * Includes only the listed keys.
- */
- private class IncludeChecker implements ListChecker {
- @Override
- public boolean check(final String key) {
- return mdcIncludes.contains(key);
- }
- }
-
- /**
- * Excludes the listed keys.
- */
- private class ExcludeChecker implements ListChecker {
- @Override
- public boolean check(final String key) {
- return !mdcExcludes.contains(key);
- }
- }
-
- /**
- * Does nothing.
- */
- private class NoopChecker implements ListChecker {
- @Override
- public boolean check(final String key) {
- return true;
- }
- }
-
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
@@ -643,25 +608,24 @@
@PluginFactory
public static Rfc5424Layout createLayout(
// @formatter:off
- @PluginAttribute(value = "facility", defaultString = "LOCAL0") final Facility facility,
- @PluginAttribute("id") final String id,
- @PluginAttribute(value = "enterpriseNumber", defaultInt = DEFAULT_ENTERPRISE_NUMBER)
- final int enterpriseNumber,
- @PluginAttribute(value = "includeMDC", defaultBoolean = true) final boolean includeMDC,
- @PluginAttribute(value = "mdcId", defaultString = DEFAULT_MDCID) final String mdcId,
- @PluginAttribute("mdcPrefix") final String mdcPrefix,
- @PluginAttribute("eventPrefix") final String eventPrefix,
- @PluginAttribute(value = "newLine") final boolean newLine,
+ @PluginAttribute(defaultString = "LOCAL0") final Facility facility,
+ @PluginAttribute final String id,
+ @PluginAttribute(defaultInt = DEFAULT_ENTERPRISE_NUMBER) final int enterpriseNumber,
+ @PluginAttribute(defaultBoolean = true) final boolean includeMDC,
+ @PluginAttribute(defaultString = DEFAULT_MDCID) final String mdcId,
+ @PluginAttribute final String mdcPrefix,
+ @PluginAttribute final String eventPrefix,
+ @PluginAttribute final boolean newLine,
@PluginAttribute("newLineEscape") final String escapeNL,
- @PluginAttribute("appName") final String appName,
+ @PluginAttribute final String appName,
@PluginAttribute("messageId") final String msgId,
@PluginAttribute("mdcExcludes") final String excludes,
@PluginAttribute("mdcIncludes") String includes,
@PluginAttribute("mdcRequired") final String required,
- @PluginAttribute("exceptionPattern") final String exceptionPattern,
+ @PluginAttribute final String exceptionPattern,
// RFC 5425
- @PluginAttribute(value = "useTlsMessageFormat") final boolean useTlsMessageFormat,
- @PluginElement("LoggerFields") final LoggerFields[] loggerFields,
+ @PluginAttribute final boolean useTlsMessageFormat,
+ @PluginElement final LoggerFields[] loggerFields,
@PluginConfiguration final Configuration config) {
// @formatter:on
if (includes != null && excludes != null) {
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/ScriptPatternSelector.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/ScriptPatternSelector.java
index 35c6f4b..35bba99 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/ScriptPatternSelector.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/ScriptPatternSelector.java
@@ -16,29 +16,30 @@
*/
package org.apache.logging.log4j.core.layout;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import javax.script.SimpleBindings;
-
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.config.Configuration;
-import org.apache.logging.log4j.core.config.Node;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
-import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
-import org.apache.logging.log4j.core.config.plugins.PluginElement;
import org.apache.logging.log4j.core.pattern.PatternFormatter;
import org.apache.logging.log4j.core.pattern.PatternParser;
import org.apache.logging.log4j.core.script.AbstractScript;
import org.apache.logging.log4j.core.script.ScriptRef;
+import org.apache.logging.log4j.plugins.Node;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginBuilderAttribute;
+import org.apache.logging.log4j.plugins.PluginElement;
+import org.apache.logging.log4j.plugins.PluginFactory;
import org.apache.logging.log4j.status.StatusLogger;
+import javax.script.SimpleBindings;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
/**
- * Selects the pattern to use based on the Marker in the LogEvent.
+ * Selects the pattern to use based on the result of executing a Script. The returned value will be used as the "key"
+ * to choose between one of the configured patterns. If no key is returned or there is no match the default
+ * pattern will be used.
*/
@Plugin(name = "ScriptPatternSelector", category = Node.CATEGORY, elementType = PatternSelector.ELEMENT_TYPE, printObject = true)
public class ScriptPatternSelector implements PatternSelector {
@@ -46,7 +47,7 @@
/**
* Custom ScriptPatternSelector builder. Use the {@link #newBuilder() builder factory method} to create this.
*/
- public static class Builder implements org.apache.logging.log4j.core.util.Builder<ScriptPatternSelector> {
+ public static class Builder implements org.apache.logging.log4j.plugins.util.Builder<ScriptPatternSelector> {
@PluginElement("Script")
private AbstractScript script;
@@ -140,16 +141,12 @@
private final String defaultPattern;
- private static Logger LOGGER = StatusLogger.getLogger();
+ private static final Logger LOGGER = StatusLogger.getLogger();
private final AbstractScript script;
private final Configuration configuration;
+ private final boolean requiresLocation;
-
- /**
- * @deprecated Use {@link #newBuilder()} instead. This will be private in a future version.
- */
- @Deprecated
- public ScriptPatternSelector(final AbstractScript script, final PatternMatch[] properties, final String defaultPattern,
+ private ScriptPatternSelector(final AbstractScript script, final PatternMatch[] properties, final String defaultPattern,
final boolean alwaysWriteExceptions, final boolean disableAnsi,
final boolean noConsoleNoAnsi, final Configuration config) {
this.script = script;
@@ -158,11 +155,16 @@
config.getScriptManager().addScript(script);
}
final PatternParser parser = PatternLayout.createPatternParser(config);
+ boolean needsLocation = false;
for (final PatternMatch property : properties) {
try {
final List<PatternFormatter> list = parser.parse(property.getPattern(), alwaysWriteExceptions, disableAnsi, noConsoleNoAnsi);
- formatterMap.put(property.getKey(), list.toArray(new PatternFormatter[list.size()]));
+ PatternFormatter[] formatters = list.toArray(new PatternFormatter[list.size()]);
+ formatterMap.put(property.getKey(), formatters);
patternMap.put(property.getKey(), property.getPattern());
+ for (int i = 0; !needsLocation && i < formatters.length; ++i) {
+ needsLocation = formatters[i].requiresLocation();
+ }
} catch (final RuntimeException ex) {
throw new IllegalArgumentException("Cannot parse pattern '" + property.getPattern() + "'", ex);
}
@@ -171,9 +173,18 @@
final List<PatternFormatter> list = parser.parse(defaultPattern, alwaysWriteExceptions, disableAnsi, noConsoleNoAnsi);
defaultFormatters = list.toArray(new PatternFormatter[list.size()]);
this.defaultPattern = defaultPattern;
+ for (int i = 0; !needsLocation && i < defaultFormatters.length; ++i) {
+ needsLocation = defaultFormatters[i].requiresLocation();
+ }
} catch (final RuntimeException ex) {
throw new IllegalArgumentException("Cannot parse pattern '" + defaultPattern + "'", ex);
}
+ requiresLocation = needsLocation;
+ }
+
+ @Override
+ public boolean requiresLocation() {
+ return requiresLocation;
}
@Override
@@ -197,41 +208,11 @@
*
* @return a ScriptPatternSelector builder.
*/
- @PluginBuilderFactory
+ @PluginFactory
public static Builder newBuilder() {
return new Builder();
}
- /**
- * Deprecated, use {@link #newBuilder()} instead.
- *
- * @param script
- * @param properties
- * @param defaultPattern
- * @param alwaysWriteExceptions
- * @param noConsoleNoAnsi
- * @param configuration
- * @return a new ScriptPatternSelector
- * @deprecated Use {@link #newBuilder()} instead.
- */
- @Deprecated
- public static ScriptPatternSelector createSelector(
- final AbstractScript script,
- final PatternMatch[] properties,
- final String defaultPattern,
- final boolean alwaysWriteExceptions,
- final boolean noConsoleNoAnsi,
- final Configuration configuration) {
- final Builder builder = newBuilder();
- builder.setScript(script);
- builder.setProperties(properties);
- builder.setDefaultPattern(defaultPattern);
- builder.setAlwaysWriteExceptions(alwaysWriteExceptions);
- builder.setNoConsoleNoAnsi(noConsoleNoAnsi);
- builder.setConfiguration(configuration);
- return builder.build();
- }
-
@Override
public String toString() {
final StringBuilder sb = new StringBuilder();
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/SerializedLayout.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/SerializedLayout.java
deleted file mode 100644
index a77e819..0000000
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/SerializedLayout.java
+++ /dev/null
@@ -1,125 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache license, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the license for the specific language governing permissions and
- * limitations under the license.
- */
-package org.apache.logging.log4j.core.layout;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.ObjectOutputStream;
-import java.io.OutputStream;
-
-import org.apache.logging.log4j.core.Layout;
-import org.apache.logging.log4j.core.LogEvent;
-import org.apache.logging.log4j.core.config.Node;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginFactory;
-
-/**
- * Formats a {@link LogEvent} in its Java serialized form.
- *
- * @deprecated Java Serialization has inherent security weaknesses, see https://www.owasp.org/index.php/Deserialization_of_untrusted_data .
- * Using this layout is no longer recommended. An alternative layout containing the same information is
- * {@link JsonLayout} when configured with properties="true". Deprecated since 2.9.
- */
-@Deprecated
-@Plugin(name = "SerializedLayout", category = Node.CATEGORY, elementType = Layout.ELEMENT_TYPE, printObject = true)
-public final class SerializedLayout extends AbstractLayout<LogEvent> {
-
- private static byte[] serializedHeader;
-
- static {
- final ByteArrayOutputStream baos = new ByteArrayOutputStream();
- try {
- new ObjectOutputStream(baos).close();
- serializedHeader = baos.toByteArray();
- } catch (final Exception ex) {
- LOGGER.error("Unable to generate Object stream header", ex);
- }
- }
-
- private SerializedLayout() {
- super(null, null, null);
- LOGGER.warn("SerializedLayout is deprecated due to the inherent security weakness in Java Serialization, see https://www.owasp.org/index.php/Deserialization_of_untrusted_data Consider using another layout, e.g. JsonLayout");
- }
-
- /**
- * Formats a {@link org.apache.logging.log4j.core.LogEvent} as a serialized byte array of the LogEvent object.
- *
- * @param event The LogEvent.
- * @return the formatted LogEvent.
- */
- @Override
- public byte[] toByteArray(final LogEvent event) {
- final ByteArrayOutputStream baos = new ByteArrayOutputStream();
- try (final ObjectOutputStream oos = new PrivateObjectOutputStream(baos)) {
- oos.writeObject(event);
- oos.reset();
- } catch (final IOException ioe) {
- LOGGER.error("Serialization of LogEvent failed.", ioe);
- }
- return baos.toByteArray();
- }
-
- /**
- * Returns the LogEvent.
- *
- * @param event The Logging Event.
- * @return The LogEvent.
- */
- @Override
- public LogEvent toSerializable(final LogEvent event) {
- return event;
- }
-
- /**
- * Creates a SerializedLayout.
- * @return A SerializedLayout.
- */
- @Deprecated
- @PluginFactory
- public static SerializedLayout createLayout() {
- return new SerializedLayout();
- }
-
- @Override
- public byte[] getHeader() {
- return serializedHeader;
- }
-
- /**
- * SerializedLayout returns a binary stream.
- * @return The content type.
- */
- @Override
- public String getContentType() {
- return "application/octet-stream";
- }
-
- /**
- * The stream header will be written in the Manager so skip it here.
- */
- private class PrivateObjectOutputStream extends ObjectOutputStream {
-
- public PrivateObjectOutputStream(final OutputStream os) throws IOException {
- super(os);
- }
-
- @Override
- protected void writeStreamHeader() {
- // do nothing
- }
- }
-}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/SyslogLayout.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/SyslogLayout.java
index 97fcc3b..5e6d51b 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/SyslogLayout.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/SyslogLayout.java
@@ -16,6 +16,17 @@
*/
package org.apache.logging.log4j.core.layout;
+import org.apache.logging.log4j.core.Layout;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.net.Facility;
+import org.apache.logging.log4j.core.net.Priority;
+import org.apache.logging.log4j.core.util.NetUtils;
+import org.apache.logging.log4j.plugins.Node;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginBuilderAttribute;
+import org.apache.logging.log4j.plugins.PluginFactory;
+import org.apache.logging.log4j.util.Chars;
+
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.text.SimpleDateFormat;
@@ -26,17 +37,6 @@
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-import org.apache.logging.log4j.core.Layout;
-import org.apache.logging.log4j.core.LogEvent;
-import org.apache.logging.log4j.core.config.Node;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
-import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
-import org.apache.logging.log4j.core.net.Facility;
-import org.apache.logging.log4j.core.net.Priority;
-import org.apache.logging.log4j.core.util.NetUtils;
-import org.apache.logging.log4j.util.Chars;
-
/**
* Formats a log event as a BSD Log record.
*/
@@ -55,7 +55,7 @@
* @param <B> the builder type
*/
public static class Builder<B extends Builder<B>> extends AbstractStringLayout.Builder<B>
- implements org.apache.logging.log4j.core.util.Builder<SyslogLayout> {
+ implements org.apache.logging.log4j.plugins.util.Builder<SyslogLayout> {
public Builder() {
super();
@@ -105,7 +105,7 @@
}
- @PluginBuilderFactory
+ @PluginFactory
public static <B extends Builder<B>> B newBuilder() {
return new Builder<B>().asBuilder();
}
@@ -198,22 +198,6 @@
}
/**
- * Creates a SyslogLayout.
- *
- * @param facility The Facility is used to try to classify the message.
- * @param includeNewLine If true a newline will be appended to the result.
- * @param escapeNL Pattern to use for replacing newlines.
- * @param charset The character set.
- * @return A SyslogLayout.
- * @deprecated Use {@link #newBuilder()}.
- */
- @Deprecated
- public static SyslogLayout createLayout(final Facility facility, final boolean includeNewLine,
- final String escapeNL, final Charset charset) {
- return new SyslogLayout(facility, includeNewLine, escapeNL, charset);
- }
-
- /**
* Gets the facility.
*
* @return the facility
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/internal/ExcludeChecker.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/internal/ExcludeChecker.java
new file mode 100644
index 0000000..2841dd2
--- /dev/null
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/internal/ExcludeChecker.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.logging.log4j.core.layout.internal;
+
+import java.util.List;
+
+/**
+ * Excludes the listed keys.
+ */
+public class ExcludeChecker implements ListChecker {
+ private final List<String> list;
+
+ public ExcludeChecker(final List<String> list) {
+ this.list = list;
+ }
+
+ @Override
+ public boolean check(final String key) {
+ return !list.contains(key);
+ }
+
+ @Override
+ public String toString() {
+ return "ThreadContextExcludes=" + list.toString();
+ }
+}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/internal/IncludeChecker.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/internal/IncludeChecker.java
new file mode 100644
index 0000000..c87b807
--- /dev/null
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/internal/IncludeChecker.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.logging.log4j.core.layout.internal;
+
+import java.util.List;
+
+/**
+ * Includes only the listed keys.
+ */
+public class IncludeChecker implements ListChecker {
+ private final List<String> list;
+
+ public IncludeChecker(final List<String> list) {
+ this.list = list;
+ }
+ @Override
+ public boolean check(final String key) {
+ return list.contains(key);
+ }
+
+ @Override
+ public String toString() {
+ return "ThreadContextIncludes=" + list.toString();
+ }
+}
+
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/internal/ListChecker.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/internal/ListChecker.java
new file mode 100644
index 0000000..1993bc0
--- /dev/null
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/internal/ListChecker.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.logging.log4j.core.layout.internal;
+
+import java.util.List;
+
+/**
+ * Class Description goes here.
+ */
+
+public interface ListChecker {
+
+ static final NoopChecker NOOP_CHECKER = new NoopChecker();
+
+ boolean check(final String key);
+
+ /**
+ * Does nothing.
+ */
+ public class NoopChecker implements ListChecker {
+ @Override
+ public boolean check(final String key) {
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return "";
+ }
+ }
+}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/package-info.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/package-info.java
index 581c139..58c4503 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/package-info.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/layout/package-info.java
@@ -16,9 +16,9 @@
*/
/**
* Log4j 2 Layout support. {@link org.apache.logging.log4j.core.Layout} plugins should use the
- * {@linkplain org.apache.logging.log4j.core.config.plugins.Plugin#category() plugin category}
- * {@link org.apache.logging.log4j.core.config.Node#CATEGORY Core} and the
- * {@linkplain org.apache.logging.log4j.core.config.plugins.Plugin#elementType() element type}
+ * {@linkplain org.apache.logging.log4j.plugins.Plugin#category() plugin category}
+ * {@link org.apache.logging.log4j.plugins.Node#CATEGORY Core} and the
+ * {@linkplain org.apache.logging.log4j.plugins.Plugin#elementType() element type}
* {@link org.apache.logging.log4j.core.Layout#ELEMENT_TYPE layout}.
*/
package org.apache.logging.log4j.core.layout;
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/Base64StrLookup.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/Base64StrLookup.java
new file mode 100644
index 0000000..d498d98
--- /dev/null
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/Base64StrLookup.java
@@ -0,0 +1,19 @@
+package org.apache.logging.log4j.core.lookup;
+
+import java.util.Base64;
+
+import org.apache.logging.log4j.core.LogEvent;
+
+/**
+ * Decodes Base64 strings.
+ *
+ * @since 3.0.0
+ */
+public class Base64StrLookup extends AbstractLookup {
+
+ @Override
+ public String lookup(LogEvent event, String key) {
+ return new String(Base64.getDecoder().decode(key));
+ }
+
+}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/ContextMapLookup.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/ContextMapLookup.java
index 220d17a..d8a6310 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/ContextMapLookup.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/ContextMapLookup.java
@@ -18,7 +18,7 @@
import org.apache.logging.log4j.ThreadContext;
import org.apache.logging.log4j.core.LogEvent;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.plugins.Plugin;
import org.apache.logging.log4j.core.ContextDataInjector;
import org.apache.logging.log4j.core.impl.ContextDataInjectorFactory;
import org.apache.logging.log4j.util.ReadOnlyStringMap;
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/DateLookup.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/DateLookup.java
index 3e630b0..2db4e68 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/DateLookup.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/DateLookup.java
@@ -24,7 +24,7 @@
import org.apache.logging.log4j.Marker;
import org.apache.logging.log4j.MarkerManager;
import org.apache.logging.log4j.core.LogEvent;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.plugins.Plugin;
import org.apache.logging.log4j.status.StatusLogger;
/**
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/EnvironmentLookup.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/EnvironmentLookup.java
index 7f0d935..5ea8657 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/EnvironmentLookup.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/EnvironmentLookup.java
@@ -17,7 +17,7 @@
package org.apache.logging.log4j.core.lookup;
import org.apache.logging.log4j.core.LogEvent;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.plugins.Plugin;
/**
* Looks up keys from environment variables.
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/EventLookup.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/EventLookup.java
new file mode 100644
index 0000000..134de26
--- /dev/null
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/EventLookup.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 org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.plugins.Plugin;
+
+/**
+ * Looks up values from the log event.
+ */
+@Plugin(name = "event", category = StrLookup.CATEGORY)
+public class EventLookup extends AbstractLookup {
+
+ /**
+ * Looks up the value from the logging event.
+ * @param event The current LogEvent.
+ * @param key the key to be looked up.
+ * @return The value of the specified log event field.
+ */
+ @Override
+ public String lookup(final LogEvent event, final String key) {
+ switch (key) {
+ case "Marker": {
+ return event.getMarker() != null ? event.getMarker().getName() : null;
+ }
+ case "ThreadName": {
+ return event.getThreadName();
+ }
+ case "Level": {
+ return event.getLevel().toString();
+ }
+ case "ThreadId": {
+ return Long.toString(event.getThreadId());
+ }
+ case "Timestamp": {
+ return Long.toString(event.getTimeMillis());
+ }
+ case "Exception": {
+ if (event.getThrown() != null) {
+ return event.getThrown().getClass().getSimpleName();
+ }
+ if (event.getThrownProxy() != null) {
+ return event.getThrownProxy().getName();
+ }
+ return null;
+ }
+ case "Logger": {
+ return event.getLoggerName();
+ }
+ case "Message": {
+ return event.getMessage().getFormattedMessage();
+ }
+ default: {
+ return null;
+ }
+ }
+ }
+}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/Interpolator.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/Interpolator.java
index e09cd9e..446d57b 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/Interpolator.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/Interpolator.java
@@ -24,10 +24,10 @@
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.config.ConfigurationAware;
-import org.apache.logging.log4j.core.config.plugins.util.PluginManager;
-import org.apache.logging.log4j.core.config.plugins.util.PluginType;
import org.apache.logging.log4j.core.util.Loader;
-import org.apache.logging.log4j.core.util.ReflectionUtil;
+import org.apache.logging.log4j.util.ReflectionUtil;
+import org.apache.logging.log4j.plugins.util.PluginManager;
+import org.apache.logging.log4j.plugins.util.PluginType;
import org.apache.logging.log4j.status.StatusLogger;
import org.apache.logging.log4j.util.Constants;
@@ -36,17 +36,23 @@
*/
public class Interpolator extends AbstractConfigurationAwareLookup {
+ /** Constant for the prefix separator. */
+ public static final char PREFIX_SEPARATOR = ':';
+
private static final String LOOKUP_KEY_WEB = "web";
+ private static final String LOOKUP_KEY_DOCKER = "docker";
+
+ private static final String LOOKUP_KEY_KUBERNETES = "kubernetes";
+
+ private static final String LOOKUP_KEY_SPRING = "spring";
+
private static final String LOOKUP_KEY_JNDI = "jndi";
private static final String LOOKUP_KEY_JVMRUNARGS = "jvmrunargs";
private static final Logger LOGGER = StatusLogger.getLogger();
- /** Constant for the prefix separator. */
- private static final char PREFIX_SEPARATOR = ':';
-
private final Map<String, StrLookup> strLookupMap = new HashMap<>();
private final StrLookup defaultLookup;
@@ -71,7 +77,7 @@
for (final Map.Entry<String, PluginType<?>> entry : plugins.entrySet()) {
try {
final Class<? extends StrLookup> clazz = entry.getValue().getPluginClass().asSubclass(StrLookup.class);
- strLookupMap.put(entry.getKey(), ReflectionUtil.instantiate(clazz));
+ strLookupMap.put(entry.getKey().toLowerCase(), ReflectionUtil.instantiate(clazz));
} catch (final Throwable t) {
handleError(entry.getKey(), t);
}
@@ -97,6 +103,9 @@
strLookupMap.put("main", MainMapLookup.MAIN_SINGLETON);
strLookupMap.put("marker", new MarkerLookup());
strLookupMap.put("java", new JavaLookup());
+ strLookupMap.put("base64", new Base64StrLookup());
+ strLookupMap.put("lower", new LowerLookup());
+ strLookupMap.put("upper", new UpperLookup());
// JNDI
try {
// [LOG4J2-703] We might be on Android
@@ -126,6 +135,26 @@
} else {
LOGGER.debug("Not in a ServletContext environment, thus not loading WebLookup plugin.");
}
+ try {
+ strLookupMap.put(LOOKUP_KEY_DOCKER,
+ Loader.newCheckedInstanceOf("org.apache.logging.log4j.docker.DockerLookup", StrLookup.class));
+ } catch (final Exception ignored) {
+ handleError(LOOKUP_KEY_DOCKER, ignored);
+ }
+ try {
+ strLookupMap.put(LOOKUP_KEY_SPRING,
+ Loader.newCheckedInstanceOf("org.apache.logging.log4j.spring.cloud.config.client.SpringLookup", StrLookup.class));
+ } catch (final Exception ignored) {
+ handleError(LOOKUP_KEY_SPRING, ignored);
+ }
+ try {
+ strLookupMap.put(LOOKUP_KEY_KUBERNETES,
+ Loader.newCheckedInstanceOf("org.apache.logging.log4j.kubernetes.KubernetesLookup", StrLookup.class));
+ } catch (final Exception ignored) {
+ handleError(LOOKUP_KEY_KUBERNETES, ignored);
+ } catch (final NoClassDefFoundError error) {
+ handleError(LOOKUP_KEY_KUBERNETES, error);
+ }
}
public Map<String, StrLookup> getStrLookupMap() {
@@ -151,6 +180,13 @@
"available. If you want better web container support, please add the log4j-web JAR to your " +
"web archive or server lib directory.");
break;
+ case LOOKUP_KEY_DOCKER: case LOOKUP_KEY_SPRING:
+ break;
+ case LOOKUP_KEY_KUBERNETES:
+ if (t instanceof NoClassDefFoundError) {
+ LOGGER.warn("Unable to create Kubernetes lookup due to missing dependency: {}", t.getMessage());
+ }
+ break;
default:
LOGGER.error("Unable to create Lookup for {}", lookupKey, t);
}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/JavaLookup.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/JavaLookup.java
index 4b230f9..af3f094 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/JavaLookup.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/JavaLookup.java
@@ -19,7 +19,7 @@
import java.util.Locale;
import org.apache.logging.log4j.core.LogEvent;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.plugins.Plugin;
import org.apache.logging.log4j.util.Strings;
/**
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/JmxRuntimeInputArgumentsLookup.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/JmxRuntimeInputArgumentsLookup.java
index 3dc2e5d..e1cec53 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/JmxRuntimeInputArgumentsLookup.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/JmxRuntimeInputArgumentsLookup.java
@@ -20,7 +20,7 @@
import java.util.List;
import java.util.Map;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.plugins.Plugin;
/**
* Maps JVM input arguments (but not main arguments) using JMX to acquire JVM arguments.
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/JndiLookup.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/JndiLookup.java
index 30e65ad..be0174e 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/JndiLookup.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/JndiLookup.java
@@ -24,7 +24,7 @@
import org.apache.logging.log4j.Marker;
import org.apache.logging.log4j.MarkerManager;
import org.apache.logging.log4j.core.LogEvent;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.plugins.Plugin;
import org.apache.logging.log4j.core.net.JndiManager;
import org.apache.logging.log4j.status.StatusLogger;
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/Log4jLookup.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/Log4jLookup.java
index 2f1ef67..a405687 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/Log4jLookup.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/Log4jLookup.java
@@ -23,7 +23,7 @@
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.config.ConfigurationSource;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.plugins.Plugin;
import org.apache.logging.log4j.status.StatusLogger;
/**
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/LowerLookup.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/LowerLookup.java
new file mode 100644
index 0000000..366ff9a
--- /dev/null
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/LowerLookup.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.lookup;
+
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.plugins.Plugin;
+
+/**
+ * Converts values to lower case. The passed in "key" should be the value of another lookup.
+ */
+@Plugin(name = "lower", category = StrLookup.CATEGORY)
+public class LowerLookup implements StrLookup {
+
+ /**
+ * Converts the "key" to lower case.
+ * @param key the key to be looked up, may be null
+ * @return The value associated with the key.
+ */
+ @Override
+ public String lookup(final String key) {
+ return key != null ? key.toLowerCase() : null;
+ }
+
+ /**
+ * Converts the "key" to lower case.
+ * @param event The current LogEvent.
+ * @param key the key to be looked up, may be null
+ * @return The value associated with the key.
+ */
+ @Override
+ public String lookup(final LogEvent event, final String key) {
+ return lookup(key);
+ }
+}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/MainMapLookup.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/MainMapLookup.java
index a50de0d..df8d8a4 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/MainMapLookup.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/MainMapLookup.java
@@ -19,7 +19,7 @@
import java.util.Map;
import org.apache.logging.log4j.core.LogEvent;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.plugins.Plugin;
/**
* A map-based lookup for main arguments.
@@ -64,9 +64,13 @@
* Second using the argument at position n as the key to access the value at n+1.
* </p>
* <ul>
- * <li>{@code "main:--file"} = {@code "path/file.txt"}</li>
- * <li>{@code "main:-x"} = {@code "2"}</li>
+ * <li>{@code "main:\--file"} = {@code "path/file.txt"}</li>
+ * <li>{@code "main:\-x"} = {@code "2"}</li>
* </ul>
+ *<p>Note: Many applications use leading dashes to identify command arguments. Specifying {@code "main:--file}
+ * would result in the lookup failing because it would look for a variable named "main" with a default
+ * value of "-file". To avoid this the ":" separating the Lookup name from the key must be followed by
+ * a backslash as an escape character.</p>
*
* @param args
* An application's {@code public static main(String[])} arguments.
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/MapLookup.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/MapLookup.java
index 466decb..7d60979 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/MapLookup.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/MapLookup.java
@@ -21,8 +21,8 @@
import java.util.Map;
import org.apache.logging.log4j.core.LogEvent;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.message.StringMapMessage;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.message.MapMessage;
/**
* A map-based lookup.
@@ -66,37 +66,6 @@
return new HashMap<>(initialCapacity);
}
- /**
- * An application's {@code public static main(String[])} method calls this method to make its main arguments
- * available for lookup with the prefix {@code main}.
- * <p>
- * The map provides two kinds of access: First by index, starting at {@code "0"}, {@code "1"} and so on. For
- * example, the command line {@code --file path/file.txt -x 2} can be accessed from a configuration file with:
- * </p>
- * <ul>
- * <li>{@code "main:0"} = {@code "--file"}</li>
- * <li>{@code "main:1"} = {@code "path/file.txt"}</li>
- * <li>{@code "main:2"} = {@code "-x"}</li>
- * <li>{@code "main:3"} = {@code "2"}</li>
- * </ul>
- * <p>
- * Second using the argument at position n as the key to access the value at n+1.
- * </p>
- * <ul>
- * <li>{@code "main:--file"} = {@code "path/file.txt"}</li>
- * <li>{@code "main:-x"} = {@code "2"}</li>
- * </ul>
- *
- * @param args
- * An application's {@code public static main(String[])} arguments.
- * @since 2.1
- * @deprecated As of 2.4, use {@link MainMapLookup#setMainArguments(String[])}
- */
- @Deprecated
- public static void setMainArguments(final String... args) {
- MainMapLookup.setMainArguments(args);
- }
-
static Map<String, String> toMap(final List<String> args) {
if (args == null) {
return null;
@@ -118,7 +87,7 @@
@Override
public String lookup(final LogEvent event, final String key) {
- final boolean isMapMessage = event != null && event.getMessage() instanceof StringMapMessage;
+ final boolean isMapMessage = event != null && event.getMessage() instanceof MapMessage;
if (map == null && !isMapMessage) {
return null;
}
@@ -129,7 +98,7 @@
}
}
if (isMapMessage) {
- return ((StringMapMessage) event.getMessage()).get(key);
+ return ((MapMessage) event.getMessage()).get(key);
}
return null;
}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/MarkerLookup.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/MarkerLookup.java
index 320db49..bdf71f2 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/MarkerLookup.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/MarkerLookup.java
@@ -20,7 +20,7 @@
import org.apache.logging.log4j.Marker;
import org.apache.logging.log4j.MarkerManager;
import org.apache.logging.log4j.core.LogEvent;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.plugins.Plugin;
/**
* Looks-up markers.
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/ResourceBundleLookup.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/ResourceBundleLookup.java
index 83187c2..ff5ebab 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/ResourceBundleLookup.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/ResourceBundleLookup.java
@@ -23,7 +23,7 @@
import org.apache.logging.log4j.Marker;
import org.apache.logging.log4j.MarkerManager;
import org.apache.logging.log4j.core.LogEvent;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.plugins.Plugin;
import org.apache.logging.log4j.status.StatusLogger;
/**
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/StrSubstitutor.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/StrSubstitutor.java
index 405ca28..33fc535 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/StrSubstitutor.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/StrSubstitutor.java
@@ -158,7 +158,11 @@
/**
* Constant for the default value delimiter of a variable.
*/
- public static final StrMatcher DEFAULT_VALUE_DELIMITER = StrMatcher.stringMatcher(":-");
+ public static final String DEFAULT_VALUE_DELIMITER_STRING = ":-";
+ public static final StrMatcher DEFAULT_VALUE_DELIMITER = StrMatcher.stringMatcher(DEFAULT_VALUE_DELIMITER_STRING);
+
+ public static final String ESCAPE_DELIMITER_STRING = ":\\-";
+ public static final StrMatcher DEFAULT_VALUE_ESCAPE_DELIMITER = StrMatcher.stringMatcher(ESCAPE_DELIMITER_STRING);
private static final int BUF_SIZE = 256;
@@ -180,9 +184,15 @@
/**
* Stores the default variable value delimiter
*/
+ private String valueDelimiterString;
private StrMatcher valueDelimiterMatcher;
/**
+ * Escape string to avoid matching the value delimiter matcher;
+ */
+ private StrMatcher valueEscapeDelimiterMatcher;
+
+ /**
* Variable resolution is delegated to an implementer of VariableResolver.
*/
private StrLookup variableResolver;
@@ -323,7 +333,9 @@
public StrSubstitutor(final StrLookup variableResolver, final StrMatcher prefixMatcher,
final StrMatcher suffixMatcher,
final char escape) {
- this(variableResolver, prefixMatcher, suffixMatcher, escape, DEFAULT_VALUE_DELIMITER);
+ this(variableResolver, prefixMatcher, suffixMatcher, escape, DEFAULT_VALUE_DELIMITER,
+ DEFAULT_VALUE_ESCAPE_DELIMITER);
+ this.valueDelimiterString = DEFAULT_VALUE_DELIMITER_STRING;
}
/**
@@ -336,8 +348,8 @@
* @param valueDelimiterMatcher the variable default value delimiter matcher, may be null
* @throws IllegalArgumentException if the prefix or suffix is null
*/
- public StrSubstitutor(
- final StrLookup variableResolver, final StrMatcher prefixMatcher, final StrMatcher suffixMatcher, final char escape, final StrMatcher valueDelimiterMatcher) {
+ public StrSubstitutor(final StrLookup variableResolver, final StrMatcher prefixMatcher,
+ final StrMatcher suffixMatcher, final char escape, final StrMatcher valueDelimiterMatcher) {
this.setVariableResolver(variableResolver);
this.setVariablePrefixMatcher(prefixMatcher);
this.setVariableSuffixMatcher(suffixMatcher);
@@ -345,6 +357,28 @@
this.setValueDelimiterMatcher(valueDelimiterMatcher);
}
+ /**
+ * Creates a new instance and initializes it.
+ *
+ * @param variableResolver the variable resolver, may be null
+ * @param prefixMatcher the prefix for variables, not null
+ * @param suffixMatcher the suffix for variables, not null
+ * @param escape the escape character
+ * @param valueDelimiterMatcher the variable default value delimiter matcher, may be null
+ * @param valueEscapeMatcher the matcher to escape defaulting, may be null.
+ * @throws IllegalArgumentException if the prefix or suffix is null
+ */
+ public StrSubstitutor(final StrLookup variableResolver, final StrMatcher prefixMatcher,
+ final StrMatcher suffixMatcher, final char escape, final StrMatcher valueDelimiterMatcher,
+ final StrMatcher valueEscapeMatcher) {
+ this.setVariableResolver(variableResolver);
+ this.setVariablePrefixMatcher(prefixMatcher);
+ this.setVariableSuffixMatcher(suffixMatcher);
+ this.setEscapeChar(escape);
+ this.setValueDelimiterMatcher(valueDelimiterMatcher);
+ valueEscapeDelimiterMatcher = valueEscapeMatcher;
+ }
+
//-----------------------------------------------------------------------
/**
* Replaces all the occurrences of variables in the given source object with
@@ -960,10 +994,32 @@
&& prefixMatcher.isMatch(varNameExprChars, i, i, varNameExprChars.length) != 0) {
break;
}
- if ((valueDelimiterMatchLen = valueDelimiterMatcher.isMatch(varNameExprChars, i)) != 0) {
- varName = varNameExpr.substring(0, i);
- varDefaultValue = varNameExpr.substring(i + valueDelimiterMatchLen);
- break;
+ if (valueEscapeDelimiterMatcher != null) {
+ int matchLen = valueEscapeDelimiterMatcher.isMatch(varNameExprChars, i);
+ if (matchLen != 0) {
+ String varNamePrefix = varNameExpr.substring(0, i) + Interpolator.PREFIX_SEPARATOR;
+ varName = varNamePrefix + varNameExpr.substring(i + matchLen - 1);
+ for (int j = i + matchLen; j < varNameExprChars.length; ++j){
+ if ((valueDelimiterMatchLen = valueDelimiterMatcher.isMatch(varNameExprChars, j)) != 0) {
+ varName = varNamePrefix + varNameExpr.substring(i + matchLen, j);
+ varDefaultValue = varNameExpr.substring(j + valueDelimiterMatchLen);
+ break;
+ }
+ }
+ break;
+ } else {
+ if ((valueDelimiterMatchLen = valueDelimiterMatcher.isMatch(varNameExprChars, i)) != 0) {
+ varName = varNameExpr.substring(0, i);
+ varDefaultValue = varNameExpr.substring(i + valueDelimiterMatchLen);
+ break;
+ }
+ }
+ } else {
+ if ((valueDelimiterMatchLen = valueDelimiterMatcher.isMatch(varNameExprChars, i)) != 0) {
+ varName = varNameExpr.substring(0, i);
+ varDefaultValue = varNameExpr.substring(i + valueDelimiterMatchLen);
+ break;
+ }
}
}
}
@@ -1294,6 +1350,9 @@
setValueDelimiterMatcher(null);
return this;
}
+ String escapeValue = valueDelimiter.substring(0, valueDelimiter.length() - 1) + "\\"
+ + valueDelimiter.substring(valueDelimiter.length() - 1);
+ valueEscapeDelimiterMatcher = StrMatcher.stringMatcher(escapeValue);
return setValueDelimiterMatcher(StrMatcher.stringMatcher(valueDelimiter));
}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/StructuredDataLookup.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/StructuredDataLookup.java
index 379d6a9..cf12cf9 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/StructuredDataLookup.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/StructuredDataLookup.java
@@ -17,7 +17,7 @@
package org.apache.logging.log4j.core.lookup;
import org.apache.logging.log4j.core.LogEvent;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.plugins.Plugin;
import org.apache.logging.log4j.message.StructuredDataMessage;
/**
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/SystemPropertiesLookup.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/SystemPropertiesLookup.java
index b74569a..3f35608 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/SystemPropertiesLookup.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/SystemPropertiesLookup.java
@@ -20,7 +20,7 @@
import org.apache.logging.log4j.Marker;
import org.apache.logging.log4j.MarkerManager;
import org.apache.logging.log4j.core.LogEvent;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.plugins.Plugin;
import org.apache.logging.log4j.status.StatusLogger;
/**
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/UpperLookup.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/UpperLookup.java
new file mode 100644
index 0000000..94737b9
--- /dev/null
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/UpperLookup.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.lookup;
+
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.plugins.Plugin;
+
+/**
+ * Converts values to upper case. The passed in "key" should be the value of another lookup.
+ */
+@Plugin(name = "upper", category = StrLookup.CATEGORY)
+public class UpperLookup implements StrLookup {
+
+ /**
+ * Converts the "key" to upper case.
+ * @param key the key to be looked up, may be null
+ * @return The value associated with the key.
+ */
+ @Override
+ public String lookup(final String key) {
+ return key != null ? key.toUpperCase() : null;
+ }
+
+ /**
+ * Converts the "key" to upper case.
+ * @param event The current LogEvent.
+ * @param key the key to be looked up, may be null
+ * @return The value associated with the key.
+ */
+ @Override
+ public String lookup(final LogEvent event, final String key) {
+ return lookup(key);
+ }
+}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/package-info.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/package-info.java
index 87010c4..a5081ba 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/package-info.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/lookup/package-info.java
@@ -17,7 +17,7 @@
/**
* Log4j 2 Lookups. These are used in variable interpolation in various configuration attributes.
* {@link org.apache.logging.log4j.core.lookup.StrLookup} plugins should use the
- * {@linkplain org.apache.logging.log4j.core.config.plugins.Plugin#category() plugin category}
+ * {@linkplain org.apache.logging.log4j.plugins.Plugin#category() plugin category}
* {@link org.apache.logging.log4j.core.lookup.StrLookup#CATEGORY Lookup}.
*/
package org.apache.logging.log4j.core.lookup;
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/MulticastDnsAdvertiser.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/MulticastDnsAdvertiser.java
index 3d610bf..fc7daf2 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/MulticastDnsAdvertiser.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/MulticastDnsAdvertiser.java
@@ -24,7 +24,7 @@
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.plugins.Plugin;
import org.apache.logging.log4j.core.util.Integers;
import org.apache.logging.log4j.status.StatusLogger;
import org.apache.logging.log4j.util.LoaderUtil;
@@ -46,7 +46,7 @@
private static final int MAX_LENGTH = 255;
private static final int DEFAULT_PORT = 4555;
- private static Object jmDNS = initializeJmDns();
+ private static final Object jmDNS = initializeJmDns();
private static Class<?> jmDNSClass;
private static Class<?> serviceInfoClass;
@@ -189,7 +189,7 @@
try {
jmDNSClass = LoaderUtil.loadClass("javax.jmdns.JmDNS");
serviceInfoClass = LoaderUtil.loadClass("javax.jmdns.ServiceInfo");
- // if version 3 is available, use it to constuct a serviceInfo instance, otherwise support the version1 API
+ // if version 3 is available, use it to construct a serviceInfo instance, otherwise support the version1 API
boolean isVersion3 = false;
try {
// create method is in version 3, not version 1
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/SocketAddress.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/SocketAddress.java
index ef3ff05..9ae43e2 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/SocketAddress.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/SocketAddress.java
@@ -16,16 +16,16 @@
*/
package org.apache.logging.log4j.core.net;
+import org.apache.logging.log4j.plugins.Node;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginBuilderAttribute;
+import org.apache.logging.log4j.plugins.PluginFactory;
+import org.apache.logging.log4j.plugins.validation.constraints.ValidHost;
+import org.apache.logging.log4j.plugins.validation.constraints.ValidPort;
+
import java.net.InetAddress;
import java.net.InetSocketAddress;
-import org.apache.logging.log4j.core.config.Node;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
-import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
-import org.apache.logging.log4j.core.config.plugins.validation.constraints.ValidHost;
-import org.apache.logging.log4j.core.config.plugins.validation.constraints.ValidPort;
-
/**
* Plugin to hold a hostname and port (socket address).
*
@@ -66,12 +66,12 @@
return socketAddress.getHostName();
}
- @PluginBuilderFactory
+ @PluginFactory
public static Builder newBuilder() {
return new Builder();
}
- public static class Builder implements org.apache.logging.log4j.core.util.Builder<SocketAddress> {
+ public static class Builder implements org.apache.logging.log4j.plugins.util.Builder<SocketAddress> {
@PluginBuilderAttribute
@ValidHost
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/SocketOptions.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/SocketOptions.java
index 711d044..f0a8156 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/SocketOptions.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/SocketOptions.java
@@ -16,23 +16,23 @@
*/
package org.apache.logging.log4j.core.net;
+import org.apache.logging.log4j.core.Core;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginBuilderAttribute;
+import org.apache.logging.log4j.plugins.PluginElement;
+import org.apache.logging.log4j.plugins.PluginFactory;
+import org.apache.logging.log4j.plugins.util.Builder;
+
import java.net.Socket;
import java.net.SocketException;
-import org.apache.logging.log4j.core.Core;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
-import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
-import org.apache.logging.log4j.core.config.plugins.PluginElement;
-import org.apache.logging.log4j.core.util.Builder;
-
/**
* Holds all socket options settable via {@link Socket} methods.
*/
@Plugin(name = "SocketOptions", category = Core.CATEGORY_NAME, printObject = true)
public class SocketOptions implements Builder<SocketOptions>, Cloneable {
- @PluginBuilderFactory
+ @PluginFactory
public static SocketOptions newBuilder() {
return new SocketOptions();
}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/SocketPerformancePreferences.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/SocketPerformancePreferences.java
index 8a7e1b0..67a1856 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/SocketPerformancePreferences.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/SocketPerformancePreferences.java
@@ -16,13 +16,14 @@
*/
package org.apache.logging.log4j.core.net;
-import java.net.Socket;
import org.apache.logging.log4j.core.Core;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
-import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
-import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required;
-import org.apache.logging.log4j.core.util.Builder;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginBuilderAttribute;
+import org.apache.logging.log4j.plugins.PluginFactory;
+import org.apache.logging.log4j.plugins.util.Builder;
+import org.apache.logging.log4j.plugins.validation.constraints.Required;
+
+import java.net.Socket;
/**
* Holds all socket options settable via {@link Socket#setPerformancePreferences(int, int, int)}.
@@ -33,7 +34,7 @@
@Plugin(name = "SocketPerformancePreferences", category = Core.CATEGORY_NAME, printObject = true)
public class SocketPerformancePreferences implements Builder<SocketPerformancePreferences>, Cloneable {
- @PluginBuilderFactory
+ @PluginFactory
public static SocketPerformancePreferences newBuilder() {
return new SocketPerformancePreferences();
}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/SslSocketManager.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/SslSocketManager.java
index 2a4593d..666aa7e 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/SslSocketManager.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/SslSocketManager.java
@@ -22,6 +22,7 @@
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
+import java.util.List;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
@@ -39,31 +40,6 @@
private final SslConfiguration sslConfig;
/**
- *
- *
- * @param name The unique name of this connection.
- * @param os The OutputStream.
- * @param sock The Socket.
- * @param inetAddress The Internet address of the host.
- * @param host The name of the host.
- * @param port The port number on the host.
- * @param connectTimeoutMillis the connect timeout in milliseconds.
- * @param reconnectionDelayMillis Reconnection interval.
- * @param immediateFail
- * @param layout The Layout.
- * @param bufferSize The buffer size.
- * @deprecated Use {@link #SslSocketManager(String, OutputStream, Socket, SslConfiguration, InetAddress, String, int, int, int, boolean, Layout, int, SocketOptions)}.
- */
- @Deprecated
- public SslSocketManager(final String name, final OutputStream os, final Socket sock,
- final SslConfiguration sslConfig, final InetAddress inetAddress, final String host, final int port,
- final int connectTimeoutMillis, final int reconnectionDelayMillis, final boolean immediateFail,
- final Layout<? extends Serializable> layout, final int bufferSize) {
- super(name, os, sock, inetAddress, host, port, connectTimeoutMillis, reconnectionDelayMillis, immediateFail, layout, bufferSize, null);
- this.sslConfig = sslConfig;
- }
-
- /**
*
*
* @param name The unique name of this connection.
@@ -106,16 +82,6 @@
}
}
- /**
- * @deprecated Use {@link SslSocketManager#getSocketManager(SslConfiguration, String, int, int, int, boolean, Layout, int, SocketOptions)}.
- */
- @Deprecated
- public static SslSocketManager getSocketManager(final SslConfiguration sslConfig, final String host, final int port,
- final int connectTimeoutMillis, final int reconnectDelayMillis, final boolean immediateFail,
- final Layout<? extends Serializable> layout, final int bufferSize) {
- return getSocketManager(sslConfig, host, port, connectTimeoutMillis, reconnectDelayMillis, immediateFail, layout, bufferSize, null);
- }
-
public static SslSocketManager getSocketManager(final SslConfiguration sslConfig, final String host, int port,
final int connectTimeoutMillis, int reconnectDelayMillis, final boolean immediateFail,
final Layout<? extends Serializable> layout, final int bufferSize, final SocketOptions socketOptions) {
@@ -134,11 +100,10 @@
}
@Override
- protected Socket createSocket(final String host, final int port) throws IOException {
+ protected Socket createSocket(final InetSocketAddress socketAddress) throws IOException {
final SSLSocketFactory socketFactory = createSslSocketFactory(sslConfig);
- final InetSocketAddress address = new InetSocketAddress(host, port);
final Socket newSocket = socketFactory.createSocket();
- newSocket.connect(address, getConnectTimeoutMillis());
+ newSocket.connect(socketAddress, getConnectTimeoutMillis());
return newSocket;
}
@@ -164,23 +129,32 @@
data.connectTimeoutMillis, data.reconnectDelayMillis, data.immediateFail, data.layout, data.bufferSize,
data.socketOptions);
}
-
+
@Override
Socket createSocket(final SslFactoryData data) throws IOException {
- return SslSocketManager.createSocket(data.host, data.port, data.connectTimeoutMillis, data.sslConfiguration,
- data.socketOptions);
+ List<InetSocketAddress> socketAddresses = resolver.resolveHost(data.host, data.port);
+ IOException ioe = null;
+ for (InetSocketAddress socketAddress : socketAddresses) {
+ try {
+ return SslSocketManager.createSocket(socketAddress, data.connectTimeoutMillis,
+ data.sslConfiguration, data.socketOptions);
+ } catch (IOException ex) {
+ ioe = ex;
+ }
+ }
+ throw new IOException(errorMessage(data, socketAddresses) , ioe);
}
}
- static Socket createSocket(final String host, int port, int connectTimeoutMillis,
- final SslConfiguration sslConfiguration, SocketOptions socketOptions) throws IOException {
+ static Socket createSocket(final InetSocketAddress socketAddress, final int connectTimeoutMillis,
+ final SslConfiguration sslConfiguration, final SocketOptions socketOptions) throws IOException {
final SSLSocketFactory socketFactory = createSslSocketFactory(sslConfiguration);
final SSLSocket socket = (SSLSocket) socketFactory.createSocket();
if (socketOptions != null) {
// Not sure which options must be applied before or after the connect() call.
socketOptions.apply(socket);
}
- socket.connect(new InetSocketAddress(host, port), connectTimeoutMillis);
+ socket.connect(socketAddress, connectTimeoutMillis);
if (socketOptions != null) {
// Not sure which options must be applied before or after the connect() call.
socketOptions.apply(socket);
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/TcpSocketManager.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/TcpSocketManager.java
index f5b971d..9b39bce 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/TcpSocketManager.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/TcpSocketManager.java
@@ -24,7 +24,9 @@
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.UnknownHostException;
+import java.util.ArrayList;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
@@ -91,43 +93,6 @@
* The Layout.
* @param bufferSize
* The buffer size.
- * @deprecated Use
- * {@link TcpSocketManager#TcpSocketManager(String, OutputStream, Socket, InetAddress, String, int, int, int, boolean, Layout, int, SocketOptions)}.
- */
- @Deprecated
- public TcpSocketManager(final String name, final OutputStream os, final Socket socket,
- final InetAddress inetAddress, final String host, final int port, final int connectTimeoutMillis,
- final int reconnectionDelayMillis, final boolean immediateFail, final Layout<? extends Serializable> layout,
- final int bufferSize) {
- this(name, os, socket, inetAddress, host, port, connectTimeoutMillis, reconnectionDelayMillis, immediateFail,
- layout, bufferSize, null);
- }
-
- /**
- * Constructs.
- *
- * @param name
- * The unique name of this connection.
- * @param os
- * The OutputStream.
- * @param socket
- * The Socket.
- * @param inetAddress
- * The Internet address of the host.
- * @param host
- * The name of the host.
- * @param port
- * The port number on the host.
- * @param connectTimeoutMillis
- * the connect timeout in milliseconds.
- * @param reconnectionDelayMillis
- * Reconnection interval.
- * @param immediateFail
- * True if the write should fail if no socket is immediately available.
- * @param layout
- * The Layout.
- * @param bufferSize
- * The buffer size.
*/
public TcpSocketManager(final String name, final OutputStream os, final Socket socket,
final InetAddress inetAddress, final String host, final int port, final int connectTimeoutMillis,
@@ -160,30 +125,6 @@
* @param bufferSize
* The buffer size.
* @return A TcpSocketManager.
- * @deprecated Use {@link #getSocketManager(String, int, int, int, boolean, Layout, int, SocketOptions)}.
- */
- @Deprecated
- public static TcpSocketManager getSocketManager(final String host, final int port, final int connectTimeoutMillis,
- final int reconnectDelayMillis, final boolean immediateFail, final Layout<? extends Serializable> layout,
- final int bufferSize) {
- return getSocketManager(host, port, connectTimeoutMillis, reconnectDelayMillis, immediateFail, layout,
- bufferSize, null);
- }
-
- /**
- * Obtains a TcpSocketManager.
- *
- * @param host
- * The host to connect to.
- * @param port
- * The port on the host.
- * @param connectTimeoutMillis
- * the connect timeout in milliseconds
- * @param reconnectDelayMillis
- * The interval to pause between retries.
- * @param bufferSize
- * The buffer size.
- * @return A TcpSocketManager.
*/
public static TcpSocketManager getSocketManager(final String host, int port, final int connectTimeoutMillis,
int reconnectDelayMillis, final boolean immediateFail, final Layout<? extends Serializable> layout,
@@ -340,9 +281,30 @@
}
void reconnect() throws IOException {
- final Socket sock = createSocket(inetAddress.getHostName(), port);
+ List<InetSocketAddress> socketAddresses = FACTORY.resolver.resolveHost(host, port);
+ if (socketAddresses.size() == 1) {
+ LOGGER.debug("Reconnecting " + socketAddresses.get(0));
+ connect(socketAddresses.get(0));
+ } else {
+ IOException ioe = null;
+ for (InetSocketAddress socketAddress : socketAddresses) {
+ try {
+ LOGGER.debug("Reconnecting " + socketAddress);
+ connect(socketAddress);
+ return;
+ } catch (IOException ex) {
+ ioe = ex;
+ }
+ }
+ throw ioe;
+ }
+ }
+
+ private void connect(InetSocketAddress socketAddress) throws IOException {
+ final Socket sock = createSocket(socketAddress);
@SuppressWarnings("resource") // newOS is managed by the enclosing Manager.
final OutputStream newOS = sock.getOutputStream();
+ InetAddress prev = socket != null ? socket.getInetAddress() : null;
synchronized (owner) {
Closer.closeSilently(getOutputStream());
setOutputStream(newOS);
@@ -350,12 +312,14 @@
reconnector = null;
shutdown = true;
}
- LOGGER.debug("Connection to {}:{} reestablished: {}", host, port, socket);
+ String type = prev != null && prev.getHostAddress().equals(socketAddress.getAddress().getHostAddress()) ?
+ "reestablished" : "established";
+ LOGGER.debug("Connection to {}:{} {}: {}", host, port, type, socket);
}
@Override
public String toString() {
- return "Reconnector [latch=" + latch + ", shutdown=" + shutdown + ", owner=" + owner + "]";
+ return "Reconnector [latch=" + latch + ", shutdown=" + shutdown + "]";
}
}
@@ -366,19 +330,19 @@
return recon;
}
- protected Socket createSocket(final String host, final int port) throws IOException {
- return createSocket(host, port, socketOptions, connectTimeoutMillis);
+ protected Socket createSocket(final InetSocketAddress socketAddress) throws IOException {
+ return createSocket(socketAddress, socketOptions, connectTimeoutMillis);
}
- protected static Socket createSocket(final String host, final int port, final SocketOptions socketOptions,
+ protected static Socket createSocket(final InetSocketAddress socketAddress, final SocketOptions socketOptions,
final int connectTimeoutMillis) throws IOException {
- LOGGER.debug("Creating socket {}:{}", host, port);
+ LOGGER.debug("Creating socket {}", socketAddress.toString());
final Socket newSocket = new Socket();
if (socketOptions != null) {
// Not sure which options must be applied before or after the connect() call.
socketOptions.apply(newSocket);
}
- newSocket.connect(new InetSocketAddress(host, port), connectTimeoutMillis);
+ newSocket.connect(socketAddress, connectTimeoutMillis);
if (socketOptions != null) {
// Not sure which options must be applied before or after the connect() call.
socketOptions.apply(newSocket);
@@ -386,6 +350,7 @@
return newSocket;
}
+
/**
* Data for the factory.
*/
@@ -431,6 +396,8 @@
protected static class TcpSocketManagerFactory<M extends TcpSocketManager, T extends FactoryData>
implements ManagerFactory<M, T> {
+ static HostResolver resolver = new HostResolver();
+
@SuppressWarnings("resource")
@Override
public M createManager(final String name, final T data) {
@@ -467,9 +434,58 @@
}
Socket createSocket(final T data) throws IOException {
- return TcpSocketManager.createSocket(data.host, data.port, data.socketOptions, data.connectTimeoutMillis);
+ List<InetSocketAddress> socketAddresses = resolver.resolveHost(data.host, data.port);
+ IOException ioe = null;
+ for (InetSocketAddress socketAddress : socketAddresses) {
+ try {
+ return TcpSocketManager.createSocket(socketAddress, data.socketOptions, data.connectTimeoutMillis);
+ } catch (IOException ex) {
+ ioe = ex;
+ }
+ }
+ throw new IOException(errorMessage(data, socketAddresses) , ioe);
}
+ protected String errorMessage(final T data, List<InetSocketAddress> socketAddresses) {
+ StringBuilder sb = new StringBuilder("Unable to create socket for ");
+ sb.append(data.host).append(" at port ").append(data.port);
+ if (socketAddresses.size() == 1) {
+ if (!socketAddresses.get(0).getAddress().getHostAddress().equals(data.host)) {
+ sb.append(" using ip address ").append(socketAddresses.get(0).getAddress().getHostAddress());
+ sb.append(" and port ").append(socketAddresses.get(0).getPort());
+ }
+ } else {
+ sb.append(" using ip addresses and ports ");
+ for (int i = 0; i < socketAddresses.size(); ++i) {
+ if (i > 0) {
+ sb.append(", ");
+ sb.append(socketAddresses.get(i).getAddress().getHostAddress());
+ sb.append(":").append(socketAddresses.get(i).getPort());
+ }
+ }
+ }
+ return sb.toString();
+ }
+ }
+
+ /**
+ * This method is only for unit testing. It is not Thread-safe.
+ * @param resolver the HostResolver.
+ */
+ public static void setHostResolver(HostResolver resolver) {
+ TcpSocketManagerFactory.resolver = resolver;
+ }
+
+ public static class HostResolver {
+
+ public List<InetSocketAddress> resolveHost(String host, int port) throws UnknownHostException {
+ InetAddress[] addresses = InetAddress.getAllByName(host);
+ List<InetSocketAddress> socketAddresses = new ArrayList<>(addresses.length);
+ for (InetAddress address: addresses) {
+ socketAddresses.add(new InetSocketAddress(address, port));
+ }
+ return socketAddresses;
+ }
}
/**
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/UrlConnectionFactory.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/UrlConnectionFactory.java
new file mode 100644
index 0000000..fab8b77
--- /dev/null
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/UrlConnectionFactory.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.net;
+
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.net.URLConnection;
+import javax.net.ssl.HttpsURLConnection;
+
+import org.apache.logging.log4j.core.config.ConfigurationFactory;
+import org.apache.logging.log4j.core.net.ssl.LaxHostnameVerifier;
+import org.apache.logging.log4j.core.net.ssl.SslConfiguration;
+import org.apache.logging.log4j.core.net.ssl.SslConfigurationFactory;
+import org.apache.logging.log4j.core.util.AuthorizationProvider;
+
+/**
+ * Constructs an HTTPURLConnection. This class should be considered to be internal
+ */
+public class UrlConnectionFactory {
+
+ private static final int DEFAULT_TIMEOUT = 60000;
+ private static final int connectTimeoutMillis = DEFAULT_TIMEOUT;
+ private static final int readTimeoutMillis = DEFAULT_TIMEOUT;
+ private static final String JSON = "application/json";
+ private static final String XML = "application/xml";
+ private static final String PROPERTIES = "text/x-java-properties";
+ private static final String TEXT = "text/plain";
+ private static final String HTTP = "http";
+ private static final String HTTPS = "https";
+
+ public static HttpURLConnection createConnection(URL url, long lastModifiedMillis, SslConfiguration sslConfiguration)
+ throws IOException {
+ final HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
+ AuthorizationProvider provider = ConfigurationFactory.getAuthorizationProvider();
+ if (provider != null) {
+ provider.addAuthorization(urlConnection);
+ }
+ urlConnection.setAllowUserInteraction(false);
+ urlConnection.setDoOutput(true);
+ urlConnection.setDoInput(true);
+ urlConnection.setRequestMethod("GET");
+ if (connectTimeoutMillis > 0) {
+ urlConnection.setConnectTimeout(connectTimeoutMillis);
+ }
+ if (readTimeoutMillis > 0) {
+ urlConnection.setReadTimeout(readTimeoutMillis);
+ }
+ String[] fileParts = url.getFile().split("\\.");
+ String type = fileParts[fileParts.length - 1].trim();
+ String contentType = isXml(type) ? XML : isJson(type) ? JSON : isProperties(type) ? PROPERTIES : TEXT;
+ urlConnection.setRequestProperty("Content-Type", contentType);
+ if (lastModifiedMillis > 0) {
+ urlConnection.setIfModifiedSince(lastModifiedMillis);
+ }
+ if (url.getProtocol().equals(HTTPS) && sslConfiguration != null) {
+ ((HttpsURLConnection) urlConnection).setSSLSocketFactory(sslConfiguration.getSslSocketFactory());
+ if (!sslConfiguration.isVerifyHostName()) {
+ ((HttpsURLConnection) urlConnection).setHostnameVerifier(LaxHostnameVerifier.INSTANCE);
+ }
+ }
+ return urlConnection;
+ }
+
+ public static URLConnection createConnection(URL url) throws IOException {
+ URLConnection urlConnection = null;
+ if (url.getProtocol().equals(HTTPS) || url.getProtocol().equals(HTTP)) {
+ urlConnection = createConnection(url, 0, SslConfigurationFactory.getSslConfiguration());
+ } else {
+ urlConnection = url.openConnection();
+ }
+ return urlConnection;
+ }
+
+
+ private static boolean isXml(String type) {
+ return type.equalsIgnoreCase("xml");
+ }
+
+ private static boolean isJson(String type) {
+ return type.equalsIgnoreCase("json") || type.equalsIgnoreCase("jsn");
+ }
+
+ private static boolean isProperties(String type) {
+ return type.equalsIgnoreCase("properties");
+ }
+}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/AbstractKeyStoreConfiguration.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/AbstractKeyStoreConfiguration.java
index 5855026..511fd56 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/AbstractKeyStoreConfiguration.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/AbstractKeyStoreConfiguration.java
@@ -42,24 +42,6 @@
this.keyStore = this.load();
}
- /**
- * @deprecated Use {@link #AbstractKeyStoreConfiguration(String, PasswordProvider, String)} instead
- */
- @Deprecated
- public AbstractKeyStoreConfiguration(final String location, final char[] password, final String keyStoreType)
- throws StoreConfigurationException {
- this(location, new MemoryPasswordProvider(password), keyStoreType);
- }
-
- /**
- * @deprecated Use {@link #AbstractKeyStoreConfiguration(String, PasswordProvider, String)} instead
- */
- @Deprecated
- public AbstractKeyStoreConfiguration(final String location, final String password, final String keyStoreType)
- throws StoreConfigurationException {
- this(location, new MemoryPasswordProvider(password == null ? null : password.toCharArray()), keyStoreType);
- }
-
@Override
protected KeyStore load() throws StoreConfigurationException {
final String loadLocation = this.getLocation();
@@ -70,7 +52,7 @@
}
try (final InputStream fin = openInputStream(loadLocation)) {
final KeyStore ks = KeyStore.getInstance(this.keyStoreType);
- char[] password = this.getPasswordAsCharArray();
+ char[] password = this.getPassword();
try {
ks.load(fin, password);
} finally {
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/KeyStoreConfiguration.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/KeyStoreConfiguration.java
index 0c9e3ce..abe1e25 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/KeyStoreConfiguration.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/KeyStoreConfiguration.java
@@ -24,9 +24,9 @@
import javax.net.ssl.KeyManagerFactory;
import org.apache.logging.log4j.core.Core;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
-import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginAttribute;
+import org.apache.logging.log4j.plugins.PluginFactory;
/**
* Configuration of the KeyStore
@@ -94,12 +94,12 @@
@PluginFactory
public static KeyStoreConfiguration createKeyStoreConfiguration(
// @formatter:off
- @PluginAttribute("location") final String location,
- @PluginAttribute(value = "password", sensitive = true) final char[] password,
- @PluginAttribute("passwordEnvironmentVariable") final String passwordEnvironmentVariable,
- @PluginAttribute("passwordFile") final String passwordFile,
+ @PluginAttribute final String location,
+ @PluginAttribute(sensitive = true) final char[] password,
+ @PluginAttribute final String passwordEnvironmentVariable,
+ @PluginAttribute final String passwordFile,
@PluginAttribute("type") final String keyStoreType,
- @PluginAttribute("keyManagerFactoryAlgorithm") final String keyManagerFactoryAlgorithm) throws StoreConfigurationException {
+ @PluginAttribute final String keyManagerFactoryAlgorithm) throws StoreConfigurationException {
// @formatter:on
if (password != null && passwordEnvironmentVariable != null && passwordFile != null) {
@@ -123,50 +123,10 @@
}
}
- /**
- * @deprecated use {@link #createKeyStoreConfiguration(String, char[], String, String, String, String)}
- */
- @Deprecated
- public static KeyStoreConfiguration createKeyStoreConfiguration(
- // @formatter:off
- final String location,
- final char[] password,
- final String keyStoreType,
- final String keyManagerFactoryAlgorithm) throws StoreConfigurationException {
- // @formatter:on
- return createKeyStoreConfiguration(location, password, null, null, keyStoreType, keyManagerFactoryAlgorithm);
- }
-
- /**
- * Creates a KeyStoreConfiguration.
- *
- * @param location The location of the KeyStore, a file path, URL or resource.
- * @param password The password to access the KeyStore.
- * @param keyStoreType The KeyStore type, null defaults to {@code "JKS"}.
- * @param keyManagerFactoryAlgorithm The standard name of the requested algorithm. See the Java Secure Socket
- * Extension Reference Guide for information about these names.
- * @return a new KeyStoreConfiguration
- * @throws StoreConfigurationException Thrown if this call cannot load the KeyStore.
- * @deprecated Use createKeyStoreConfiguration(String, char[], String, String)
- */
- @Deprecated
- public static KeyStoreConfiguration createKeyStoreConfiguration(
- // @formatter:off
- final String location,
- final String password,
- final String keyStoreType,
- final String keyManagerFactoryAlgorithm) throws StoreConfigurationException {
- // @formatter:on
- return createKeyStoreConfiguration(location,
- (password == null ? null : password.toCharArray()),
- keyStoreType,
- keyManagerFactoryAlgorithm);
- }
-
public KeyManagerFactory initKeyManagerFactory() throws NoSuchAlgorithmException, UnrecoverableKeyException,
KeyStoreException {
final KeyManagerFactory kmFactory = KeyManagerFactory.getInstance(this.keyManagerFactoryAlgorithm);
- char[] password = this.getPasswordAsCharArray();
+ char[] password = this.getPassword();
try {
kmFactory.init(this.getKeyStore(), password);
} finally {
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/SslConfiguration.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/SslConfiguration.java
index b129184..6c5d087 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/SslConfiguration.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/SslConfiguration.java
@@ -30,10 +30,10 @@
import javax.net.ssl.TrustManagerFactory;
import org.apache.logging.log4j.core.Core;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
-import org.apache.logging.log4j.core.config.plugins.PluginElement;
-import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginAttribute;
+import org.apache.logging.log4j.plugins.PluginElement;
+import org.apache.logging.log4j.plugins.PluginFactory;
import org.apache.logging.log4j.status.StatusLogger;
/**
@@ -46,13 +46,15 @@
private final TrustStoreConfiguration trustStoreConfig;
private final SSLContext sslContext;
private final String protocol;
+ private final boolean verifyHostName;
private SslConfiguration(final String protocol, final KeyStoreConfiguration keyStoreConfig,
- final TrustStoreConfiguration trustStoreConfig) {
+ final TrustStoreConfiguration trustStoreConfig, boolean verifyHostName) {
this.keyStoreConfig = keyStoreConfig;
this.trustStoreConfig = trustStoreConfig;
this.protocol = protocol == null ? SslConfigurationDefaults.PROTOCOL : protocol;
this.sslContext = this.createSslContext();
+ this.verifyHostName = verifyHostName;
}
/**
@@ -127,7 +129,7 @@
try {
return createSslContext(true, false);
} catch (final KeyStoreConfigurationException dummy) {
- LOGGER.debug("Exception occured while using default keystore. This should be a BUG");
+ LOGGER.debug("Exception occurred while using default keystore. This should be a BUG");
return null;
}
}
@@ -137,7 +139,7 @@
return createSslContext(false, true);
}
catch (final TrustStoreConfigurationException dummy) {
- LOGGER.debug("Exception occured while using default truststore. This should be a BUG");
+ LOGGER.debug("Exception occurred while using default truststore. This should be a BUG");
return null;
}
}
@@ -228,11 +230,31 @@
@PluginFactory
public static SslConfiguration createSSLConfiguration(
// @formatter:off
- @PluginAttribute("protocol") final String protocol,
- @PluginElement("KeyStore") final KeyStoreConfiguration keyStoreConfig,
- @PluginElement("TrustStore") final TrustStoreConfiguration trustStoreConfig) {
+ @PluginAttribute final String protocol,
+ @PluginElement final KeyStoreConfiguration keyStoreConfig,
+ @PluginElement final TrustStoreConfiguration trustStoreConfig) {
// @formatter:on
- return new SslConfiguration(protocol, keyStoreConfig, trustStoreConfig);
+ return new SslConfiguration(protocol, keyStoreConfig, trustStoreConfig, false);
+ }
+
+ /**
+ * Creates an SslConfiguration from a KeyStoreConfiguration and a TrustStoreConfiguration.
+ *
+ * @param protocol The protocol, see http://docs.oracle.com/javase/7/docs/technotes/guides/security/StandardNames.html#SSLContext
+ * @param keyStoreConfig The KeyStoreConfiguration.
+ * @param trustStoreConfig The TrustStoreConfiguration.
+ * @param verifyHostName whether or not to perform host name verification
+ * @return a new SslConfiguration
+ * @since 2.12
+ */
+ public static SslConfiguration createSSLConfiguration(
+ // @formatter:off
+ @PluginAttribute final String protocol,
+ @PluginElement final KeyStoreConfiguration keyStoreConfig,
+ @PluginElement final TrustStoreConfiguration trustStoreConfig,
+ @PluginAttribute final boolean verifyHostName) {
+ // @formatter:on
+ return new SslConfiguration(protocol, keyStoreConfig, trustStoreConfig, verifyHostName);
}
@Override
@@ -304,4 +326,8 @@
public String getProtocol() {
return protocol;
}
+
+ public boolean isVerifyHostName() {
+ return verifyHostName;
+ }
}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/SslConfigurationFactory.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/SslConfigurationFactory.java
new file mode 100644
index 0000000..d0a517d
--- /dev/null
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/SslConfigurationFactory.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.net.ssl;
+
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.status.StatusLogger;
+import org.apache.logging.log4j.util.PropertiesUtil;
+
+/**
+ * Creates an SSL configuration from Log4j properties.
+ */
+public class SslConfigurationFactory {
+
+ private static final Logger LOGGER = StatusLogger.getLogger();
+ private static SslConfiguration sslConfiguration = null;
+
+ private static final String trustStorelocation = "log4j2.trustStore.location";
+ private static final String trustStorePassword = "log4j2.trustStore.password";
+ private static final String trustStorePasswordFile = "log4j2.trustStore.passwordFile";
+ private static final String trustStorePasswordEnvVar = "log4j2.trustStore.passwordEnvironmentVariable";
+ private static final String trustStoreKeyStoreType = "log4j2.trustStore.keyStoreType";
+ private static final String trustStoreKeyManagerFactoryAlgorithm = "log4j2.trustStore.keyManagerFactoryAlgorithm";
+ private static final String keyStoreLocation = "log4j2.keyStore.location";
+ private static final String keyStorePassword = "log4j2.keyStore.password";
+ private static final String keyStorePasswordFile = "log4j2.keyStore.passwordFile";
+ private static final String keyStorePasswordEnvVar = "log4j2.keyStore.passwordEnvironmentVariable";
+ private static final String keyStoreType = "log4j2.keyStore.type";
+ private static final String keyStoreKeyManagerFactoryAlgorithm = "log4j2.keyStore.keyManagerFactoryAlgorithm";
+ private static final String verifyHostName = "log4j2.ssl.verifyHostName";
+
+ static {
+ PropertiesUtil props = PropertiesUtil.getProperties();
+ KeyStoreConfiguration keyStoreConfiguration = null;
+ TrustStoreConfiguration trustStoreConfiguration = null;
+ String location = props.getStringProperty(trustStorelocation);
+ if (location != null) {
+ String password = props.getStringProperty(trustStorePassword);
+ char[] passwordChars = null;
+ if (password != null) {
+ passwordChars = password.toCharArray();
+ }
+ try {
+ trustStoreConfiguration = TrustStoreConfiguration.createKeyStoreConfiguration(location, passwordChars,
+ props.getStringProperty(trustStorePasswordEnvVar), props.getStringProperty(trustStorePasswordFile),
+ props.getStringProperty(trustStoreKeyStoreType), props.getStringProperty(trustStoreKeyManagerFactoryAlgorithm));
+ } catch (Exception ex) {
+ LOGGER.warn("Unable to create trust store configuration due to: {} {}", ex.getClass().getName(),
+ ex.getMessage());
+ }
+ }
+ location = props.getStringProperty(keyStoreLocation);
+ if (location != null) {
+ String password = props.getStringProperty(keyStorePassword);
+ char[] passwordChars = null;
+ if (password != null) {
+ passwordChars = password.toCharArray();
+ }
+ try {
+ keyStoreConfiguration = KeyStoreConfiguration.createKeyStoreConfiguration(location, passwordChars,
+ props.getStringProperty(keyStorePasswordEnvVar), props.getStringProperty(keyStorePasswordFile),
+ props.getStringProperty(keyStoreType), props.getStringProperty(keyStoreKeyManagerFactoryAlgorithm));
+ } catch (Exception ex) {
+ LOGGER.warn("Unable to create key store configuration due to: {} {}", ex.getClass().getName(),
+ ex.getMessage());
+ }
+ }
+ if (trustStoreConfiguration != null || keyStoreConfiguration != null) {
+ boolean isVerifyHostName = props.getBooleanProperty(verifyHostName, false);
+ sslConfiguration = SslConfiguration.createSSLConfiguration("https", keyStoreConfiguration,
+ trustStoreConfiguration, isVerifyHostName);
+ }
+ }
+
+ public static SslConfiguration getSslConfiguration() {
+ return sslConfiguration;
+ }
+}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/StoreConfiguration.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/StoreConfiguration.java
index 9bdeaf5..d1462f1 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/StoreConfiguration.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/StoreConfiguration.java
@@ -36,22 +36,6 @@
}
/**
- * @deprecated Use {@link #StoreConfiguration(String, PasswordProvider)}
- */
- @Deprecated
- public StoreConfiguration(final String location, final char[] password) {
- this(location, new MemoryPasswordProvider(password));
- }
-
- /**
- * @deprecated Use {@link #StoreConfiguration(String, PasswordProvider)}
- */
- @Deprecated
- public StoreConfiguration(final String location, final String password) {
- this(location, new MemoryPasswordProvider(password == null ? null : password.toCharArray()));
- }
-
- /**
* Clears the secret fields in this object.
*/
public void clearSecrets() {
@@ -67,16 +51,7 @@
this.location = location;
}
- /**
- *
- * @deprecated Use getPasswordAsCharArray()
- */
- @Deprecated
- public String getPassword() {
- return String.valueOf(this.passwordProvider.getPassword());
- }
-
- public char[] getPasswordAsCharArray() {
+ public char[] getPassword() {
return this.passwordProvider.getPassword();
}
@@ -85,15 +60,6 @@
}
/**
- *
- * @deprecated Use getPasswordAsCharArray()
- */
- @Deprecated
- public void setPassword(final String password) {
- this.passwordProvider = new MemoryPasswordProvider(password == null ? null : password.toCharArray());
- }
-
- /**
* @throws StoreConfigurationException May be thrown by subclasses
*/
protected T load() throws StoreConfigurationException {
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/TrustStoreConfiguration.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/TrustStoreConfiguration.java
index b5c282b..17cbe51 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/TrustStoreConfiguration.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/TrustStoreConfiguration.java
@@ -23,9 +23,9 @@
import javax.net.ssl.TrustManagerFactory;
import org.apache.logging.log4j.core.Core;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
-import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginAttribute;
+import org.apache.logging.log4j.plugins.PluginFactory;
/**
* Configuration of the TrustStore
@@ -57,16 +57,6 @@
}
/**
- * @deprecated Use {@link #TrustStoreConfiguration(String, PasswordProvider, String, String)} instead
- */
- @Deprecated
- public TrustStoreConfiguration(final String location, final String password, final String keyStoreType,
- final String trustManagerFactoryAlgorithm) throws StoreConfigurationException {
- this(location, new MemoryPasswordProvider(password == null ? null : password.toCharArray()), keyStoreType,
- trustManagerFactoryAlgorithm);
- }
-
- /**
* Creates a KeyStoreConfiguration.
*
* @param location
@@ -83,12 +73,12 @@
@PluginFactory
public static TrustStoreConfiguration createKeyStoreConfiguration(
// @formatter:off
- @PluginAttribute("location") final String location,
- @PluginAttribute(value = "password", sensitive = true) final char[] password,
- @PluginAttribute("passwordEnvironmentVariable") final String passwordEnvironmentVariable,
- @PluginAttribute("passwordFile") final String passwordFile,
+ @PluginAttribute final String location,
+ @PluginAttribute(sensitive = true) final char[] password,
+ @PluginAttribute final String passwordEnvironmentVariable,
+ @PluginAttribute final String passwordFile,
@PluginAttribute("type") final String keyStoreType,
- @PluginAttribute("trustManagerFactoryAlgorithm") final String trustManagerFactoryAlgorithm) throws StoreConfigurationException {
+ @PluginAttribute final String trustManagerFactoryAlgorithm) throws StoreConfigurationException {
// @formatter:on
if (password != null && passwordEnvironmentVariable != null && passwordFile != null) {
@@ -112,44 +102,6 @@
}
}
- /**
- * @deprecated Use {@link #createKeyStoreConfiguration(String, char[], String, String, String, String)}
- */
- @Deprecated
- public static TrustStoreConfiguration createKeyStoreConfiguration(
- // @formatter:off
- final String location,
- final char[] password,
- final String keyStoreType,
- final String trustManagerFactoryAlgorithm) throws StoreConfigurationException {
- // @formatter:on
- return createKeyStoreConfiguration(location, password, null, null, keyStoreType, trustManagerFactoryAlgorithm);
- }
-
- /**
- * Creates a KeyStoreConfiguration.
- *
- * @param location The location of the KeyStore, a file path, URL or resource.
- * @param password The password to access the KeyStore.
- * @param keyStoreType The KeyStore type, null defaults to {@code "JKS"}.
- * @param trustManagerFactoryAlgorithm The standard name of the requested trust management algorithm. See the Java
- * Secure Socket Extension Reference Guide for information these names.
- * @return a new TrustStoreConfiguration
- * @throws StoreConfigurationException Thrown if this instance cannot load the KeyStore.
- * @deprecated Use createKeyStoreConfiguration(String, char[], String, String)
- */
- @Deprecated
- public static TrustStoreConfiguration createKeyStoreConfiguration(
- // @formatter:off
- final String location,
- final String password,
- final String keyStoreType,
- final String trustManagerFactoryAlgorithm) throws StoreConfigurationException {
- // @formatter:on
- return createKeyStoreConfiguration(location, (password == null ? null : password.toCharArray()),
- null, null, keyStoreType, trustManagerFactoryAlgorithm);
- }
-
public TrustManagerFactory initTrustManagerFactory() throws NoSuchAlgorithmException, KeyStoreException {
final TrustManagerFactory tmFactory = TrustManagerFactory.getInstance(this.trustManagerFactoryAlgorithm);
tmFactory.init(this.getKeyStore());
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/package-info.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/package-info.java
index 8424471..93e6359 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/package-info.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/net/ssl/package-info.java
@@ -17,4 +17,4 @@
/**
* Log4j 2 SSL support
*/
-package org.apache.logging.log4j.core.net.ssl;
\ No newline at end of file
+package org.apache.logging.log4j.core.net.ssl;
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/osgi/Activator.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/osgi/Activator.java
index 816ce37..97aea84 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/osgi/Activator.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/osgi/Activator.java
@@ -17,98 +17,79 @@
package org.apache.logging.log4j.core.osgi;
+import java.util.Collection;
import java.util.Hashtable;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
-import org.apache.logging.log4j.core.config.plugins.util.PluginRegistry;
import org.apache.logging.log4j.core.impl.Log4jProvider;
+import org.apache.logging.log4j.core.impl.ThreadContextDataInjector;
+import org.apache.logging.log4j.core.impl.ThreadContextDataProvider;
+import org.apache.logging.log4j.core.plugins.Log4jPlugins;
import org.apache.logging.log4j.core.util.Constants;
+import org.apache.logging.log4j.core.util.ContextDataProvider;
+import org.apache.logging.log4j.plugins.processor.PluginService;
import org.apache.logging.log4j.spi.Provider;
import org.apache.logging.log4j.status.StatusLogger;
import org.apache.logging.log4j.util.PropertiesUtil;
-import org.osgi.framework.Bundle;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
-import org.osgi.framework.BundleEvent;
+import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceReference;
import org.osgi.framework.ServiceRegistration;
-import org.osgi.framework.SynchronousBundleListener;
-import org.osgi.framework.wiring.BundleWiring;
/**
* OSGi BundleActivator.
*/
-public final class Activator implements BundleActivator, SynchronousBundleListener {
+public final class Activator implements BundleActivator {
private static final Logger LOGGER = StatusLogger.getLogger();
private final AtomicReference<BundleContext> contextRef = new AtomicReference<>();
ServiceRegistration provideRegistration = null;
+ ServiceRegistration pluginRegistration = null;
+ ServiceRegistration contextDataRegistration = null;
@Override
public void start(final BundleContext context) throws Exception {
+ pluginRegistration = context.registerService(PluginService.class.getName(), new Log4jPlugins(),
+ new Hashtable<>());
final Provider provider = new Log4jProvider();
final Hashtable<String, String> props = new Hashtable<>();
props.put("APIVersion", "2.60");
+ final ContextDataProvider threadContextProvider = new ThreadContextDataProvider();
provideRegistration = context.registerService(Provider.class.getName(), provider, props);
+ contextDataRegistration = context.registerService(ContextDataProvider.class.getName(), threadContextProvider,
+ null);
+ loadContextProviders(context);
// allow the user to override the default ContextSelector (e.g., by using BasicContextSelector for a global cfg)
if (PropertiesUtil.getProperties().getStringProperty(Constants.LOG4J_CONTEXT_SELECTOR) == null) {
System.setProperty(Constants.LOG4J_CONTEXT_SELECTOR, BundleContextSelector.class.getName());
}
- if (this.contextRef.compareAndSet(null, context)) {
- context.addBundleListener(this);
- // done after the BundleListener as to not miss any new bundle installs in the interim
- scanInstalledBundlesForPlugins(context);
- }
- }
-
- private static void scanInstalledBundlesForPlugins(final BundleContext context) {
- final Bundle[] bundles = context.getBundles();
- for (final Bundle bundle : bundles) {
- // TODO: bundle state can change during this
- scanBundleForPlugins(bundle);
- }
- }
-
- private static void scanBundleForPlugins(final Bundle bundle) {
- final long bundleId = bundle.getBundleId();
- // LOG4J2-920: don't scan system bundle for plugins
- if (bundle.getState() == Bundle.ACTIVE && bundleId != 0) {
- LOGGER.trace("Scanning bundle [{}, id=%d] for plugins.", bundle.getSymbolicName(), bundleId);
- PluginRegistry.getInstance().loadFromBundle(bundleId,
- bundle.adapt(BundleWiring.class).getClassLoader());
- }
- }
-
- private static void stopBundlePlugins(final Bundle bundle) {
- LOGGER.trace("Stopping bundle [{}] plugins.", bundle.getSymbolicName());
- // TODO: plugin lifecycle code
- PluginRegistry.getInstance().clearBundlePlugins(bundle.getBundleId());
+ contextRef.compareAndSet(null, context);
}
@Override
public void stop(final BundleContext context) throws Exception {
provideRegistration.unregister();
+ pluginRegistration.unregister();
+ contextDataRegistration.unregister();
this.contextRef.compareAndSet(context, null);
- LogManager.shutdown();
+ LogManager.shutdown(false, true);
}
- @Override
- public void bundleChanged(final BundleEvent event) {
- switch (event.getType()) {
- // FIXME: STARTING instead of STARTED?
- case BundleEvent.STARTED:
- scanBundleForPlugins(event.getBundle());
- break;
-
- case BundleEvent.STOPPING:
- stopBundlePlugins(event.getBundle());
- break;
-
- default:
- break;
+ private static void loadContextProviders(final BundleContext bundleContext) {
+ try {
+ final Collection<ServiceReference<ContextDataProvider>> serviceReferences =
+ bundleContext.getServiceReferences(ContextDataProvider.class, null);
+ for (final ServiceReference<ContextDataProvider> serviceReference : serviceReferences) {
+ final ContextDataProvider provider = bundleContext.getService(serviceReference);
+ ThreadContextDataInjector.contextDataProviders.add(provider);
+ }
+ } catch (final InvalidSyntaxException ex) {
+ LOGGER.error("Error accessing context data provider", ex);
}
}
}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/osgi/BundleContextSelector.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/osgi/BundleContextSelector.java
index 5e22922..ba0a14e 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/osgi/BundleContextSelector.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/osgi/BundleContextSelector.java
@@ -19,6 +19,7 @@
import java.lang.ref.WeakReference;
import java.net.URI;
import java.util.Objects;
+import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.logging.log4j.core.LoggerContext;
@@ -39,6 +40,74 @@
public class BundleContextSelector extends ClassLoaderContextSelector {
@Override
+ public void shutdown(final String fqcn, final ClassLoader loader, final boolean currentContext,
+ final boolean allContexts) {
+ LoggerContext ctx = null;
+ Bundle bundle = null;
+ if (currentContext) {
+ ctx = ContextAnchor.THREAD_CONTEXT.get();
+ ContextAnchor.THREAD_CONTEXT.remove();
+ }
+ if (ctx == null && loader instanceof BundleReference) {
+ bundle = ((BundleReference) loader).getBundle();
+ ctx = getLoggerContext(bundle);
+ removeLoggerContext(ctx);
+ }
+ if (ctx == null) {
+ final Class<?> callerClass = StackLocatorUtil.getCallerClass(fqcn);
+ if (callerClass != null) {
+ bundle = FrameworkUtil.getBundle(callerClass);
+ ctx = getLoggerContext(FrameworkUtil.getBundle(callerClass));
+ removeLoggerContext(ctx);
+ }
+ }
+ if (ctx == null) {
+ ctx = ContextAnchor.THREAD_CONTEXT.get();
+ ContextAnchor.THREAD_CONTEXT.remove();
+ }
+ if (ctx != null) {
+ ctx.stop(DEFAULT_STOP_TIMEOUT, TimeUnit.MILLISECONDS);
+ }
+ if (bundle != null && allContexts) {
+ final Bundle[] bundles = bundle.getBundleContext().getBundles();
+ for (final Bundle bdl : bundles) {
+ ctx = getLoggerContext(bdl);
+ if (ctx != null) {
+ ctx.stop(DEFAULT_STOP_TIMEOUT, TimeUnit.MILLISECONDS);
+ }
+ }
+ }
+ }
+
+ private LoggerContext getLoggerContext(final Bundle bundle) {
+ final String name = Objects.requireNonNull(bundle, "No Bundle provided").getSymbolicName();
+ final AtomicReference<WeakReference<LoggerContext>> ref = CONTEXT_MAP.get(name);
+ if (ref != null && ref.get() != null) {
+ return ref.get().get();
+ }
+ return null;
+ }
+
+ private void removeLoggerContext(LoggerContext context) {
+ CONTEXT_MAP.remove(context.getName());
+ }
+
+ @Override
+ public boolean hasContext(final String fqcn, final ClassLoader loader, final boolean currentContext) {
+ if (currentContext && ContextAnchor.THREAD_CONTEXT.get() != null) {
+ return ContextAnchor.THREAD_CONTEXT.get().isStarted();
+ }
+ if (loader instanceof BundleReference) {
+ return hasContext(((BundleReference) loader).getBundle());
+ }
+ final Class<?> callerClass = StackLocatorUtil.getCallerClass(fqcn);
+ if (callerClass != null) {
+ return hasContext(FrameworkUtil.getBundle(callerClass));
+ }
+ return ContextAnchor.THREAD_CONTEXT.get() != null && ContextAnchor.THREAD_CONTEXT.get().isStarted();
+ }
+
+ @Override
public LoggerContext getContext(final String fqcn, final ClassLoader loader, final boolean currentContext,
final URI configLocation) {
if (currentContext) {
@@ -60,6 +129,12 @@
return lc == null ? getDefault() : lc;
}
+ private static boolean hasContext(final Bundle bundle) {
+ final String name = Objects.requireNonNull(bundle, "No Bundle provided").getSymbolicName();
+ final AtomicReference<WeakReference<LoggerContext>> ref = CONTEXT_MAP.get(name);
+ return ref != null && ref.get() != null && ref.get().get() != null && ref.get().get().isStarted();
+ }
+
private static LoggerContext locateContext(final Bundle bundle, final URI configLocation) {
final String name = Objects.requireNonNull(bundle, "No Bundle provided").getSymbolicName();
final AtomicReference<WeakReference<LoggerContext>> ref = CONTEXT_MAP.get(name);
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/osgi/package-info.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/osgi/package-info.java
index d7963e5..7c7e8ce 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/osgi/package-info.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/osgi/package-info.java
@@ -18,4 +18,4 @@
/**
* Collection of OSGi-specific classes for bundles.
*/
-package org.apache.logging.log4j.core.osgi;
\ No newline at end of file
+package org.apache.logging.log4j.core.osgi;
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/AbstractStyleNameConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/AbstractStyleNameConverter.java
index 2e1db29..a82aa1f 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/AbstractStyleNameConverter.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/AbstractStyleNameConverter.java
@@ -23,7 +23,7 @@
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.config.Configuration;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.plugins.Plugin;
import org.apache.logging.log4j.core.layout.PatternLayout;
import org.apache.logging.log4j.util.PerformanceSensitive;
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/ClassNamePatternConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/ClassNamePatternConverter.java
index 30f580f..e4b1fcb 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/ClassNamePatternConverter.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/ClassNamePatternConverter.java
@@ -17,7 +17,7 @@
package org.apache.logging.log4j.core.pattern;
import org.apache.logging.log4j.core.LogEvent;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.plugins.Plugin;
/**
@@ -64,4 +64,9 @@
abbreviate(element.getClassName(), toAppendTo);
}
}
+
+ @Override
+ public boolean requiresLocation() {
+ return true;
+ }
}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/DatePatternConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/DatePatternConverter.java
index 049c9ae..fa7edfe 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/DatePatternConverter.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/DatePatternConverter.java
@@ -23,7 +23,7 @@
import java.util.concurrent.atomic.AtomicReference;
import org.apache.logging.log4j.core.LogEvent;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.plugins.Plugin;
import org.apache.logging.log4j.core.util.Constants;
import org.apache.logging.log4j.core.time.Instant;
import org.apache.logging.log4j.core.time.MutableInstant;
@@ -88,7 +88,7 @@
private final FixedDateFormat fixedDateFormat;
// below fields are only used in ThreadLocal caching mode
- private final char[] cachedBuffer = new char[64]; // max length of formatted date-time in any format < 64
+ private final char[] cachedBuffer = new char[70]; // max length of formatted date-time in any format < 70
private int length = 0;
FixedFormatter(final FixedDateFormat fixedDateFormat) {
@@ -216,7 +216,7 @@
// if we get here, options is a non-null array with at least one element (first of which non-null)
Objects.requireNonNull(options);
if (options.length == 0) {
- throw new IllegalArgumentException("options array must have at least one element");
+ throw new IllegalArgumentException("Options array must have at least one element");
}
Objects.requireNonNull(options[0]);
final String patternOption = options[0];
@@ -279,9 +279,8 @@
threadLocalMutableInstant.set(result);
}
return result;
- } else {
- return new MutableInstant();
}
+ return new MutableInstant();
}
public void format(final Instant instant, final StringBuilder output) {
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/EncodingPatternConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/EncodingPatternConverter.java
index 941ecca..c1b83c3 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/EncodingPatternConverter.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/EncodingPatternConverter.java
@@ -20,7 +20,7 @@
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.config.Configuration;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.plugins.Plugin;
import org.apache.logging.log4j.core.layout.PatternLayout;
import org.apache.logging.log4j.util.EnglishEnums;
import org.apache.logging.log4j.util.PerformanceSensitive;
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/EndOfBatchPatternConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/EndOfBatchPatternConverter.java
index bf62768..bff1c7d 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/EndOfBatchPatternConverter.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/EndOfBatchPatternConverter.java
@@ -17,7 +17,7 @@
package org.apache.logging.log4j.core.pattern;
import org.apache.logging.log4j.core.LogEvent;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.plugins.Plugin;
import org.apache.logging.log4j.util.PerformanceSensitive;
/**
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/EqualsIgnoreCaseReplacementConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/EqualsIgnoreCaseReplacementConverter.java
index 8e30ac5..cc97ad1 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/EqualsIgnoreCaseReplacementConverter.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/EqualsIgnoreCaseReplacementConverter.java
@@ -19,7 +19,7 @@
import java.util.List;
import org.apache.logging.log4j.core.config.Configuration;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.plugins.Plugin;
import org.apache.logging.log4j.core.layout.PatternLayout;
import org.apache.logging.log4j.util.PerformanceSensitive;
import org.apache.logging.log4j.util.StringBuilders;
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/EqualsReplacementConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/EqualsReplacementConverter.java
index da2938a..e266b41 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/EqualsReplacementConverter.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/EqualsReplacementConverter.java
@@ -19,7 +19,7 @@
import java.util.List;
import org.apache.logging.log4j.core.config.Configuration;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.plugins.Plugin;
import org.apache.logging.log4j.core.layout.PatternLayout;
import org.apache.logging.log4j.util.PerformanceSensitive;
import org.apache.logging.log4j.util.StringBuilders;
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/ExtendedThrowablePatternConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/ExtendedThrowablePatternConverter.java
index 8ec9b7d..d078a5e 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/ExtendedThrowablePatternConverter.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/ExtendedThrowablePatternConverter.java
@@ -18,7 +18,7 @@
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.config.Configuration;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.plugins.Plugin;
import org.apache.logging.log4j.core.impl.ThrowableProxy;
/**
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/FileDatePatternConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/FileDatePatternConverter.java
index c9eb039..a843e48 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/FileDatePatternConverter.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/FileDatePatternConverter.java
@@ -16,7 +16,7 @@
*/
package org.apache.logging.log4j.core.pattern;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.plugins.Plugin;
import org.apache.logging.log4j.util.PerformanceSensitive;
/**
@@ -27,11 +27,15 @@
@Plugin(name = "FileDatePatternConverter", category = "FileConverter")
@ConverterKeys({ "d", "date" })
@PerformanceSensitive("allocation")
-public final class FileDatePatternConverter {
+public final class FileDatePatternConverter implements ArrayPatternConverter {
+
+ private final DatePatternConverter delegate;
+
/**
* Private constructor.
*/
- private FileDatePatternConverter() {
+ private FileDatePatternConverter(final String... options) {
+ delegate = DatePatternConverter.newInstance(options);
}
/**
@@ -40,14 +44,35 @@
* @param options options, may be null.
* @return instance of pattern converter.
*/
- public static PatternConverter newInstance(final String[] options) {
+ public static FileDatePatternConverter newInstance(final String[] options) {
if (options == null || options.length == 0) {
- return DatePatternConverter.newInstance(
- new String[]{
- "yyyy-MM-dd"
- });
+ return new FileDatePatternConverter("yyyy-MM-dd");
}
- return DatePatternConverter.newInstance(options);
+ return new FileDatePatternConverter(options);
+ }
+
+ @Override
+ public void format(final Object obj, final StringBuilder toAppendTo) {
+ delegate.format(obj, toAppendTo);
+ }
+
+ @Override
+ public String getName() {
+ return delegate.getName();
+ }
+
+ @Override
+ public String getStyleClass(final Object e) {
+ return delegate.getStyleClass(e);
+ }
+
+ @Override
+ public void format(final StringBuilder toAppendTo, final Object... objects) {
+ delegate.format(toAppendTo, objects);
+ }
+
+ public String getPattern() {
+ return delegate.getPattern();
}
}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/FileLocationPatternConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/FileLocationPatternConverter.java
index 7fcd33d..047bf08 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/FileLocationPatternConverter.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/FileLocationPatternConverter.java
@@ -17,7 +17,7 @@
package org.apache.logging.log4j.core.pattern;
import org.apache.logging.log4j.core.LogEvent;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.plugins.Plugin;
/**
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/FormattingInfo.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/FormattingInfo.java
index 0b9f2b9..bd88679 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/FormattingInfo.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/FormattingInfo.java
@@ -30,6 +30,11 @@
private static final char[] SPACES = new char[] { ' ', ' ', ' ', ' ', ' ', ' ', ' ', ' ' };
/**
+ * Array of zeros.
+ */
+ private static final char[] ZEROS = new char[] { '0', '0', '0', '0', '0', '0', '0', '0' };
+
+ /**
* Default instance.
*/
private static final FormattingInfo DEFAULT = new FormattingInfo(false, 0, Integer.MAX_VALUE, true);
@@ -55,6 +60,11 @@
private final boolean leftTruncate;
/**
+ * Use zero-padding instead whitespace padding
+ */
+ private final boolean zeroPad;
+
+ /**
* Creates new instance.
*
* @param leftAlign
@@ -67,10 +77,29 @@
* truncates to the left if true
*/
public FormattingInfo(final boolean leftAlign, final int minLength, final int maxLength, final boolean leftTruncate) {
+ this(leftAlign, minLength, maxLength, leftTruncate, false);
+ }
+
+ /**
+ * Creates new instance.
+ *
+ * @param leftAlign
+ * left align if true.
+ * @param minLength
+ * minimum length.
+ * @param maxLength
+ * maximum length.
+ * @param leftTruncate
+ * truncates to the left if true
+ * @param zeroPad
+ * use zero-padding instead of whitespace-padding
+ */
+ public FormattingInfo(final boolean leftAlign, final int minLength, final int maxLength, final boolean leftTruncate, final boolean zeroPad) {
this.leftAlign = leftAlign;
this.minLength = minLength;
this.maxLength = maxLength;
this.leftTruncate = leftTruncate;
+ this.zeroPad = zeroPad;
}
/**
@@ -101,6 +130,15 @@
}
/**
+ * Determine if zero-padded.
+ *
+ * @return true if zero-padded.
+ */
+ public boolean isZeroPad() {
+ return zeroPad;
+ }
+
+ /**
* Get minimum length.
*
* @return minimum length.
@@ -146,11 +184,13 @@
} else {
int padLength = minLength - rawLength;
- for (; padLength > SPACES.length; padLength -= SPACES.length) {
- buffer.insert(fieldStart, SPACES);
+ final char[] paddingArray= zeroPad ? ZEROS : SPACES;
+
+ for (; padLength > paddingArray.length; padLength -= paddingArray.length) {
+ buffer.insert(fieldStart, paddingArray);
}
- buffer.insert(fieldStart, SPACES, 0, padLength);
+ buffer.insert(fieldStart, paddingArray, 0, padLength);
}
}
}
@@ -172,6 +212,8 @@
sb.append(minLength);
sb.append(", leftTruncate=");
sb.append(leftTruncate);
+ sb.append(", zeroPad=");
+ sb.append(zeroPad);
sb.append(']');
return sb.toString();
}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/FullLocationPatternConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/FullLocationPatternConverter.java
index 120a671..9cf4856 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/FullLocationPatternConverter.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/FullLocationPatternConverter.java
@@ -17,7 +17,7 @@
package org.apache.logging.log4j.core.pattern;
import org.apache.logging.log4j.core.LogEvent;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.plugins.Plugin;
/**
@@ -60,4 +60,9 @@
output.append(element.toString());
}
}
+
+ @Override
+ public boolean requiresLocation() {
+ return true;
+ }
}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/HighlightConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/HighlightConverter.java
index c4b9703..5ac11eb 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/HighlightConverter.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/HighlightConverter.java
@@ -25,7 +25,7 @@
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.config.Configuration;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.plugins.Plugin;
import org.apache.logging.log4j.core.layout.PatternLayout;
import org.apache.logging.log4j.util.PerformanceSensitive;
import org.apache.logging.log4j.util.Strings;
@@ -80,9 +80,9 @@
@PerformanceSensitive("allocation")
public final class HighlightConverter extends LogEventPatternConverter implements AnsiConverter {
- private static final Map<Level, String> DEFAULT_STYLES = new HashMap<>();
+ private static final Map<String, String> DEFAULT_STYLES = new HashMap<>();
- private static final Map<Level, String> LOGBACK_STYLES = new HashMap<>();
+ private static final Map<String, String> LOGBACK_STYLES = new HashMap<>();
private static final String STYLE_KEY = "STYLE";
@@ -90,23 +90,23 @@
private static final String STYLE_KEY_LOGBACK = "LOGBACK";
- private static final Map<String, Map<Level, String>> STYLES = new HashMap<>();
+ private static final Map<String, Map<String, String>> STYLES = new HashMap<>();
static {
// Default styles:
- DEFAULT_STYLES.put(Level.FATAL, AnsiEscape.createSequence("BRIGHT", "RED"));
- DEFAULT_STYLES.put(Level.ERROR, AnsiEscape.createSequence("BRIGHT", "RED"));
- DEFAULT_STYLES.put(Level.WARN, AnsiEscape.createSequence("YELLOW"));
- DEFAULT_STYLES.put(Level.INFO, AnsiEscape.createSequence("GREEN"));
- DEFAULT_STYLES.put(Level.DEBUG, AnsiEscape.createSequence("CYAN"));
- DEFAULT_STYLES.put(Level.TRACE, AnsiEscape.createSequence("BLACK"));
+ DEFAULT_STYLES.put(Level.FATAL.name(), AnsiEscape.createSequence("BRIGHT", "RED"));
+ DEFAULT_STYLES.put(Level.ERROR.name(), AnsiEscape.createSequence("BRIGHT", "RED"));
+ DEFAULT_STYLES.put(Level.WARN.name(), AnsiEscape.createSequence("YELLOW"));
+ DEFAULT_STYLES.put(Level.INFO.name(), AnsiEscape.createSequence("GREEN"));
+ DEFAULT_STYLES.put(Level.DEBUG.name(), AnsiEscape.createSequence("CYAN"));
+ DEFAULT_STYLES.put(Level.TRACE.name(), AnsiEscape.createSequence("BLACK"));
// Logback styles:
- LOGBACK_STYLES.put(Level.FATAL, AnsiEscape.createSequence("BLINK", "BRIGHT", "RED"));
- LOGBACK_STYLES.put(Level.ERROR, AnsiEscape.createSequence("BRIGHT", "RED"));
- LOGBACK_STYLES.put(Level.WARN, AnsiEscape.createSequence("RED"));
- LOGBACK_STYLES.put(Level.INFO, AnsiEscape.createSequence("BLUE"));
- LOGBACK_STYLES.put(Level.DEBUG, AnsiEscape.createSequence((String[]) null));
- LOGBACK_STYLES.put(Level.TRACE, AnsiEscape.createSequence((String[]) null));
+ LOGBACK_STYLES.put(Level.FATAL.name(), AnsiEscape.createSequence("BLINK", "BRIGHT", "RED"));
+ LOGBACK_STYLES.put(Level.ERROR.name(), AnsiEscape.createSequence("BRIGHT", "RED"));
+ LOGBACK_STYLES.put(Level.WARN.name(), AnsiEscape.createSequence("RED"));
+ LOGBACK_STYLES.put(Level.INFO.name(), AnsiEscape.createSequence("BLUE"));
+ LOGBACK_STYLES.put(Level.DEBUG.name(), AnsiEscape.createSequence((String[]) null));
+ LOGBACK_STYLES.put(Level.TRACE.name(), AnsiEscape.createSequence((String[]) null));
// Style map:
STYLES.put(STYLE_KEY_DEFAULT, DEFAULT_STYLES);
STYLES.put(STYLE_KEY_LOGBACK, LOGBACK_STYLES);
@@ -140,7 +140,7 @@
* The second slot can optionally contain the style map.
* @return a new map
*/
- private static Map<Level, String> createLevelStyleMap(final String[] options) {
+ private static Map<String, String> createLevelStyleMap(final String[] options) {
if (options.length < 2) {
return DEFAULT_STYLES;
}
@@ -150,12 +150,12 @@
.replaceAll(PatternParser.NO_CONSOLE_NO_ANSI + "=(true|false)", Strings.EMPTY);
//
final Map<String, String> styles = AnsiEscape.createMap(string, new String[] {STYLE_KEY});
- final Map<Level, String> levelStyles = new HashMap<>(DEFAULT_STYLES);
+ final Map<String, String> levelStyles = new HashMap<>(DEFAULT_STYLES);
for (final Map.Entry<String, String> entry : styles.entrySet()) {
final String key = entry.getKey().toUpperCase(Locale.ENGLISH);
final String value = entry.getValue();
if (STYLE_KEY.equalsIgnoreCase(key)) {
- final Map<Level, String> enumMap = STYLES.get(value.toUpperCase(Locale.ENGLISH));
+ final Map<String, String> enumMap = STYLES.get(value.toUpperCase(Locale.ENGLISH));
if (enumMap == null) {
LOGGER.error("Unknown level style: " + value + ". Use one of " +
Arrays.toString(STYLES.keySet().toArray()));
@@ -165,9 +165,10 @@
} else {
final Level level = Level.toLevel(key, null);
if (level == null) {
- LOGGER.error("Unknown level name: {}; use one of {}", key, Arrays.toString(Level.values()));
+ LOGGER.warn("Setting style for yet unknown level name {}", key);
+ levelStyles.put(key, value);
} else {
- levelStyles.put(level, value);
+ levelStyles.put(level.name(), value);
}
}
}
@@ -199,7 +200,7 @@
return new HighlightConverter(formatters, createLevelStyleMap(options), hideAnsi);
}
- private final Map<Level, String> levelStyles;
+ private final Map<String, String> levelStyles;
private final List<PatternFormatter> patternFormatters;
@@ -215,7 +216,7 @@
* @param noAnsi
* If true, do not output ANSI escape codes.
*/
- private HighlightConverter(final List<PatternFormatter> patternFormatters, final Map<Level, String> levelStyles, final boolean noAnsi) {
+ private HighlightConverter(final List<PatternFormatter> patternFormatters, final Map<String, String> levelStyles, final boolean noAnsi) {
super("style", "style");
this.patternFormatters = patternFormatters;
this.levelStyles = levelStyles;
@@ -230,9 +231,12 @@
public void format(final LogEvent event, final StringBuilder toAppendTo) {
int start = 0;
int end = 0;
+ String levelStyle = levelStyles.get(event.getLevel().name());
if (!noAnsi) { // use ANSI: set prefix
start = toAppendTo.length();
- toAppendTo.append(levelStyles.get(event.getLevel()));
+ if (levelStyle != null) {
+ toAppendTo.append(levelStyle);
+ }
end = toAppendTo.length();
}
@@ -246,14 +250,14 @@
if (!noAnsi) {
if (empty) {
toAppendTo.setLength(start); // erase prefix
- } else {
+ } else if (levelStyle != null) {
toAppendTo.append(defaultStyle); // add postfix
}
}
}
String getLevelStyle(Level level) {
- return levelStyles.get(level);
+ return levelStyles.get(level.name());
}
@Override
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/IntegerPatternConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/IntegerPatternConverter.java
index 4a41da4..8096530 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/IntegerPatternConverter.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/IntegerPatternConverter.java
@@ -18,7 +18,7 @@
import java.util.Date;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.plugins.Plugin;
import org.apache.logging.log4j.util.PerformanceSensitive;
/**
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/LevelPatternConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/LevelPatternConverter.java
index debee5a..46b448c 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/LevelPatternConverter.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/LevelPatternConverter.java
@@ -22,7 +22,7 @@
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.core.LogEvent;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.plugins.Plugin;
import org.apache.logging.log4j.core.util.Patterns;
import org.apache.logging.log4j.util.PerformanceSensitive;
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/LineLocationPatternConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/LineLocationPatternConverter.java
index 2b912d7..97805c2 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/LineLocationPatternConverter.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/LineLocationPatternConverter.java
@@ -17,7 +17,7 @@
package org.apache.logging.log4j.core.pattern;
import org.apache.logging.log4j.core.LogEvent;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.plugins.Plugin;
/**
* Returns the event's line location information in a StringBuilder.
@@ -60,4 +60,9 @@
output.append(element.getLineNumber());
}
}
+
+ @Override
+ public boolean requiresLocation() {
+ return true;
+ }
}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/LineSeparatorPatternConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/LineSeparatorPatternConverter.java
index 1d7b022..103e87a 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/LineSeparatorPatternConverter.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/LineSeparatorPatternConverter.java
@@ -17,7 +17,7 @@
package org.apache.logging.log4j.core.pattern;
import org.apache.logging.log4j.core.LogEvent;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.plugins.Plugin;
import org.apache.logging.log4j.util.PerformanceSensitive;
import org.apache.logging.log4j.util.Strings;
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/LogEventPatternConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/LogEventPatternConverter.java
index 84cee32..2ff4297 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/LogEventPatternConverter.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/LogEventPatternConverter.java
@@ -65,6 +65,15 @@
return false;
}
+ /**
+ * Some pattern converters require location information. By returning true the location can be
+ * calculated efficiently.
+ * @return true if this PatternConverter uses location information.
+ */
+ public boolean requiresLocation() {
+ return false;
+ }
+
public boolean isVariable() {
return true;
}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/LoggerFqcnPatternConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/LoggerFqcnPatternConverter.java
index cba32c9..e6efe24 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/LoggerFqcnPatternConverter.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/LoggerFqcnPatternConverter.java
@@ -17,7 +17,7 @@
package org.apache.logging.log4j.core.pattern;
import org.apache.logging.log4j.core.LogEvent;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.plugins.Plugin;
import org.apache.logging.log4j.util.PerformanceSensitive;
/**
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/LoggerPatternConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/LoggerPatternConverter.java
index 609af7a..ec140eb 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/LoggerPatternConverter.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/LoggerPatternConverter.java
@@ -17,7 +17,7 @@
package org.apache.logging.log4j.core.pattern;
import org.apache.logging.log4j.core.LogEvent;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.plugins.Plugin;
import org.apache.logging.log4j.util.PerformanceSensitive;
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/MapPatternConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/MapPatternConverter.java
index 6f018bd..8b684a4 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/MapPatternConverter.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/MapPatternConverter.java
@@ -17,9 +17,11 @@
package org.apache.logging.log4j.core.pattern;
import org.apache.logging.log4j.core.LogEvent;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.message.StringMapMessage;
-import org.apache.logging.log4j.util.IndexedReadOnlyStringMap;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.message.MapMessage;
+import org.apache.logging.log4j.message.MapMessage.MapFormat;
+
+import java.util.Objects;
/**
* Able to handle the contents of the LogEvent's MapMessage and either
@@ -30,29 +32,53 @@
@Plugin(name = "MapPatternConverter", category = PatternConverter.CATEGORY)
@ConverterKeys({ "K", "map", "MAP" })
public final class MapPatternConverter extends LogEventPatternConverter {
+
+ private static final String JAVA_UNQUOTED = MapFormat.JAVA_UNQUOTED.name();
+
/**
* Name of property to output.
*/
private final String key;
/**
+ * Format to use when no key is provided.
+ *
+ * @see MapFormat
+ * @since 2.11.2
+ */
+ private final String[] format;
+
+ /**
* Private constructor.
*
* @param options options, may be null.
*/
- private MapPatternConverter(final String[] options) {
+ private MapPatternConverter(final String[] options, String... format) {
super(options != null && options.length > 0 ? "MAP{" + options[0] + '}' : "MAP", "map");
key = options != null && options.length > 0 ? options[0] : null;
+ this.format = format;
}
/**
- * Obtains an instance of PropertiesPatternConverter.
+ * Obtains an instance of {@link MapPatternConverter}.
*
* @param options options, may be null or first element contains name of property to format.
- * @return instance of PropertiesPatternConverter.
+ * @return instance of {@link MapPatternConverter}.
*/
public static MapPatternConverter newInstance(final String[] options) {
- return new MapPatternConverter(options);
+ return new MapPatternConverter(options, JAVA_UNQUOTED);
+ }
+
+ /**
+ * Obtain an instance of {@link MapPatternConverter}.
+ *
+ * @param options options, may be null or first element contains name of property to format.
+ * @param format the format to use if no options are given (i.e., options is null). Ignored if options is non-null.
+ * @return instance of {@link MapPatternConverter}.
+ * @since 2.11.2
+ */
+ public static MapPatternConverter newInstance(final String[] options, final MapFormat format) {
+ return new MapPatternConverter(options, Objects.toString(format, JAVA_UNQUOTED));
}
/**
@@ -60,31 +86,19 @@
*/
@Override
public void format(final LogEvent event, final StringBuilder toAppendTo) {
- StringMapMessage msg;
- if (event.getMessage() instanceof StringMapMessage) {
- msg = (StringMapMessage) event.getMessage();
+ MapMessage msg;
+ if (event.getMessage() instanceof MapMessage) {
+ msg = (MapMessage) event.getMessage();
} else {
return;
}
- final IndexedReadOnlyStringMap sortedMap = msg.getIndexedReadOnlyStringMap();
// if there is no additional options, we output every single
// Key/Value pair for the Map in a similar format to Hashtable.toString()
if (key == null) {
- if (sortedMap.isEmpty()) {
- toAppendTo.append("{}");
- return;
- }
- toAppendTo.append("{");
- for (int i = 0; i < sortedMap.size(); i++) {
- if (i > 0) {
- toAppendTo.append(", ");
- }
- toAppendTo.append(sortedMap.getKeyAt(i)).append('=').append((String)sortedMap.getValueAt(i));
- }
- toAppendTo.append('}');
+ msg.formatTo(format, toAppendTo);
} else {
// otherwise they just want a single key output
- final String val = sortedMap.getValue(key);
+ final String val = msg.get(key);
if (val != null) {
toAppendTo.append(val);
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/MarkerPatternConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/MarkerPatternConverter.java
index dde5502..9b3e4c9 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/MarkerPatternConverter.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/MarkerPatternConverter.java
@@ -18,7 +18,7 @@
import org.apache.logging.log4j.Marker;
import org.apache.logging.log4j.core.LogEvent;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.plugins.Plugin;
import org.apache.logging.log4j.util.PerformanceSensitive;
import org.apache.logging.log4j.util.StringBuilders;
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/MarkerSimpleNamePatternConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/MarkerSimpleNamePatternConverter.java
index b274eb1..341885c 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/MarkerSimpleNamePatternConverter.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/MarkerSimpleNamePatternConverter.java
@@ -18,7 +18,7 @@
import org.apache.logging.log4j.Marker;
import org.apache.logging.log4j.core.LogEvent;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.plugins.Plugin;
import org.apache.logging.log4j.util.PerformanceSensitive;
/**
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/MaxLengthConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/MaxLengthConverter.java
index 1a59a4b..4f301c2 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/MaxLengthConverter.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/MaxLengthConverter.java
@@ -22,7 +22,7 @@
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.appender.AbstractAppender;
import org.apache.logging.log4j.core.config.Configuration;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.plugins.Plugin;
import org.apache.logging.log4j.core.layout.PatternLayout;
import org.apache.logging.log4j.util.PerformanceSensitive;
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/MdcPatternConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/MdcPatternConverter.java
index b39c15e..b7b3c1b 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/MdcPatternConverter.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/MdcPatternConverter.java
@@ -19,7 +19,7 @@
import org.apache.logging.log4j.util.PerformanceSensitive;
import org.apache.logging.log4j.util.ReadOnlyStringMap;
import org.apache.logging.log4j.core.LogEvent;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.plugins.Plugin;
import org.apache.logging.log4j.util.TriConsumer;
import org.apache.logging.log4j.util.StringBuilders;
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/MessagePatternConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/MessagePatternConverter.java
index 92d75a2..d76388d 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/MessagePatternConverter.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/MessagePatternConverter.java
@@ -20,7 +20,7 @@
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.config.Configuration;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.plugins.Plugin;
import org.apache.logging.log4j.core.util.ArrayUtils;
import org.apache.logging.log4j.core.util.Constants;
import org.apache.logging.log4j.core.util.Loader;
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/MethodLocationPatternConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/MethodLocationPatternConverter.java
index 457b7a6..b697685 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/MethodLocationPatternConverter.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/MethodLocationPatternConverter.java
@@ -17,7 +17,7 @@
package org.apache.logging.log4j.core.pattern;
import org.apache.logging.log4j.core.LogEvent;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.plugins.Plugin;
/**
@@ -60,4 +60,9 @@
toAppendTo.append(element.getMethodName());
}
}
+
+ @Override
+ public boolean requiresLocation() {
+ return true;
+ }
}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/NanoTimePatternConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/NanoTimePatternConverter.java
index ba6186b..6fc3c0f 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/NanoTimePatternConverter.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/NanoTimePatternConverter.java
@@ -17,7 +17,7 @@
package org.apache.logging.log4j.core.pattern;
import org.apache.logging.log4j.core.LogEvent;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.plugins.Plugin;
import org.apache.logging.log4j.util.PerformanceSensitive;
/**
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/NdcPatternConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/NdcPatternConverter.java
index 754ea9a..7749c03 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/NdcPatternConverter.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/NdcPatternConverter.java
@@ -17,7 +17,7 @@
package org.apache.logging.log4j.core.pattern;
import org.apache.logging.log4j.core.LogEvent;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.plugins.Plugin;
import org.apache.logging.log4j.util.PerformanceSensitive;
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/PatternFormatter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/PatternFormatter.java
index 4bde1e2..de6526b 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/PatternFormatter.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/PatternFormatter.java
@@ -68,6 +68,15 @@
}
/**
+ * Most pattern formatters do not use location information. When they do they should return true here
+ * so that the logging system can efficiently capture it.
+ * @return true if location information is required.
+ */
+ public boolean requiresLocation() {
+ return converter.requiresLocation();
+ }
+
+ /**
* Returns a String suitable for debugging.
*
* @return a String suitable for debugging.
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/PatternParser.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/PatternParser.java
index 21747e0..21c3ffb 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/PatternParser.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/PatternParser.java
@@ -27,9 +27,9 @@
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.core.config.Configuration;
-import org.apache.logging.log4j.core.config.plugins.util.PluginManager;
-import org.apache.logging.log4j.core.config.plugins.util.PluginType;
import org.apache.logging.log4j.core.time.SystemNanoClock;
+import org.apache.logging.log4j.plugins.util.PluginManager;
+import org.apache.logging.log4j.plugins.util.PluginType;
import org.apache.logging.log4j.status.StatusLogger;
import org.apache.logging.log4j.util.Strings;
@@ -87,7 +87,7 @@
private final Configuration config;
- private final Map<String, Class<PatternConverter>> converterRules;
+ private final Map<String, Class<? extends PatternConverter>> converterRules;
/**
* Constructor.
@@ -131,12 +131,11 @@
final PluginManager manager = new PluginManager(converterKey);
manager.collectPlugins(config == null ? null : config.getPluginPackages());
final Map<String, PluginType<?>> plugins = manager.getPlugins();
- final Map<String, Class<PatternConverter>> converters = new LinkedHashMap<>();
+ final Map<String, Class<? extends PatternConverter>> converters = new LinkedHashMap<>();
for (final PluginType<?> type : plugins.values()) {
try {
- @SuppressWarnings("unchecked")
- final Class<PatternConverter> clazz = (Class<PatternConverter>) type.getPluginClass();
+ final Class<? extends PatternConverter> clazz = type.getPluginClass().asSubclass(PatternConverter.class);
if (filterClass != null && !filterClass.isAssignableFrom(clazz)) {
continue;
}
@@ -396,9 +395,15 @@
currentLiteral.append(c);
switch (c) {
+ case '0':
+ // a '0' directly after the % sign indicates zero-padding
+ formattingInfo = new FormattingInfo(formattingInfo.isLeftAligned(), formattingInfo.getMinLength(),
+ formattingInfo.getMaxLength(), formattingInfo.isLeftTruncate(), true);
+ break;
+
case '-':
formattingInfo = new FormattingInfo(true, formattingInfo.getMinLength(),
- formattingInfo.getMaxLength(), formattingInfo.isLeftTruncate());
+ formattingInfo.getMaxLength(), formattingInfo.isLeftTruncate(), formattingInfo.isZeroPad());
break;
case '.':
@@ -409,7 +414,7 @@
if (c >= '0' && c <= '9') {
formattingInfo = new FormattingInfo(formattingInfo.isLeftAligned(), c - '0',
- formattingInfo.getMaxLength(), formattingInfo.isLeftTruncate());
+ formattingInfo.getMaxLength(), formattingInfo.isLeftTruncate(), formattingInfo.isZeroPad());
state = ParserState.MIN_STATE;
} else {
i = finalizeConverter(c, pattern, i, currentLiteral, formattingInfo, converterRules,
@@ -430,7 +435,7 @@
if (c >= '0' && c <= '9') {
// Multiply the existing value and add the value of the number just encountered.
formattingInfo = new FormattingInfo(formattingInfo.isLeftAligned(), formattingInfo.getMinLength()
- * DECIMAL + c - '0', formattingInfo.getMaxLength(), formattingInfo.isLeftTruncate());
+ * DECIMAL + c - '0', formattingInfo.getMaxLength(), formattingInfo.isLeftTruncate(), formattingInfo.isZeroPad());
} else if (c == '.') {
state = ParserState.DOT_STATE;
} else {
@@ -448,14 +453,14 @@
switch (c) {
case '-':
formattingInfo = new FormattingInfo(formattingInfo.isLeftAligned(), formattingInfo.getMinLength(),
- formattingInfo.getMaxLength(),false);
+ formattingInfo.getMaxLength(),false, formattingInfo.isZeroPad());
break;
default:
if (c >= '0' && c <= '9') {
formattingInfo = new FormattingInfo(formattingInfo.isLeftAligned(), formattingInfo.getMinLength(),
- c - '0', formattingInfo.isLeftTruncate());
+ c - '0', formattingInfo.isLeftTruncate(), formattingInfo.isZeroPad());
state = ParserState.MAX_STATE;
} else {
LOGGER.error("Error occurred in position " + i + ".\n Was expecting digit, instead got char \"" + c
@@ -473,7 +478,7 @@
if (c >= '0' && c <= '9') {
// Multiply the existing value and add the value of the number just encountered.
formattingInfo = new FormattingInfo(formattingInfo.isLeftAligned(), formattingInfo.getMinLength(),
- formattingInfo.getMaxLength() * DECIMAL + c - '0', formattingInfo.isLeftTruncate());
+ formattingInfo.getMaxLength() * DECIMAL + c - '0', formattingInfo.isLeftTruncate(), formattingInfo.isZeroPad());
} else {
i = finalizeConverter(c, pattern, i, currentLiteral, formattingInfo, converterRules,
patternConverters, formattingInfos, disableAnsi, noConsoleNoAnsi, convertBackslashes);
@@ -512,10 +517,10 @@
* @return converter or null.
*/
private PatternConverter createConverter(final String converterId, final StringBuilder currentLiteral,
- final Map<String, Class<PatternConverter>> rules, final List<String> options, final boolean disableAnsi,
+ final Map<String, Class<? extends PatternConverter>> rules, final List<String> options, final boolean disableAnsi,
final boolean noConsoleNoAnsi) {
String converterName = converterId;
- Class<PatternConverter> converterClass = null;
+ Class<? extends PatternConverter> converterClass = null;
if (rules == null) {
LOGGER.error("Null rules for [" + converterId + ']');
@@ -540,8 +545,10 @@
final Method[] methods = converterClass.getDeclaredMethods();
Method newInstanceMethod = null;
for (final Method method : methods) {
- if (Modifier.isStatic(method.getModifiers()) && method.getDeclaringClass().equals(converterClass)
- && method.getName().equals("newInstance")) {
+ if (Modifier.isStatic(method.getModifiers())
+ && method.getDeclaringClass().equals(converterClass)
+ && method.getName().equals("newInstance")
+ && areValidNewInstanceParameters(method.getParameterTypes())) {
if (newInstanceMethod == null) {
newInstanceMethod = method;
} else if (method.getReturnType().equals(newInstanceMethod.getReturnType())) {
@@ -595,6 +602,17 @@
return null;
}
+ /** LOG4J2-2564: Returns true if all method parameters are valid for injection. */
+ private static boolean areValidNewInstanceParameters(Class<?>[] parameterTypes) {
+ for (Class<?> clazz : parameterTypes) {
+ if (!clazz.isAssignableFrom(Configuration.class)
+ && !(clazz.isArray() && "[Ljava.lang.String;".equals(clazz.getName()))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
/**
* Processes a format specifier sequence.
*
@@ -624,7 +642,7 @@
*/
private int finalizeConverter(final char c, final String pattern, final int start,
final StringBuilder currentLiteral, final FormattingInfo formattingInfo,
- final Map<String, Class<PatternConverter>> rules, final List<PatternConverter> patternConverters,
+ final Map<String, Class<? extends PatternConverter>> rules, final List<PatternConverter> patternConverters,
final List<FormattingInfo> formattingInfos, final boolean disableAnsi, final boolean noConsoleNoAnsi,
final boolean convertBackslashes) {
int i = start;
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/ProcessIdPatternConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/ProcessIdPatternConverter.java
index d182dc0..4d73cdc 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/ProcessIdPatternConverter.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/ProcessIdPatternConverter.java
@@ -17,8 +17,8 @@
package org.apache.logging.log4j.core.pattern;
import org.apache.logging.log4j.core.LogEvent;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.util.ProcessIdUtil;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.core.util.ProcessIdUtil;
@Plugin(name = "ProcessIdPatternConverter", category = "Converter")
@ConverterKeys({ "pid", "processId" })
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/RegexReplacement.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/RegexReplacement.java
index 47ce355..0d6961b 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/RegexReplacement.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/RegexReplacement.java
@@ -20,9 +20,9 @@
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.core.Core;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
-import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginAttribute;
+import org.apache.logging.log4j.plugins.PluginFactory;
import org.apache.logging.log4j.status.StatusLogger;
/**
@@ -70,8 +70,8 @@
*/
@PluginFactory
public static RegexReplacement createRegexReplacement(
- @PluginAttribute("regex") final Pattern regex,
- @PluginAttribute("replacement") final String replacement) {
+ @PluginAttribute final Pattern regex,
+ @PluginAttribute final String replacement) {
if (regex == null) {
LOGGER.error("A regular expression is required for replacement");
return null;
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/RegexReplacementConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/RegexReplacementConverter.java
index f7c4697..28e07bb 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/RegexReplacementConverter.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/RegexReplacementConverter.java
@@ -21,7 +21,7 @@
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.config.Configuration;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.plugins.Plugin;
import org.apache.logging.log4j.core.layout.PatternLayout;
/**
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/RelativeTimePatternConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/RelativeTimePatternConverter.java
index ffc1ed4..3d575fb 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/RelativeTimePatternConverter.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/RelativeTimePatternConverter.java
@@ -19,7 +19,7 @@
import java.lang.management.ManagementFactory;
import org.apache.logging.log4j.core.LogEvent;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.plugins.Plugin;
import org.apache.logging.log4j.util.PerformanceSensitive;
/**
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/RootThrowablePatternConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/RootThrowablePatternConverter.java
index 33bc095..46e09d1 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/RootThrowablePatternConverter.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/RootThrowablePatternConverter.java
@@ -18,7 +18,7 @@
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.config.Configuration;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.plugins.Plugin;
import org.apache.logging.log4j.core.impl.ThrowableProxy;
import org.apache.logging.log4j.util.Strings;
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/SequenceNumberPatternConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/SequenceNumberPatternConverter.java
index b607cbf..202070f 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/SequenceNumberPatternConverter.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/SequenceNumberPatternConverter.java
@@ -19,7 +19,7 @@
import java.util.concurrent.atomic.AtomicLong;
import org.apache.logging.log4j.core.LogEvent;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.plugins.Plugin;
import org.apache.logging.log4j.util.PerformanceSensitive;
/**
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/StyleConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/StyleConverter.java
index acbf2b6..1c35676 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/StyleConverter.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/StyleConverter.java
@@ -21,7 +21,7 @@
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.config.Configuration;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.plugins.Plugin;
import org.apache.logging.log4j.core.layout.PatternLayout;
import org.apache.logging.log4j.core.util.Patterns;
import org.apache.logging.log4j.util.PerformanceSensitive;
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/ThreadIdPatternConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/ThreadIdPatternConverter.java
index 1f5bc12..85752ab 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/ThreadIdPatternConverter.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/ThreadIdPatternConverter.java
@@ -17,7 +17,7 @@
package org.apache.logging.log4j.core.pattern;
import org.apache.logging.log4j.core.LogEvent;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.plugins.Plugin;
import org.apache.logging.log4j.util.PerformanceSensitive;
/**
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/ThreadNamePatternConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/ThreadNamePatternConverter.java
index de3e824..060df3d 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/ThreadNamePatternConverter.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/ThreadNamePatternConverter.java
@@ -17,7 +17,7 @@
package org.apache.logging.log4j.core.pattern;
import org.apache.logging.log4j.core.LogEvent;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.plugins.Plugin;
import org.apache.logging.log4j.util.PerformanceSensitive;
/**
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/ThreadPriorityPatternConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/ThreadPriorityPatternConverter.java
index b520892..72fc77a 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/ThreadPriorityPatternConverter.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/ThreadPriorityPatternConverter.java
@@ -17,7 +17,7 @@
package org.apache.logging.log4j.core.pattern;
import org.apache.logging.log4j.core.LogEvent;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.plugins.Plugin;
import org.apache.logging.log4j.util.PerformanceSensitive;
/**
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/ThrowablePatternConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/ThrowablePatternConverter.java
index 7b9679c..5de51d8 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/ThrowablePatternConverter.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/ThrowablePatternConverter.java
@@ -24,7 +24,7 @@
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.config.Configuration;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.plugins.Plugin;
import org.apache.logging.log4j.core.impl.ThrowableFormatOptions;
import org.apache.logging.log4j.core.layout.PatternLayout;
import org.apache.logging.log4j.core.util.StringBuilderWriter;
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/UuidPatternConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/UuidPatternConverter.java
index 439935b..d82fed1 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/UuidPatternConverter.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/UuidPatternConverter.java
@@ -19,7 +19,7 @@
import java.util.UUID;
import org.apache.logging.log4j.core.LogEvent;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.plugins.Plugin;
import org.apache.logging.log4j.core.util.UuidUtil;
/**
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/VariablesNotEmptyReplacementConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/VariablesNotEmptyReplacementConverter.java
index 1f0d827..8d24a2d 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/VariablesNotEmptyReplacementConverter.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/pattern/VariablesNotEmptyReplacementConverter.java
@@ -20,7 +20,7 @@
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.config.Configuration;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.plugins.Plugin;
import org.apache.logging.log4j.core.layout.PatternLayout;
import org.apache.logging.log4j.util.PerformanceSensitive;
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/script/Script.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/script/Script.java
index b33b1f2..b51eb37 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/script/Script.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/script/Script.java
@@ -16,11 +16,11 @@
*/
package org.apache.logging.log4j.core.script;
-import org.apache.logging.log4j.core.config.Node;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
-import org.apache.logging.log4j.core.config.plugins.PluginFactory;
-import org.apache.logging.log4j.core.config.plugins.PluginValue;
+import org.apache.logging.log4j.plugins.Node;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginAttribute;
+import org.apache.logging.log4j.plugins.PluginFactory;
+import org.apache.logging.log4j.plugins.PluginValue;
/**
* Container for the language and body of a script.
@@ -39,9 +39,9 @@
@PluginFactory
public static Script createScript(
// @formatter:off
- @PluginAttribute("name") final String name,
- @PluginAttribute(ATTR_LANGUAGE) String language,
- @PluginValue(ATTR_SCRIPT_TEXT) final String scriptText) {
+ @PluginAttribute final String name,
+ @PluginAttribute String language,
+ @PluginValue final String scriptText) {
// @formatter:on
if (language == null) {
LOGGER.error("No '{}' attribute provided for {} plugin '{}'", ATTR_LANGUAGE, PLUGIN_NAME, name);
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/script/ScriptFile.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/script/ScriptFile.java
index 9bb2a1d..097c191 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/script/ScriptFile.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/script/ScriptFile.java
@@ -26,10 +26,10 @@
import java.nio.file.Path;
import java.nio.file.Paths;
-import org.apache.logging.log4j.core.config.Node;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
-import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+import org.apache.logging.log4j.plugins.Node;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginAttribute;
+import org.apache.logging.log4j.plugins.PluginFactory;
import org.apache.logging.log4j.core.util.ExtensionLanguageMapping;
import org.apache.logging.log4j.core.util.FileUtils;
import org.apache.logging.log4j.core.util.IOUtils;
@@ -62,11 +62,11 @@
@PluginFactory
public static ScriptFile createScript(
// @formatter:off
- @PluginAttribute("name") String name,
- @PluginAttribute("language") String language,
+ @PluginAttribute String name,
+ @PluginAttribute String language,
@PluginAttribute("path") final String filePathOrUri,
- @PluginAttribute("isWatched") final Boolean isWatched,
- @PluginAttribute("charset") final Charset charset) {
+ @PluginAttribute final Boolean isWatched,
+ @PluginAttribute final Charset charset) {
// @formatter:on
if (filePathOrUri == null) {
LOGGER.error("No script path provided for ScriptFile");
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/script/ScriptManager.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/script/ScriptManager.java
index 1d6deaf..a167aa4 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/script/ScriptManager.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/script/ScriptManager.java
@@ -22,6 +22,7 @@
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.List;
+import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
@@ -80,7 +81,7 @@
final int factorySize = factories.size();
logger.debug("Installed {} script engine{}", factorySize, factorySize != 1 ? "s" : Strings.EMPTY);
for (final ScriptEngineFactory factory : factories) {
- String threading = (String) factory.getParameter(KEY_THREADING);
+ String threading = Objects.toString(factory.getParameter(KEY_THREADING), null);
if (threading == null) {
threading = "Not Thread Safe";
}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/script/ScriptRef.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/script/ScriptRef.java
index 9fe074b..abec150 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/script/ScriptRef.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/script/ScriptRef.java
@@ -17,11 +17,11 @@
package org.apache.logging.log4j.core.script;
import org.apache.logging.log4j.core.config.Configuration;
-import org.apache.logging.log4j.core.config.Node;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
+import org.apache.logging.log4j.plugins.Node;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginAttribute;
import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
-import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+import org.apache.logging.log4j.plugins.PluginFactory;
/**
* Contains a reference to a script defined elsewhere in the configuration.
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/selector/BasicContextSelector.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/selector/BasicContextSelector.java
index 502084d..aac0221 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/selector/BasicContextSelector.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/selector/BasicContextSelector.java
@@ -20,6 +20,7 @@
import java.util.ArrayList;
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;
@@ -32,6 +33,17 @@
private static final LoggerContext CONTEXT = new LoggerContext("Default");
@Override
+ public void shutdown(String fqcn, ClassLoader loader, boolean currentContext, boolean allContexts) {
+ ContextAnchor.THREAD_CONTEXT.get().stop(DEFAULT_STOP_TIMEOUT, TimeUnit.MILLISECONDS);
+ }
+
+ @Override
+ public boolean hasContext(String fqcn, ClassLoader loader, boolean currentContext) {
+ LoggerContext ctx = ContextAnchor.THREAD_CONTEXT.get();
+ 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();
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/selector/ClassLoaderContextSelector.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/selector/ClassLoaderContextSelector.java
index 8ffe5a5..aac9099 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/selector/ClassLoaderContextSelector.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/selector/ClassLoaderContextSelector.java
@@ -25,10 +25,12 @@
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.impl.ContextAnchor;
+import org.apache.logging.log4j.spi.LoggerContextShutdownAware;
import org.apache.logging.log4j.status.StatusLogger;
import org.apache.logging.log4j.util.StackLocatorUtil;
@@ -43,7 +45,7 @@
*
* This ContextSelector should not be used with a Servlet Filter such as the Log4jServletFilter.
*/
-public class ClassLoaderContextSelector implements ContextSelector {
+public class ClassLoaderContextSelector implements ContextSelector, LoggerContextShutdownAware {
private static final AtomicReference<LoggerContext> DEFAULT_CONTEXT = new AtomicReference<>();
@@ -53,6 +55,64 @@
new ConcurrentHashMap<>();
@Override
+ public void shutdown(final String fqcn, final ClassLoader loader, final boolean currentContext,
+ final boolean allContexts) {
+ LoggerContext ctx = null;
+ if (currentContext) {
+ ctx = ContextAnchor.THREAD_CONTEXT.get();
+ } else if (loader != null) {
+ ctx = findContext(loader);
+ } else {
+ final Class<?> clazz = StackLocatorUtil.getCallerClass(fqcn);
+ if (clazz != null) {
+ ctx = findContext(clazz.getClassLoader());
+ }
+ if (ctx == null) {
+ ctx = ContextAnchor.THREAD_CONTEXT.get();
+ }
+ }
+ if (ctx != null) {
+ ctx.stop(DEFAULT_STOP_TIMEOUT, TimeUnit.MILLISECONDS);
+ }
+ }
+
+ @Override
+ public void contextShutdown(org.apache.logging.log4j.spi.LoggerContext loggerContext) {
+ if (loggerContext instanceof LoggerContext) {
+ removeContext((LoggerContext) loggerContext);
+ }
+ }
+
+ @Override
+ public boolean hasContext(final String fqcn, final ClassLoader loader, final boolean currentContext) {
+ LoggerContext ctx;
+ if (currentContext) {
+ ctx = ContextAnchor.THREAD_CONTEXT.get();
+ } else if (loader != null) {
+ ctx = findContext(loader);
+ } else {
+ final Class<?> clazz = StackLocatorUtil.getCallerClass(fqcn);
+ if (clazz != null) {
+ ctx = findContext(clazz.getClassLoader());
+ } else {
+ ctx = ContextAnchor.THREAD_CONTEXT.get();
+ }
+ }
+ return ctx != null && ctx.isStarted();
+ }
+
+ private LoggerContext findContext(ClassLoader loaderOrNull) {
+ final ClassLoader loader = loaderOrNull != null ? loaderOrNull : ClassLoader.getSystemClassLoader();
+ final String name = toContextMapKey(loader);
+ AtomicReference<WeakReference<LoggerContext>> ref = CONTEXT_MAP.get(name);
+ if (ref != null) {
+ final WeakReference<LoggerContext> weakRef = ref.get();
+ return weakRef.get();
+ }
+ return null;
+ }
+
+ @Override
public LoggerContext getContext(final String fqcn, final ClassLoader loader, final boolean currentContext) {
return getContext(fqcn, loader, currentContext, null);
}
@@ -143,11 +203,12 @@
}
}
LoggerContext ctx = createContext(name, configLocation);
- final AtomicReference<WeakReference<LoggerContext>> r = new AtomicReference<>();
- r.set(new WeakReference<>(ctx));
- CONTEXT_MAP.putIfAbsent(name, r);
- ctx = CONTEXT_MAP.get(name).get().get();
- return ctx;
+ LoggerContext newContext = CONTEXT_MAP.computeIfAbsent(name,
+ k -> new AtomicReference<>(new WeakReference<>(ctx))).get().get();
+ if (newContext != null && newContext == ctx) {
+ newContext.addShutdownListener(this);
+ }
+ return newContext;
}
final WeakReference<LoggerContext> weakRef = ref.get();
LoggerContext ctx = weakRef.get();
@@ -163,7 +224,9 @@
return ctx;
}
ctx = createContext(name, configLocation);
- ref.compareAndSet(weakRef, new WeakReference<>(ctx));
+ if (ref.compareAndSet(weakRef, new WeakReference<>(ctx))) {
+ ctx.addShutdownListener(this);
+ }
return ctx;
}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/selector/ContextSelector.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/selector/ContextSelector.java
index 65c4dd7..3eb0837 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/selector/ContextSelector.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/selector/ContextSelector.java
@@ -18,6 +18,7 @@
import java.net.URI;
import java.util.List;
+import java.util.concurrent.TimeUnit;
import org.apache.logging.log4j.core.LoggerContext;
@@ -26,6 +27,36 @@
*/
public interface ContextSelector {
+ long DEFAULT_STOP_TIMEOUT = 50;
+
+ /**
+ * Shuts down the LoggerContext.
+ * @param fqcn The fully qualified class name of the caller.
+ * @param loader The ClassLoader to use or null.
+ * @param currentContext If true returns the current Context, if false returns the Context appropriate
+ * @param allContexts if true all LoggerContexts that can be located will be shutdown.
+ * @since 2.13.0
+ */
+ default void shutdown(final String fqcn, final ClassLoader loader, final boolean currentContext,
+ final boolean allContexts) {
+ if (hasContext(fqcn, loader, currentContext)) {
+ getContext(fqcn, loader, currentContext).stop(DEFAULT_STOP_TIMEOUT, TimeUnit.MILLISECONDS);
+ }
+ }
+
+ /**
+ * Checks to see if a LoggerContext is installed. The default implementation returns false.
+ * @param fqcn The fully qualified class name of the caller.
+ * @param loader The ClassLoader to use or null.
+ * @param currentContext If true returns the current Context, if false returns the Context appropriate
+ * for the caller if a more appropriate Context can be determined.
+ * @return true if a LoggerContext has been installed, false otherwise.
+ * @since 2.13.0
+ */
+ default boolean hasContext(String fqcn, ClassLoader loader, boolean currentContext) {
+ return false;
+ }
+
/**
* Returns the LoggerContext.
* @param fqcn The fully qualified class name of the caller.
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/selector/JndiContextSelector.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/selector/JndiContextSelector.java
index b790eaf..36571fd 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/selector/JndiContextSelector.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/selector/JndiContextSelector.java
@@ -23,6 +23,7 @@
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.TimeUnit;
import javax.naming.NamingException;
import org.apache.logging.log4j.core.LoggerContext;
@@ -93,6 +94,33 @@
private static final StatusLogger LOGGER = StatusLogger.getLogger();
@Override
+ public void shutdown(String fqcn, ClassLoader loader, boolean currentContext, boolean allContexts) {
+ LoggerContext ctx = ContextAnchor.THREAD_CONTEXT.get();
+ if (ctx == null) {
+ String loggingContextName = getContextName();
+ if (loggingContextName != null) {
+ ctx = CONTEXT_MAP.get(loggingContextName);
+ }
+ }
+ if (ctx != null) {
+ ctx.stop(DEFAULT_STOP_TIMEOUT, TimeUnit.MILLISECONDS);
+ }
+ }
+
+ @Override
+ public boolean hasContext(String fqcn, ClassLoader loader, boolean currentContext) {
+ LoggerContext ctx = ContextAnchor.THREAD_CONTEXT.get();
+ if (ctx == null) {
+ String loggingContextName = getContextName();
+ if (loggingContextName == null) {
+ return false;
+ }
+ ctx = CONTEXT_MAP.get(loggingContextName);
+ }
+ return ctx != null && ctx.isStarted();
+ }
+
+ @Override
public LoggerContext getContext(final String fqcn, final ClassLoader loader, final boolean currentContext) {
return getContext(fqcn, loader, currentContext, null);
}
@@ -106,6 +134,12 @@
return lc;
}
+ String loggingContextName = getContextName();
+
+ return loggingContextName == null ? CONTEXT : locateContext(loggingContextName, null, configLocation);
+ }
+
+ private String getContextName() {
String loggingContextName = null;
try (final JndiManager jndiManager = JndiManager.getDefaultManager()) {
@@ -113,8 +147,7 @@
} catch (final NamingException ne) {
LOGGER.error("Unable to lookup {}", Constants.JNDI_CONTEXT_NAME, ne);
}
-
- return loggingContextName == null ? CONTEXT : locateContext(loggingContextName, null, configLocation);
+ return loggingContextName;
}
@Override
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/time/Clock.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/time/Clock.java
index a7a52a5..ecf4b38 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/time/Clock.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/time/Clock.java
@@ -18,9 +18,7 @@
/**
* Provides the time stamp used in log events.
- * <p>
- * This interface replaces {@link org.apache.logging.log4j.core.util.Clock}.
- * </p>
+ *
* @since 2.11
*/
public interface Clock {
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/time/ClockFactory.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/time/ClockFactory.java
index d5721c4..4753b51 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/time/ClockFactory.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/time/ClockFactory.java
@@ -30,9 +30,7 @@
/**
* Factory for {@code Clock} objects.
- * <p>
- * This class replaces {@link org.apache.logging.log4j.core.util.ClockFactory}.
- * </p>
+ *
* @since 2.11
*/
public final class ClockFactory {
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/time/MutableInstant.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/time/MutableInstant.java
index 303f75d..8e245d0 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/time/MutableInstant.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/time/MutableInstant.java
@@ -22,7 +22,7 @@
/**
* An instantaneous point on the time line, used for high-precision log event timestamps.
- * Modelled on <a href="https://docs.oracle.com/javase/9/docs/api/index.html?java/time/class-use/Instant.html">java.time.Instant</a>,
+ * Modeled on <a href="https://docs.oracle.com/javase/9/docs/api/index.html?java/time/class-use/Instant.html">java.time.Instant</a>,
* except that this version is mutable to prevent allocating temporary objects that need to be garbage-collected later.
* <p>
* Instances of this class are <em>not</em> thread-safe and should not be shared between threads.
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/time/NanoClock.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/time/NanoClock.java
index 3c1ed96..e5aef57 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/time/NanoClock.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/time/NanoClock.java
@@ -21,9 +21,7 @@
/**
* Provides the {@link LogEvent#getNanoTime() high-resolution time stamp} used in log events.
- * <p>
- * This interface replaces {@link org.apache.logging.log4j.core.util.NanoClock}.
- * </p>
+ *
* @since 2.11
*/
public interface NanoClock {
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/time/SystemNanoClock.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/time/SystemNanoClock.java
index 7577534..50b8a84 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/time/SystemNanoClock.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/time/SystemNanoClock.java
@@ -18,9 +18,7 @@
/**
* Implementation of the {@code NanoClock} interface that returns the system nano time.
- * <p>
- * This class replaces {@link org.apache.logging.log4j.core.util.SystemNanoClock}.
- * </p>
+ *
* @since 2.11
*/
public final class SystemNanoClock implements NanoClock {
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/time/internal/format/FastDateFormat.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/time/internal/format/FastDateFormat.java
index fe0c4c2..5685884 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/time/internal/format/FastDateFormat.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/time/internal/format/FastDateFormat.java
@@ -86,14 +86,17 @@
* FULL locale dependent date or time style.
*/
public static final int FULL = DateFormat.FULL;
+
/**
* LONG locale dependent date or time style.
*/
public static final int LONG = DateFormat.LONG;
+
/**
* MEDIUM locale dependent date or time style.
*/
public static final int MEDIUM = DateFormat.MEDIUM;
+
/**
* SHORT locale dependent date or time style.
*/
@@ -369,6 +372,20 @@
return cache.getDateTimeInstance(dateStyle, timeStyle, timeZone, locale);
}
+ /**
+ * <p>Constructs a new FastDateFormat.</p>
+ *
+ * @param pattern {@link java.text.SimpleDateFormat} compatible pattern
+ * @param timeZone non-null time zone to use
+ * @param locale non-null locale to use
+ * @param centuryStart The start of the 100 year period to use as the "default century" for 2 digit year parsing. If centuryStart is null, defaults to now - 80 years
+ * @throws NullPointerException if pattern, timeZone, or locale is null.
+ * @since 3.0
+ */
+ public static FastDateFormat getDateTimeInstance(final String pattern, final TimeZone timeZone, final Locale locale, final Date centuryStart) {
+ return new FastDateFormat(pattern, timeZone, locale, centuryStart);
+ }
+
// Constructor
//-----------------------------------------------------------------------
/**
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/time/internal/format/FastDateParser.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/time/internal/format/FastDateParser.java
index ce0bc4e..16bc0ce 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/time/internal/format/FastDateParser.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/time/internal/format/FastDateParser.java
@@ -573,7 +573,7 @@
case 'K': // Hour in am/pm (0-11)
return HOUR_STRATEGY;
case 'M':
- return width>=3 ?getLocaleSpecificStrategy(Calendar.MONTH, definingCalendar) :NUMBER_MONTH_STRATEGY;
+ return width >= 3 ? getLocaleSpecificStrategy(Calendar.MONTH, definingCalendar) : NUMBER_MONTH_STRATEGY;
case 'S':
return MILLISECOND_STRATEGY;
case 'W':
@@ -596,7 +596,7 @@
return WEEK_OF_YEAR_STRATEGY;
case 'y':
case 'Y':
- return width>2 ?LITERAL_YEAR_STRATEGY :ABBREVIATED_YEAR_STRATEGY;
+ return width > 2 ? LITERAL_YEAR_STRATEGY : ABBREVIATED_YEAR_STRATEGY;
case 'X':
return ISO8601TimeZoneStrategy.getStrategy(width);
case 'Z':
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/time/internal/format/FastDatePrinter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/time/internal/format/FastDatePrinter.java
index 0798fcd..2e2a12e 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/time/internal/format/FastDatePrinter.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/time/internal/format/FastDatePrinter.java
@@ -519,21 +519,6 @@
}
/**
- * Performs the formatting by applying the rules to the
- * specified calendar.
- *
- * @param calendar the calendar to format
- * @param buf the buffer to format into
- * @return the specified string buffer
- *
- * @deprecated use {@link #format(Calendar)} or {@link #format(Calendar, Appendable)}
- */
- @Deprecated
- protected StringBuffer applyRules(final Calendar calendar, final StringBuffer buf) {
- return (StringBuffer) applyRules(calendar, (Appendable)buf);
- }
-
- /**
* <p>Performs the formatting by applying the rules to the
* specified calendar.</p>
*
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/time/internal/format/FixedDateFormat.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/time/internal/format/FixedDateFormat.java
index a640aea..4316a59 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/time/internal/format/FixedDateFormat.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/time/internal/format/FixedDateFormat.java
@@ -17,20 +17,21 @@
package org.apache.logging.log4j.core.time.internal.format;
-import org.apache.logging.log4j.core.time.Instant;
-
import java.util.Arrays;
import java.util.Calendar;
import java.util.Objects;
import java.util.TimeZone;
import java.util.concurrent.TimeUnit;
+import org.apache.logging.log4j.core.time.Instant;
+
/**
* Custom time formatter that trades flexibility for performance. This formatter only supports the date patterns defined
* in {@link FixedFormat}. For any other date patterns use {@link FastDateFormat}.
* <p>
* Related benchmarks: /log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/TimeFormatBenchmark.java and
* /log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/ThreadsafeDateFormatBenchmark.java
+ * </p>
*/
public class FixedDateFormat {
@@ -38,78 +39,109 @@
* Enumeration over the supported date/time format patterns.
* <p>
* Package protected for unit tests.
+ * </p>
*/
public enum FixedFormat {
/**
* ABSOLUTE time format: {@code "HH:mm:ss,SSS"}.
*/
- ABSOLUTE("HH:mm:ss,SSS", null, 0, ':', 1, ',', 1, 3),
+ ABSOLUTE("HH:mm:ss,SSS", null, 0, ':', 1, ',', 1, 3, null),
/**
* ABSOLUTE time format with microsecond precision: {@code "HH:mm:ss,nnnnnn"}.
*/
- ABSOLUTE_MICROS("HH:mm:ss,nnnnnn", null, 0, ':', 1, ',', 1, 6),
+ ABSOLUTE_MICROS("HH:mm:ss,nnnnnn", null, 0, ':', 1, ',', 1, 6, null),
/**
* ABSOLUTE time format with nanosecond precision: {@code "HH:mm:ss,nnnnnnnnn"}.
*/
- ABSOLUTE_NANOS("HH:mm:ss,nnnnnnnnn", null, 0, ':', 1, ',', 1, 9),
+ ABSOLUTE_NANOS("HH:mm:ss,nnnnnnnnn", null, 0, ':', 1, ',', 1, 9, null),
/**
* ABSOLUTE time format variation with period separator: {@code "HH:mm:ss.SSS"}.
*/
- ABSOLUTE_PERIOD("HH:mm:ss.SSS", null, 0, ':', 1, '.', 1, 3),
+ ABSOLUTE_PERIOD("HH:mm:ss.SSS", null, 0, ':', 1, '.', 1, 3, null),
/**
* COMPACT time format: {@code "yyyyMMddHHmmssSSS"}.
*/
- COMPACT("yyyyMMddHHmmssSSS", "yyyyMMdd", 0, ' ', 0, ' ', 0, 3),
+ COMPACT("yyyyMMddHHmmssSSS", "yyyyMMdd", 0, ' ', 0, ' ', 0, 3, null),
/**
* DATE_AND_TIME time format: {@code "dd MMM yyyy HH:mm:ss,SSS"}.
*/
- DATE("dd MMM yyyy HH:mm:ss,SSS", "dd MMM yyyy ", 0, ':', 1, ',', 1, 3),
+ DATE("dd MMM yyyy HH:mm:ss,SSS", "dd MMM yyyy ", 0, ':', 1, ',', 1, 3, null),
/**
* DATE_AND_TIME time format variation with period separator: {@code "dd MMM yyyy HH:mm:ss.SSS"}.
*/
- DATE_PERIOD("dd MMM yyyy HH:mm:ss.SSS", "dd MMM yyyy ", 0, ':', 1, '.', 1, 3),
+ DATE_PERIOD("dd MMM yyyy HH:mm:ss.SSS", "dd MMM yyyy ", 0, ':', 1, '.', 1, 3, null),
/**
* DEFAULT time format: {@code "yyyy-MM-dd HH:mm:ss,SSS"}.
*/
- DEFAULT("yyyy-MM-dd HH:mm:ss,SSS", "yyyy-MM-dd ", 0, ':', 1, ',', 1, 3),
+ DEFAULT("yyyy-MM-dd HH:mm:ss,SSS", "yyyy-MM-dd ", 0, ':', 1, ',', 1, 3, null),
/**
* DEFAULT time format with microsecond precision: {@code "yyyy-MM-dd HH:mm:ss,nnnnnn"}.
*/
- DEFAULT_MICROS("yyyy-MM-dd HH:mm:ss,nnnnnn", "yyyy-MM-dd ", 0, ':', 1, ',', 1, 6),
+ DEFAULT_MICROS("yyyy-MM-dd HH:mm:ss,nnnnnn", "yyyy-MM-dd ", 0, ':', 1, ',', 1, 6, null),
/**
* DEFAULT time format with nanosecond precision: {@code "yyyy-MM-dd HH:mm:ss,nnnnnnnnn"}.
*/
- DEFAULT_NANOS("yyyy-MM-dd HH:mm:ss,nnnnnnnnn", "yyyy-MM-dd ", 0, ':', 1, ',', 1, 9),
+ DEFAULT_NANOS("yyyy-MM-dd HH:mm:ss,nnnnnnnnn", "yyyy-MM-dd ", 0, ':', 1, ',', 1, 9, null),
/**
* DEFAULT time format variation with period separator: {@code "yyyy-MM-dd HH:mm:ss.SSS"}.
*/
- DEFAULT_PERIOD("yyyy-MM-dd HH:mm:ss.SSS", "yyyy-MM-dd ", 0, ':', 1, '.', 1, 3),
+ DEFAULT_PERIOD("yyyy-MM-dd HH:mm:ss.SSS", "yyyy-MM-dd ", 0, ':', 1, '.', 1, 3, null),
/**
* ISO8601_BASIC time format: {@code "yyyyMMdd'T'HHmmss,SSS"}.
*/
- ISO8601_BASIC("yyyyMMdd'T'HHmmss,SSS", "yyyyMMdd'T'", 2, ' ', 0, ',', 1, 3),
+ ISO8601_BASIC("yyyyMMdd'T'HHmmss,SSS", "yyyyMMdd'T'", 2, ' ', 0, ',', 1, 3, null),
/**
* ISO8601_BASIC time format: {@code "yyyyMMdd'T'HHmmss.SSS"}.
*/
- ISO8601_BASIC_PERIOD("yyyyMMdd'T'HHmmss.SSS", "yyyyMMdd'T'", 2, ' ', 0, '.', 1, 3),
+ ISO8601_BASIC_PERIOD("yyyyMMdd'T'HHmmss.SSS", "yyyyMMdd'T'", 2, ' ', 0, '.', 1, 3, null),
/**
* ISO8601 time format: {@code "yyyy-MM-dd'T'HH:mm:ss,SSS"}.
*/
- ISO8601("yyyy-MM-dd'T'HH:mm:ss,SSS", "yyyy-MM-dd'T'", 2, ':', 1, ',', 1, 3),
+ ISO8601("yyyy-MM-dd'T'HH:mm:ss,SSS", "yyyy-MM-dd'T'", 2, ':', 1, ',', 1, 3, null),
+
+ // TODO Do we even want a format without seconds?
+// /**
+// * ISO8601_OFFSET_DATE_TIME time format: {@code "yyyy-MM-dd'T'HH:mmXXX"}.
+// */
+// // Would need work in org.apache.logging.log4j.core.util.datetime.FixedDateFormat.writeTime(int, char[], int)
+// ISO8601_OFFSET_DATE_TIME("yyyy-MM-dd'T'HH:mmXXX", "yyyy-MM-dd'T'", 2, ':', 1, ' ', 0, 0, FixedTimeZoneFormat.XXX),
+
+ /**
+ * ISO8601 time format: {@code "yyyy-MM-dd'T'HH:mm:ss,SSSX"} with a time zone like {@code -07}.
+ */
+ ISO8601_OFFSET_DATE_TIME_HH("yyyy-MM-dd'T'HH:mm:ss,SSSX", "yyyy-MM-dd'T'", 2, ':', 1, ',', 1, 3,
+ FixedTimeZoneFormat.HH),
+
+ /**
+ * ISO8601 time format: {@code "yyyy-MM-dd'T'HH:mm:ss,SSSXX"} with a time zone like {@code -0700}.
+ */
+ ISO8601_OFFSET_DATE_TIME_HHMM("yyyy-MM-dd'T'HH:mm:ss,SSSXX", "yyyy-MM-dd'T'", 2, ':', 1, ',', 1, 3,
+ FixedTimeZoneFormat.HHMM),
+
+ /**
+ * ISO8601 time format: {@code "yyyy-MM-dd'T'HH:mm:ss,SSSXXX"} with a time zone like {@code -07:00}.
+ */
+ ISO8601_OFFSET_DATE_TIME_HHCMM("yyyy-MM-dd'T'HH:mm:ss,SSSXXX", "yyyy-MM-dd'T'", 2, ':', 1, ',', 1, 3,
+ FixedTimeZoneFormat.HHCMM),
/**
* ISO8601 time format: {@code "yyyy-MM-dd'T'HH:mm:ss.SSS"}.
*/
- ISO8601_PERIOD("yyyy-MM-dd'T'HH:mm:ss.SSS", "yyyy-MM-dd'T'", 2, ':', 1, '.', 1, 3);
+ ISO8601_PERIOD("yyyy-MM-dd'T'HH:mm:ss.SSS", "yyyy-MM-dd'T'", 2, ':', 1, '.', 1, 3, null),
+
+ /**
+ * ISO8601 time format with support for microsecond precision: {@code "yyyy-MM-dd'T'HH:mm:ss.nnnnnn"}.
+ */
+ ISO8601_PERIOD_MICROS("yyyy-MM-dd'T'HH:mm:ss.nnnnnn", "yyyy-MM-dd'T'", 2, ':', 1, '.', 1, 6, null);
private static final String DEFAULT_SECOND_FRACTION_PATTERN = "SSS";
private static final int MILLI_FRACTION_DIGITS = DEFAULT_SECOND_FRACTION_PATTERN.length();
@@ -123,10 +155,11 @@
private final char millisSeparatorChar;
private final int millisSeparatorLength;
private final int secondFractionDigits;
+ private final FixedTimeZoneFormat fixedTimeZoneFormat;
FixedFormat(final String pattern, final String datePattern, final int escapeCount, final char timeSeparator,
final int timeSepLength, final char millisSeparator, final int millisSepLength,
- final int secondFractionDigits) {
+ final int secondFractionDigits, final FixedTimeZoneFormat fixedTimeZoneFormat) {
this.timeSeparatorChar = timeSeparator;
this.timeSeparatorLength = timeSepLength;
this.millisSeparatorChar = millisSeparator;
@@ -135,6 +168,7 @@
this.datePattern = datePattern; // may be null
this.escapeCount = escapeCount;
this.secondFractionDigits = secondFractionDigits;
+ this.fixedTimeZoneFormat = fixedTimeZoneFormat;
}
/**
@@ -171,9 +205,12 @@
}
static FixedFormat lookupIgnoringNanos(final String pattern) {
- final int nanoStart = nanoStart(pattern);
+ final int[] nanoRange = nanoRange(pattern);
+ final int nanoStart = nanoRange[0];
+ final int nanoEnd = nanoRange[1];
if (nanoStart > 0) {
- final String subPattern = pattern.substring(0, nanoStart) + DEFAULT_SECOND_FRACTION_PATTERN;
+ final String subPattern = pattern.substring(0, nanoStart) + DEFAULT_SECOND_FRACTION_PATTERN
+ + pattern.substring(nanoEnd, pattern.length());
for (final FixedFormat type : FixedFormat.values()) {
if (type.getPattern().equals(subPattern)) {
return type;
@@ -183,16 +220,33 @@
return null;
}
- private static int nanoStart(final String pattern) {
- final int index = pattern.indexOf(SECOND_FRACTION_PATTERN);
- if (index >= 0) {
- for (int i = index + 1; i < pattern.length(); i++) {
- if (pattern.charAt(i) != SECOND_FRACTION_PATTERN) {
- return -1;
- }
- }
- }
- return index;
+ private final static int[] EMPTY_RANGE = { -1, -1 };
+
+ /**
+ * @return int[0] start index inclusive; int[1] end index exclusive
+ */
+ private static int[] nanoRange(final String pattern) {
+ final int indexStart = pattern.indexOf(SECOND_FRACTION_PATTERN);
+ int indexEnd = -1;
+ if (indexStart >= 0) {
+ indexEnd = pattern.indexOf('Z', indexStart);
+ indexEnd = indexEnd < 0 ? pattern.indexOf('X', indexStart) : indexEnd;
+ indexEnd = indexEnd < 0 ? pattern.length() : indexEnd;
+ for (int i = indexStart + 1; i < indexEnd; i++) {
+ if (pattern.charAt(i) != SECOND_FRACTION_PATTERN) {
+ return EMPTY_RANGE;
+ }
+ }
+ }
+ return new int [] {indexStart, indexEnd};
+ }
+
+ /**
+ * Returns the optional time zone format.
+ * @return the optional time zone format, may be null.
+ */
+ public FixedTimeZoneFormat getTimeZoneFormat() {
+ return fixedTimeZoneFormat;
}
/**
@@ -243,6 +297,89 @@
}
}
+ private static final char NONE = (char) 0;
+
+ /**
+ * Fixed time zone formats. The enum names are symbols from Java's <a href=
+ * "https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html">DateTimeFormatter</a>.
+ *
+ * @see <a href=
+ * "https://docs.oracle.com/javase/8/docs/api/java/time/format/DateTimeFormatter.html">DateTimeFormatter</a>
+ */
+ public enum FixedTimeZoneFormat {
+
+ /**
+ * Offset like {@code -07}
+ */
+ HH(NONE, false, 3),
+
+ /**
+ * Offset like {@code -0700}.
+ * Same as Z.
+ */
+ HHMM(NONE, true, 5),
+
+ /**
+ * Offset like {@code -07:00}
+ */
+ HHCMM(':', true, 6);
+
+ private FixedTimeZoneFormat() {
+ this(NONE, true, 4);
+ }
+
+ private FixedTimeZoneFormat(final char timeSeparatorChar, final boolean minutes, final int length) {
+ this.timeSeparatorChar = timeSeparatorChar;
+ this.timeSeparatorCharLen = timeSeparatorChar != NONE ? 1 : 0;
+ this.useMinutes = minutes;
+ this.length = length;
+ }
+
+ private final char timeSeparatorChar;
+ private final int timeSeparatorCharLen;
+ private final boolean useMinutes;
+ // The length includes 1 for the leading sign
+ private final int length;
+
+ public int getLength() {
+ return length;
+ }
+
+ // Profiling showed this method is important to log4j performance. Modify with care!
+ // 262 bytes (will be inlined when hot enough: <= -XX:FreqInlineSize=325 bytes on Linux)
+ private int write(final int offset, final char[] buffer, int pos) {
+ // This method duplicates part of writeTime()
+
+ buffer[pos++] = offset < 0 ? '-' : '+';
+ final int absOffset = Math.abs(offset);
+ final int hours = absOffset / 3600000;
+ int ms = absOffset - (3600000 * hours);
+
+ // Hour
+ int temp = hours / 10;
+ buffer[pos++] = ((char) (temp + '0'));
+
+ // Do subtract to get remainder instead of doing % 10
+ buffer[pos++] = ((char) (hours - 10 * temp + '0'));
+
+ // Minute
+ if (useMinutes) {
+ buffer[pos] = timeSeparatorChar;
+ pos += timeSeparatorCharLen;
+ final int minutes = ms / 60000;
+ ms -= 60000 * minutes;
+
+ temp = minutes / 10;
+ buffer[pos++] = ((char) (temp + '0'));
+
+ // Do subtract to get remainder instead of doing % 10
+ buffer[pos++] = ((char) (minutes - 10 * temp + '0'));
+ }
+ return pos;
+ }
+
+ }
+
private final FixedFormat fixedFormat;
private final TimeZone timeZone;
private final int length;
@@ -252,9 +389,11 @@
private final char millisSeparatorChar;
private final int timeSeparatorLength;
private final int millisSeparatorLength;
+ private final FixedTimeZoneFormat fixedTimeZoneFormat;
- private volatile long midnightToday = 0;
- private volatile long midnightTomorrow = 0;
+
+ private volatile long midnightToday;
+ private volatile long midnightTomorrow;
private final int[] dstOffsets = new int[25];
// cachedDate does not need to be volatile because
@@ -270,6 +409,7 @@
* Constructs a FixedDateFormat for the specified fixed format.
* <p>
* Package protected for unit tests.
+ * </p>
*
* @param fixedFormat the fixed format
* @param tz time zone
@@ -282,6 +422,7 @@
* Constructs a FixedDateFormat for the specified fixed format.
* <p>
* Package protected for unit tests.
+ * </p>
*
* @param fixedFormat the fixed format
* @param tz time zone
@@ -294,6 +435,7 @@
this.timeSeparatorLength = fixedFormat.timeSeparatorLength;
this.millisSeparatorChar = fixedFormat.millisSeparatorChar;
this.millisSeparatorLength = fixedFormat.millisSeparatorLength;
+ this.fixedTimeZoneFormat = fixedFormat.fixedTimeZoneFormat;
this.length = fixedFormat.getLength();
this.secondFractionDigits = Math.max(1, Math.min(9, secondFractionDigits));
this.fastDateFormat = fixedFormat.getFastDateFormat(tz);
@@ -318,12 +460,16 @@
tz = TimeZone.getDefault();
}
- final FixedFormat withNanos = FixedFormat.lookupIgnoringNanos(options[0]);
+ final String option0 = options[0];
+ final FixedFormat withNanos = FixedFormat.lookupIgnoringNanos(option0);
if (withNanos != null) {
- final int secondFractionDigits = options[0].length() - FixedFormat.nanoStart(options[0]);
+ final int[] nanoRange = FixedFormat.nanoRange(option0);
+ final int nanoStart = nanoRange[0];
+ final int nanoEnd = nanoRange[1];
+ final int secondFractionDigits = nanoEnd - nanoStart;
return new FixedDateFormat(withNanos, tz, secondFractionDigits);
}
- final FixedFormat type = FixedFormat.lookup(options[0]);
+ final FixedFormat type = FixedFormat.lookup(option0);
return type == null ? null : new FixedDateFormat(type, tz);
}
@@ -440,20 +586,17 @@
}
public int formatInstant(final Instant instant, final char[] buffer, final int startPos) {
- int result = format(instant.getEpochMillisecond(), buffer, startPos);
+ final long epochMillisecond = instant.getEpochMillisecond();
+ int result = format(epochMillisecond, buffer, startPos);
result -= digitsLessThanThree();
- formatNanoOfMillisecond(instant.getNanoOfMillisecond(), buffer, startPos + result);
- return result + digitsMorePreciseThanMillis();
+ final int pos = formatNanoOfMillisecond(instant.getNanoOfMillisecond(), buffer, startPos + result);
+ return writeTimeZone(epochMillisecond, buffer, pos);
}
private int digitsLessThanThree() { // in case user specified only 1 or 2 'n' format characters
return Math.max(0, FixedFormat.MILLI_FRACTION_DIGITS - secondFractionDigits);
}
- private int digitsMorePreciseThanMillis() {
- return Math.max(0, secondFractionDigits - FixedFormat.MILLI_FRACTION_DIGITS);
- }
-
// Profiling showed this method is important to log4j performance. Modify with care!
// 28 bytes (allows immediate JVM inlining: <= -XX:MaxInlineSize=35 bytes)
public String format(final long epochMillis) {
@@ -472,7 +615,8 @@
// int ms = (int) (time % 86400000);
final int ms = (int) (millisSinceMidnight(epochMillis));
writeDate(buffer, startPos);
- return writeTime(ms, buffer, startPos + dateLength) - startPos;
+ final int pos = writeTime(ms, buffer, startPos + dateLength);
+ return pos - startPos;
}
// Profiling showed this method is important to log4j performance. Modify with care!
@@ -534,6 +678,13 @@
return pos;
}
+ private int writeTimeZone(final long epochMillis, final char[] buffer, int pos) {
+ if (fixedTimeZoneFormat != null) {
+ pos = fixedTimeZoneFormat.write(timeZone.getOffset(epochMillis), buffer, pos);
+ }
+ return pos;
+ }
+
static int[] TABLE = {
100000, // 0
10000, // 1
@@ -543,15 +694,16 @@
1, // 5
};
- private void formatNanoOfMillisecond(int nanoOfMillisecond, final char[] buffer, int pos) {
+ private int formatNanoOfMillisecond(final int nanoOfMillisecond, final char[] buffer, int pos) {
int temp;
int remain = nanoOfMillisecond;
for (int i = 0; i < secondFractionDigits - FixedFormat.MILLI_FRACTION_DIGITS; i++) {
- int divisor = TABLE[i];
+ final int divisor = TABLE[i];
temp = remain / divisor;
buffer[pos++] = ((char) (temp + '0'));
remain -= divisor * temp; // equivalent of remain % 10
}
+ return pos;
}
private int daylightSavingTime(final int hourOfDay) {
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/tools/picocli/CommandLine.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/tools/picocli/CommandLine.java
index a4cc8d2..7c9daab 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/tools/picocli/CommandLine.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/tools/picocli/CommandLine.java
@@ -139,11 +139,11 @@
private String commandName = Help.DEFAULT_COMMAND_NAME;
private boolean overwrittenOptionsAllowed = false;
private boolean unmatchedArgumentsAllowed = false;
- private List<String> unmatchedArguments = new ArrayList<String>();
+ private final List<String> unmatchedArguments = new ArrayList<String>();
private CommandLine parent;
private boolean usageHelpRequested;
private boolean versionHelpRequested;
- private List<String> versionLines = new ArrayList<String>();
+ private final List<String> versionLines = new ArrayList<String>();
/**
* Constructs a new {@code CommandLine} interpreter with the specified annotated object.
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/AbstractWatcher.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/AbstractWatcher.java
new file mode 100644
index 0000000..bdfd316
--- /dev/null
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/AbstractWatcher.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.util;
+
+import java.util.List;
+
+import org.apache.logging.log4j.core.config.Configuration;
+import org.apache.logging.log4j.core.config.ConfigurationListener;
+import org.apache.logging.log4j.core.config.Reconfigurable;
+
+/**
+ * Watcher for configuration files. Causes a reconfiguration when a file changes.
+ */
+public abstract class AbstractWatcher implements Watcher {
+
+ private final Reconfigurable reconfigurable;
+ private final List<ConfigurationListener> configurationListeners;
+ private final Log4jThreadFactory threadFactory;
+ private final Configuration configuration;
+ private Source source;
+
+ public AbstractWatcher(final Configuration configuration, final Reconfigurable reconfigurable,
+ final List<ConfigurationListener> configurationListeners) {
+ this.configuration = configuration;
+ this.reconfigurable = reconfigurable;
+ this.configurationListeners = configurationListeners;
+ this.threadFactory = configurationListeners != null ?
+ Log4jThreadFactory.createDaemonThreadFactory("ConfigurationFileWatcher") : null;
+ }
+
+ @Override
+ public List<ConfigurationListener> getListeners() {
+ return configurationListeners;
+ }
+
+ @Override
+ public void modified() {
+ for (final ConfigurationListener configurationListener : configurationListeners) {
+ final Thread thread = threadFactory.newThread(new ReconfigurationRunnable(configurationListener, reconfigurable));
+ thread.start();
+ }
+ }
+
+ public Configuration getConfiguration() {
+ return configuration;
+ }
+
+ public abstract long getLastModified();
+
+ public abstract boolean isModified();
+
+ @Override
+ public void watching(Source source) {
+ this.source = source;
+ }
+
+ @Override
+ public Source getSource() {
+ return source;
+ }
+
+ /**
+ * Helper class for triggering a reconfiguration in a background thread.
+ */
+ public 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/util/ArrayUtils.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/ArrayUtils.java
index bcf8d54..c71a166 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/ArrayUtils.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/ArrayUtils.java
@@ -52,6 +52,17 @@
}
/**
+ * Checks if an array of Objects is empty or {@code null}.
+ *
+ * @param array the array to test
+ * @return {@code true} if the array is empty or {@code null}
+ * @since 2.1
+ */
+ public static boolean isEmpty(final byte[] array) {
+ return getLength(array) == 0;
+ }
+
+ /**
* <p>Removes the element at the specified position from the specified array.
* All subsequent elements are shifted to the left (subtracts one from
* their indices).</p>
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/Assert.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/Assert.java
deleted file mode 100644
index da7f45a..0000000
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/Assert.java
+++ /dev/null
@@ -1,107 +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.util.Collection;
-import java.util.Map;
-
-/**
- * Utility class providing common validation logic.
- */
-public final class Assert {
- private Assert() {
- }
-
- /**
- * Checks if an object has empty semantics. The following scenarios are considered empty:
- * <ul>
- * <li>{@code null}</li>
- * <li>empty {@link CharSequence}</li>
- * <li>empty array</li>
- * <li>empty {@link Iterable}</li>
- * <li>empty {@link Map}</li>
- * </ul>
- *
- * @param o value to check for emptiness
- * @return true if the value is empty, false otherwise
- * @since 2.8
- */
- public static boolean isEmpty(final Object o) {
- if (o == null) {
- return true;
- }
- if (o instanceof CharSequence) {
- return ((CharSequence) o).length() == 0;
- }
- if (o.getClass().isArray()) {
- return ((Object[]) o).length == 0;
- }
- if (o instanceof Collection) {
- return ((Collection<?>) o).isEmpty();
- }
- if (o instanceof Map) {
- return ((Map<?, ?>) o).isEmpty();
- }
- return false;
- }
-
- /**
- * Opposite of {@link #isEmpty(Object)}.
- *
- * @param o value to check for non-emptiness
- * @return true if the value is non-empty, false otherwise
- * @since 2.8
- */
- public static boolean isNonEmpty(final Object o) {
- return !isEmpty(o);
- }
-
- /**
- * Checks a value for emptiness and throws an IllegalArgumentException if it's empty.
- *
- * @param value value to check for emptiness
- * @param <T> type of value
- * @return the provided value if non-empty
- * @since 2.8
- */
- public static <T> T requireNonEmpty(final T value) {
- return requireNonEmpty(value, "");
- }
-
- /**
- * Checks a value for emptiness and throws an IllegalArgumentException if it's empty.
- *
- * @param value value to check for emptiness
- * @param message message to provide in exception
- * @param <T> type of value
- * @return the provided value if non-empty
- * @since 2.8
- */
- public static <T> T requireNonEmpty(final T value, final String message) {
- if (isEmpty(value)) {
- throw new IllegalArgumentException(message);
- }
- return value;
- }
-
- public static int valueIsAtLeast(final int value, final int minValue) {
- if (value < minValue) {
- throw new IllegalArgumentException("Value should be at least " + minValue + " but was " + value);
- }
- return value;
- }
-}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/AuthorizationProvider.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/AuthorizationProvider.java
new file mode 100644
index 0000000..27be12a
--- /dev/null
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/AuthorizationProvider.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;
+
+import java.net.URLConnection;
+
+/**
+ * Interface to be implemented to add an Authorization header to an HTTP request.
+ */
+public interface AuthorizationProvider {
+
+ void addAuthorization(URLConnection urlConnection);
+}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/BasicAuthorizationProvider.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/BasicAuthorizationProvider.java
new file mode 100644
index 0000000..d751260
--- /dev/null
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/BasicAuthorizationProvider.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.util;
+
+import java.net.URLConnection;
+import java.util.Base64;
+
+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.PropertiesUtil;
+
+/**
+ * Provides the Basic Authorization header to a request.
+ */
+public class BasicAuthorizationProvider implements AuthorizationProvider {
+ private static final String[] PREFIXES = {"log4j2.config.", "logging.auth."};
+ private static final String AUTH_USER_NAME = "username";
+ private static final String AUTH_PASSWORD = "password";
+ private static final String AUTH_PASSWORD_DECRYPTOR = "passwordDecryptor";
+ public static final String CONFIG_USER_NAME = "log4j2.configurationUserName";
+ public static final String CONFIG_PASSWORD = "log4j2.configurationPassword";
+ public static final String PASSWORD_DECRYPTOR = "log4j2.passwordDecryptor";
+
+ private static final Logger LOGGER = StatusLogger.getLogger();
+ private static final Base64.Encoder encoder = Base64.getEncoder();
+
+ private String authString = null;
+
+ public BasicAuthorizationProvider(PropertiesUtil props) {
+ String userName = props.getStringProperty(PREFIXES,AUTH_USER_NAME,
+ () -> props.getStringProperty(CONFIG_USER_NAME));
+ String password = props.getStringProperty(PREFIXES, AUTH_PASSWORD,
+ () -> props.getStringProperty(CONFIG_PASSWORD));
+ String decryptor = props.getStringProperty(PREFIXES, AUTH_PASSWORD_DECRYPTOR,
+ () -> props.getStringProperty(PASSWORD_DECRYPTOR));
+ if (decryptor != null) {
+ try {
+ Object obj = LoaderUtil.newInstanceOf(decryptor);
+ if (obj instanceof PasswordDecryptor) {
+ password = ((PasswordDecryptor) obj).decryptPassword(password);
+ }
+ } catch (Exception ex) {
+ LOGGER.warn("Unable to decrypt password.", ex);
+ }
+ }
+ if (userName != null && password != null) {
+ authString = "Basic " + encoder.encodeToString((userName + ":" + password).getBytes());
+ }
+ }
+
+ @Override
+ public void addAuthorization(URLConnection urlConnection) {
+ if (authString != null) {
+ urlConnection.setRequestProperty("Authorization", authString);
+ }
+ }
+}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/Builder.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/Builder.java
index 0935ce8..9acc9d5 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/Builder.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/Builder.java
@@ -30,6 +30,7 @@
* </p>
*
* @param <T> This builder creates instances of this class.
+ * @deprecated Present only for compatibility with Log4j 2 2.x plugins. Use Builder from log4j-plugins.
*/
public interface Builder<T> {
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/Clock.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/Clock.java
deleted file mode 100644
index 15d6f65..0000000
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/Clock.java
+++ /dev/null
@@ -1,25 +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;
-
-/**
- * @deprecated Use {@link org.apache.logging.log4j.core.time.Clock} instead
- * @see org.apache.logging.log4j.core.time.Clock
- */
-@Deprecated
-public interface Clock extends org.apache.logging.log4j.core.time.Clock {
-}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/ClockFactory.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/ClockFactory.java
deleted file mode 100644
index 9ef4dbd..0000000
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/ClockFactory.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.core.util;
-
-import org.apache.logging.log4j.core.time.Clock;
-
-/**
- * @deprecated Use {@link org.apache.logging.log4j.core.time.ClockFactory} instead.
- */
-@Deprecated
-public final class ClockFactory {
-
- /** @see org.apache.logging.log4j.core.time.ClockFactory#PROPERTY_NAME */
- @Deprecated
- public static final String PROPERTY_NAME = org.apache.logging.log4j.core.time.ClockFactory.PROPERTY_NAME;
-
- private ClockFactory() {}
-
- /** @see org.apache.logging.log4j.core.time.ClockFactory#getClock() */
- @Deprecated
- public static Clock getClock() {
- return org.apache.logging.log4j.core.time.ClockFactory.getClock();
- }
-
-}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/CloseShieldOutputStream.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/CloseShieldOutputStream.java
index f04b4d3..1139bd2 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/CloseShieldOutputStream.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/CloseShieldOutputStream.java
@@ -57,4 +57,4 @@
public void write(final int b) throws IOException {
delegate.write(b);
}
-}
\ No newline at end of file
+}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/Closer.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/Closer.java
index 26a9226..9c0c46d 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/Closer.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/Closer.java
@@ -17,25 +17,33 @@
package org.apache.logging.log4j.core.util;
+import org.apache.logging.log4j.status.StatusLogger;
+
/**
- * Helper class for closing resources.
+ * Closes resources.
*/
public final class Closer {
private Closer() {
+ // empty
}
/**
* Closes an AutoCloseable or ignores if {@code null}.
*
* @param closeable the resource to close; may be null
+ * @return Whether the resource was closed.
* @throws Exception if the resource cannot be closed
* @since 2.8
+ * @since 2.11.2 returns a boolean instead of being a void return type.
*/
- public static void close(final AutoCloseable closeable) throws Exception {
+ public static boolean close(final AutoCloseable closeable) throws Exception {
if (closeable != null) {
+ StatusLogger.getLogger().debug("Closing {} {}", closeable.getClass().getSimpleName(), closeable);
closeable.close();
+ return true;
}
+ return false;
}
/**
@@ -46,8 +54,7 @@
*/
public static boolean closeSilently(final AutoCloseable closeable) {
try {
- close(closeable);
- return true;
+ return close(closeable);
} catch (final Exception ignored) {
return false;
}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/ContextDataProvider.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/ContextDataProvider.java
new file mode 100644
index 0000000..d302cf8
--- /dev/null
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/ContextDataProvider.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.util;
+
+import org.apache.logging.log4j.core.impl.JdkMapAdapterStringMap;
+import org.apache.logging.log4j.util.StringMap;
+
+import java.util.Map;
+
+/**
+ * Source of context data to be added to each log event.
+ */
+public interface ContextDataProvider {
+
+ /**
+ * Returns a Map containing context data to be injected into the event or null if no context data is to be added.
+ * @return A Map containing the context data or null.
+ */
+ Map<String, String> supplyContextData();
+
+ /**
+ * Returns the context data as a StringMap.
+ * @return the context data in a StringMap.
+ */
+ default StringMap supplyStringMap() {
+ return new JdkMapAdapterStringMap(supplyContextData());
+ }
+}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/CronExpression.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/CronExpression.java
index 3e8930a..da580ff 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/CronExpression.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/CronExpression.java
@@ -1471,7 +1471,7 @@
cl.set(Calendar.MONTH, mon);
// no '- 1' here because we are promoting the month
continue;
- } else if (daysToAdd > 0) { // are we swithing days?
+ } else if (daysToAdd > 0) { // are we switching days?
cl.set(Calendar.SECOND, 0);
cl.set(Calendar.MINUTE, 0);
cl.set(Calendar.HOUR_OF_DAY, 0);
@@ -1572,8 +1572,13 @@
protected Date getTimeBefore(final Date targetDate) {
final Calendar cl = Calendar.getInstance(getTimeZone());
+ // CronTrigger does not deal with milliseconds, so truncate target
+ cl.setTime(targetDate);
+ cl.set(Calendar.MILLISECOND, 0);
+ final Date targetDateNoMs = cl.getTime();
+
// to match this
- Date start = targetDate;
+ Date start = targetDateNoMs;
final long minIncrement = findMinIncrement();
Date prevFireTime;
do {
@@ -1583,7 +1588,7 @@
return null;
}
start = prevCheckDate;
- } while (prevFireTime.compareTo(targetDate) >= 0);
+ } while (prevFireTime.compareTo(targetDateNoMs) >= 0);
return prevFireTime;
}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/DefaultShutdownCallbackRegistry.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/DefaultShutdownCallbackRegistry.java
index 3c4f6e6..9301575 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/DefaultShutdownCallbackRegistry.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/DefaultShutdownCallbackRegistry.java
@@ -29,7 +29,8 @@
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.core.AbstractLifeCycle;
-import org.apache.logging.log4j.core.LifeCycle2;
+import org.apache.logging.log4j.core.LifeCycle;
+import org.apache.logging.log4j.core.LifeCycle.State;
import org.apache.logging.log4j.status.StatusLogger;
/**
@@ -38,7 +39,7 @@
*
* @since 2.1
*/
-public class DefaultShutdownCallbackRegistry implements ShutdownCallbackRegistry, LifeCycle2, Runnable {
+public class DefaultShutdownCallbackRegistry implements ShutdownCallbackRegistry, LifeCycle, Runnable {
/** Status logger. */
protected static final Logger LOGGER = StatusLogger.getLogger();
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/FileUtils.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/FileUtils.java
index 609a005..a50a20d 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/FileUtils.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/FileUtils.java
@@ -18,12 +18,9 @@
import java.io.File;
import java.io.IOException;
-import java.io.UnsupportedEncodingException;
-import java.net.MalformedURLException;
import java.net.URI;
+import java.net.URISyntaxException;
import java.net.URL;
-import java.net.URLDecoder;
-import java.nio.charset.StandardCharsets;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -60,41 +57,35 @@
* @return the resulting file object
*/
public static File fileFromUri(URI uri) {
- // There MUST be a better way to do this. TODO Search other ASL projects...
- if (uri == null || (uri.getScheme() != null
- && (!PROTOCOL_FILE.equals(uri.getScheme()) && !JBOSS_FILE.equals(uri.getScheme())))) {
+ if (uri == null) {
return null;
}
- if (uri.getScheme() == null) {
- File file = new File(uri.toString());
- if (file.exists()) {
- return file;
+ if (uri.isAbsolute()) {
+ if (JBOSS_FILE.equals(uri.getScheme())) try {
+ // patch the scheme
+ uri = new URI(PROTOCOL_FILE, uri.getSchemeSpecificPart(), uri.getFragment());
+ } catch (URISyntaxException use) {
+ // should not happen, ignore
}
try {
- final String path = uri.getPath();
- file = new File(path);
+ if (PROTOCOL_FILE.equals(uri.getScheme())) {
+ return new File(uri);
+ }
+ } catch (final Exception ex) {
+ LOGGER.warn("Invalid URI {}", uri);
+ }
+ } else {
+ File file = new File(uri.toString());
+ try {
if (file.exists()) {
return file;
}
- uri = new File(path).toURI();
+ final String path = uri.getPath();
+ return new File(path);
} catch (final Exception ex) {
LOGGER.warn("Invalid URI {}", uri);
- return null;
}
}
- final String charsetName = StandardCharsets.UTF_8.name();
- try {
- String fileName = uri.toURL().getFile();
- if (new File(fileName).exists()) { // LOG4J2-466
- return new File(fileName); // allow files with '+' char in name
- }
- fileName = URLDecoder.decode(fileName, charsetName);
- return new File(fileName);
- } catch (final MalformedURLException ex) {
- LOGGER.warn("Invalid URL {}", uri, ex);
- } catch (final UnsupportedEncodingException uee) {
- LOGGER.warn("Invalid encoding: {}", charsetName, uee);
- }
return null;
}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/KeyValuePair.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/KeyValuePair.java
index 6eae7de..83d0078 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/KeyValuePair.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/KeyValuePair.java
@@ -17,10 +17,10 @@
package org.apache.logging.log4j.core.util;
-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.plugins.Node;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginBuilderAttribute;
+import org.apache.logging.log4j.plugins.PluginFactory;
/**
* Key/Value pair configuration item.
@@ -64,12 +64,12 @@
return key + '=' + value;
}
- @PluginBuilderFactory
+ @PluginFactory
public static Builder newBuilder() {
return new Builder();
}
- public static class Builder implements org.apache.logging.log4j.core.util.Builder<KeyValuePair> {
+ public static class Builder implements org.apache.logging.log4j.plugins.util.Builder<KeyValuePair> {
@PluginBuilderAttribute
private String key;
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/Loader.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/Loader.java
index d1013f7..d0ee168 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/Loader.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/Loader.java
@@ -23,6 +23,7 @@
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.PropertiesUtil;
/**
* Load resources (or images) from various sources.
@@ -33,6 +34,9 @@
private static final String TSTR = "Caught Exception while in Loader.getResource. This may be innocuous.";
+ final static Boolean ignoreTccl =
+ Boolean.valueOf(PropertiesUtil.getProperties().getStringProperty(LoaderUtil.IGNORE_TCCL_PROPERTY, null));
+
private Loader() {
}
@@ -41,7 +45,12 @@
* @return the ClassLoader.
*/
public static ClassLoader getClassLoader() {
- return getClassLoader(Loader.class, null);
+ return Loader.getClassLoader(Loader.class, null);
+ }
+
+ // TODO: this method could use some explanation
+ public static ClassLoader getClassLoader(final Class<?> class1, final Class<?> class2) {
+ return LoaderUtil.getClassLoader(class1, class2);
}
/**
@@ -55,18 +64,6 @@
return LoaderUtil.getThreadContextClassLoader();
}
- // TODO: this method could use some explanation
- public static ClassLoader getClassLoader(final Class<?> class1, final Class<?> class2) {
- final ClassLoader threadContextClassLoader = getThreadContextClassLoader();
- final ClassLoader loader1 = class1 == null ? null : class1.getClassLoader();
- final ClassLoader loader2 = class2 == null ? null : class2.getClassLoader();
-
- if (isChild(threadContextClassLoader, loader1)) {
- return isChild(threadContextClassLoader, loader2) ? threadContextClassLoader : loader2;
- }
- return isChild(loader1, loader2) ? loader1 : loader2;
- }
-
/**
* This method will search for {@code resource} in different
* places. The search order is as follows:
@@ -195,26 +192,6 @@
}
/**
- * Determines if one ClassLoader is a child of another ClassLoader. Note that a {@code null} ClassLoader is
- * interpreted as the system ClassLoader as per convention.
- *
- * @param loader1 the ClassLoader to check for childhood.
- * @param loader2 the ClassLoader to check for parenthood.
- * @return {@code true} if the first ClassLoader is a strict descendant of the second ClassLoader.
- */
- private static boolean isChild(final ClassLoader loader1, final ClassLoader loader2) {
- if (loader1 != null && loader2 != null) {
- ClassLoader parent = loader1.getParent();
- while (parent != null && parent != loader2) {
- parent = parent.getParent();
- }
- // once parent is null, we're at the system CL, which would indicate they have separate ancestry
- return parent != null;
- }
- return loader1 != null;
- }
-
- /**
* Loads and initializes a named Class using a given ClassLoader.
*
* @param className The class name.
@@ -238,8 +215,8 @@
public static Class<?> loadClass(final String className, final ClassLoader loader)
throws ClassNotFoundException {
return loader != null ? loader.loadClass(className) : null;
- }
-
+ }
+
/**
* Load a Class in the {@code java.*} namespace by name. Useful for peculiar scenarios typically involving
* Google App Engine.
@@ -265,16 +242,13 @@
* @throws ClassNotFoundException if the class isn't available to the usual ClassLoaders
* @throws IllegalAccessException if the class can't be instantiated through a public constructor
* @throws InstantiationException if there was an exception whilst instantiating the class
- * @throws NoSuchMethodException if there isn't a no-args constructor on the class
* @throws InvocationTargetException if there was an exception whilst constructing the class
+ * @since 2.1
*/
- public static Object newInstanceOf(final String className)
- throws ClassNotFoundException,
- IllegalAccessException,
- InstantiationException,
- NoSuchMethodException,
- InvocationTargetException {
- return LoaderUtil.newInstanceOf(className);
+ @SuppressWarnings("unchecked")
+ public static <T> T newInstanceOf(final String className)
+ throws ClassNotFoundException, IllegalAccessException, InstantiationException, InvocationTargetException {
+ return newInstanceOf((Class<T>) loadClass(className));
}
/**
@@ -287,17 +261,59 @@
* @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}
*/
public static <T> T newCheckedInstanceOf(final String className, final Class<T> clazz)
- throws ClassNotFoundException,
- NoSuchMethodException,
- IllegalAccessException,
- InvocationTargetException,
- InstantiationException {
- return LoaderUtil.newCheckedInstanceOf(className, clazz);
+ throws ClassNotFoundException, IllegalAccessException, InvocationTargetException, InstantiationException {
+ return newInstanceOf(loadClass(className).asSubclass(clazz));
+ }
+
+ /**
+ * Loads and instantiates a class given by a property name.
+ *
+ * @param propertyName The property name to look up a class name for.
+ * @param clazz The class to cast it to.
+ * @param <T> 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}
+ */
+ public static <T> T newCheckedInstanceOfProperty(final String propertyName, final Class<T> clazz)
+ throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException,
+ IllegalAccessException {
+ final String className = PropertiesUtil.getProperties().getStringProperty(propertyName);
+ ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
+ try {
+ Thread.currentThread().setContextClassLoader(getClassLoader());
+ return LoaderUtil.newCheckedInstanceOfProperty(propertyName, clazz);
+ } finally {
+ Thread.currentThread().setContextClassLoader(contextClassLoader);
+ }
+ }
+
+ /**
+ * Loads and instantiates a Class using the default constructor.
+ *
+ * @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
+ * @since 2.7
+ */
+ public static <T> T newInstanceOf(final Class<T> clazz)
+ throws InstantiationException, IllegalAccessException, InvocationTargetException {
+ 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();
+ }
}
/**
@@ -307,11 +323,35 @@
* @return {@code true} if the class could be found or {@code false} otherwise.
*/
public static boolean isClassAvailable(final String className) {
- return LoaderUtil.isClassAvailable(className);
+ ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
+ try {
+ Thread.currentThread().setContextClassLoader(getClassLoader());
+ return LoaderUtil.isClassAvailable(className);
+ } finally {
+ Thread.currentThread().setContextClassLoader(contextClassLoader);
+ }
}
public static boolean isJansiAvailable() {
return isClassAvailable("org.fusesource.jansi.AnsiRenderer");
}
+ /**
+ * 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
+ */
+ public static Class<?> loadClass(final String className) throws ClassNotFoundException {
+ if (ignoreTccl) {
+ return Class.forName(className);
+ }
+ try {
+ return getClassLoader().loadClass(className);
+ } catch (final Throwable ignored) {
+ return Class.forName(className);
+ }
+ }
}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/NameUtil.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/NameUtil.java
deleted file mode 100644
index 4d4cfcc..0000000
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/NameUtil.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache license, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the license for the specific language governing permissions and
- * limitations under the license.
- */
-package org.apache.logging.log4j.core.util;
-
-import java.security.MessageDigest;
-
-import org.apache.logging.log4j.util.Strings;
-
-/**
- *
- */
-public final class NameUtil {
-
- private static final int MASK = 0xff;
-
- private NameUtil() {
- }
-
- public static String getSubName(final String name) {
- if (Strings.isEmpty(name)) {
- return null;
- }
- final int i = name.lastIndexOf('.');
- return i > 0 ? name.substring(0, i) : Strings.EMPTY;
- }
-
- public static String md5(final String string) {
- try {
- final MessageDigest digest = MessageDigest.getInstance("MD5");
- digest.update(string.getBytes());
- final byte[] bytes = digest.digest();
- final StringBuilder md5 = new StringBuilder();
- for (final byte b : bytes) {
- final String hex = Integer.toHexString(MASK & b);
- if (hex.length() == 1) {
- md5.append('0');
- }
- md5.append(hex);
- }
- return md5.toString();
- } catch (final Exception ex) {
- return string;
- }
- }
-}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/NanoClock.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/NanoClock.java
deleted file mode 100644
index a0afda6..0000000
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/NanoClock.java
+++ /dev/null
@@ -1,26 +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;
-
-/**
- * @deprecated Use {@link org.apache.logging.log4j.core.time.NanoClock} instead
- * @see org.apache.logging.log4j.core.time.NanoClock
- */
-@Deprecated
-public interface NanoClock extends org.apache.logging.log4j.core.time.NanoClock {
-}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/NetUtils.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/NetUtils.java
index e72beb6..195dbff 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/NetUtils.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/NetUtils.java
@@ -25,6 +25,7 @@
import java.net.URISyntaxException;
import java.net.URL;
import java.net.UnknownHostException;
+import java.util.Arrays;
import java.util.Enumeration;
import org.apache.logging.log4j.Logger;
@@ -51,19 +52,21 @@
public static String getLocalHostname() {
try {
final InetAddress addr = InetAddress.getLocalHost();
- return addr.getHostName();
+ return addr == null ? UNKNOWN_LOCALHOST : addr.getHostName();
} catch (final UnknownHostException uhe) {
try {
final Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
- while (interfaces.hasMoreElements()) {
- final NetworkInterface nic = interfaces.nextElement();
- final Enumeration<InetAddress> addresses = nic.getInetAddresses();
- while (addresses.hasMoreElements()) {
- final InetAddress address = addresses.nextElement();
- if (!address.isLoopbackAddress()) {
- final String hostname = address.getHostName();
- if (hostname != null) {
- return hostname;
+ if (interfaces != null) {
+ while (interfaces.hasMoreElements()) {
+ final NetworkInterface nic = interfaces.nextElement();
+ final Enumeration<InetAddress> addresses = nic.getInetAddresses();
+ while (addresses.hasMoreElements()) {
+ final InetAddress address = addresses.nextElement();
+ if (!address.isLoopbackAddress()) {
+ final String hostname = address.getHostName();
+ if (hostname != null) {
+ return hostname;
+ }
}
}
}
@@ -78,8 +81,70 @@
}
/**
+ * Returns the local network interface's MAC address if possible. The local network interface is defined here as
+ * the {@link java.net.NetworkInterface} that is both up and not a loopback interface.
+ *
+ * @return the MAC address of the local network interface or {@code null} if no MAC address could be determined.
+ */
+ public static byte[] getMacAddress() {
+ byte[] mac = null;
+ try {
+ final InetAddress localHost = InetAddress.getLocalHost();
+ try {
+ final NetworkInterface localInterface = NetworkInterface.getByInetAddress(localHost);
+ if (isUpAndNotLoopback(localInterface)) {
+ mac = localInterface.getHardwareAddress();
+ }
+ if (mac == null) {
+ final Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();
+ if (networkInterfaces != null) {
+ while (networkInterfaces.hasMoreElements() && mac == null) {
+ final NetworkInterface nic = networkInterfaces.nextElement();
+ if (isUpAndNotLoopback(nic)) {
+ mac = nic.getHardwareAddress();
+ }
+ }
+ }
+ }
+ } catch (final SocketException e) {
+ LOGGER.catching(e);
+ }
+ if (ArrayUtils.isEmpty(mac) && localHost != null) {
+ // Emulate a MAC address with an IP v4 or v6
+ final byte[] address = localHost.getAddress();
+ // Take only 6 bytes if the address is an IPv6 otherwise will pad with two zero bytes
+ mac = Arrays.copyOf(address, 6);
+ }
+ } catch (final UnknownHostException ignored) {
+ // ignored
+ }
+ return mac;
+ }
+
+ /**
+ * Returns the mac address, if it is available, as a string with each byte separated by a ":" character.
+ * @return the mac address String or null.
+ */
+ public static String getMacAddressString() {
+ final byte[] macAddr = getMacAddress();
+ if (!ArrayUtils.isEmpty(macAddr)) {
+ StringBuilder sb = new StringBuilder(String.format("%02x", macAddr[0]));
+ for (int i = 1; i < macAddr.length; ++i) {
+ sb.append(":").append(String.format("%02x", macAddr[i]));
+ }
+ return sb.toString();
+
+ }
+ return null;
+ }
+
+ private static boolean isUpAndNotLoopback(final NetworkInterface ni) throws SocketException {
+ return ni != null && !ni.isLoopback() && ni.isUp();
+ }
+
+ /**
* Converts a URI string or file path to a URI object.
- *
+ *
* @param path the URI string or path
* @return the URI object
*/
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/NullOutputStream.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/NullOutputStream.java
index 7403215..85dd4e4 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/NullOutputStream.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/NullOutputStream.java
@@ -34,12 +34,6 @@
private static final NullOutputStream INSTANCE = new NullOutputStream();
/**
- * @deprecated Deprecated in 2.7: use {@link #getInstance()}.
- */
- @Deprecated
- public static final NullOutputStream NULL_OUTPUT_STREAM = INSTANCE;
-
- /**
* Gets the singleton instance.
*
* @return the singleton instance.
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/OptionConverter.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/OptionConverter.java
index 9970d61..eacd715 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/OptionConverter.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/OptionConverter.java
@@ -16,12 +16,13 @@
*/
package org.apache.logging.log4j.core.util;
+import java.io.InterruptedIOException;
import java.util.Locale;
import java.util.Properties;
+import org.apache.logging.log4j.Level;
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.PropertiesUtil;
import org.apache.logging.log4j.util.Strings;
@@ -155,6 +156,72 @@
return defaultValue;
}
+ public static Level toLevel(String value, Level defaultValue) {
+ if(value == null) {
+ return defaultValue;
+ }
+
+ value = value.trim();
+
+ int hashIndex = value.indexOf('#');
+ if (hashIndex == -1) {
+ if("NULL".equalsIgnoreCase(value)) {
+ return null;
+ } else {
+ // no class name specified : use standard Level class
+ return Level.toLevel(value, defaultValue);
+ }
+ }
+
+ Level result = defaultValue;
+
+ String clazz = value.substring(hashIndex+1);
+ String levelName = value.substring(0, hashIndex);
+
+ // This is degenerate case but you never know.
+ if("NULL".equalsIgnoreCase(levelName)) {
+ return null;
+ }
+
+ LOGGER.debug("toLevel" + ":class=[" + clazz + "]"
+ + ":pri=[" + levelName + "]");
+
+ try {
+ Class customLevel = Loader.loadClass(clazz);
+
+ // get a ref to the specified class' static method
+ // toLevel(String, org.apache.log4j.Level)
+ Class[] paramTypes = new Class[] { String.class, Level.class
+ };
+ java.lang.reflect.Method toLevelMethod =
+ customLevel.getMethod("toLevel", paramTypes);
+
+ // now call the toLevel method, passing level string + default
+ Object[] params = new Object[] {levelName, defaultValue};
+ Object o = toLevelMethod.invoke(null, params);
+
+ result = (Level) o;
+ } catch(ClassNotFoundException e) {
+ LOGGER.warn("custom level class [" + clazz + "] not found.");
+ } catch(NoSuchMethodException e) {
+ LOGGER.warn("custom level class [" + clazz + "]"
+ + " does not have a class function toLevel(String, Level)", e);
+ } catch(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(ClassCastException e) {
+ LOGGER.warn("class [" + clazz + "] is not a subclass of org.apache.log4j.Level", e);
+ } catch(IllegalAccessException e) {
+ LOGGER.warn("class ["+clazz+ "] cannot be instantiated due to access restrictions", e);
+ } catch(RuntimeException e) {
+ LOGGER.warn("class ["+clazz+"], level [" + levelName + "] conversion failed.", e);
+ }
+ return result;
+ }
+
/**
*
* @param value The size of the file as a String.
@@ -226,7 +293,7 @@
final Object defaultValue) {
if (className != null) {
try {
- final Class<?> classObj = LoaderUtil.loadClass(className);
+ final Class<?> classObj = Loader.loadClass(className);
if (!superClass.isAssignableFrom(classObj)) {
LOGGER.error("A \"{}\" object is not assignable to a \"{}\" variable.", className,
superClass.getName());
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/PasswordDecryptor.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/PasswordDecryptor.java
new file mode 100644
index 0000000..a903027
--- /dev/null
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/PasswordDecryptor.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.util;
+
+/**
+ * Decrypt passwords.
+ */
+public interface PasswordDecryptor {
+
+ String decryptPassword(String password);
+}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/ProcessIdUtil.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/ProcessIdUtil.java
new file mode 100644
index 0000000..2579350
--- /dev/null
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/ProcessIdUtil.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.util;
+
+import java.io.File;
+import java.io.IOException;
+import java.lang.reflect.Method;
+
+/**
+ * @Since 2.9
+ */
+public class ProcessIdUtil {
+
+ public static final String DEFAULT_PROCESSID = "-";
+
+ public static String getProcessId() {
+ try {
+ // LOG4J2-2126 use reflection to improve compatibility with Android Platform which does not support JMX extensions
+ Class<?> managementFactoryClass = Class.forName("java.lang.management.ManagementFactory");
+ Method getRuntimeMXBean = managementFactoryClass.getDeclaredMethod("getRuntimeMXBean");
+ Class<?> runtimeMXBeanClass = Class.forName("java.lang.management.RuntimeMXBean");
+ Method getName = runtimeMXBeanClass.getDeclaredMethod("getName");
+
+ Object runtimeMXBean = getRuntimeMXBean.invoke(null);
+ String name = (String) getName.invoke(runtimeMXBean);
+ //String name = ManagementFactory.getRuntimeMXBean().getName(); //JMX not allowed on Android
+ return name.split("@")[0]; // likely works on most platforms
+ } catch (final Exception ex) {
+ try {
+ return new File("/proc/self").getCanonicalFile().getName(); // try a Linux-specific way
+ } catch (final IOException ignoredUseDefault) {
+ // Ignore exception.
+ }
+ }
+ return DEFAULT_PROCESSID;
+ }
+}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/ReflectionUtil.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/ReflectionUtil.java
deleted file mode 100644
index ffee439..0000000
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/ReflectionUtil.java
+++ /dev/null
@@ -1,200 +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.lang.reflect.AccessibleObject;
-import java.lang.reflect.Constructor;
-import java.lang.reflect.Field;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Member;
-import java.lang.reflect.Modifier;
-import java.util.Objects;
-
-/**
- * Utility class for performing common reflective operations.
- *
- * @since 2.1
- */
-public final class ReflectionUtil {
- private ReflectionUtil() {
- }
-
- /**
- * Indicates whether or not a {@link Member} is both public and is contained in a public class.
- *
- * @param <T> type of the object whose accessibility to test
- * @param member the Member to check for public accessibility (must not be {@code null}).
- * @return {@code true} if {@code member} is public and contained in a public class.
- * @throws NullPointerException if {@code member} is {@code null}.
- */
- public static <T extends AccessibleObject & Member> boolean isAccessible(final T member) {
- Objects.requireNonNull(member, "No member provided");
- return Modifier.isPublic(member.getModifiers()) && Modifier.isPublic(member.getDeclaringClass().getModifiers());
- }
-
- /**
- * Makes a {@link Member} {@link AccessibleObject#isAccessible() accessible} if the member is not public.
- *
- * @param <T> type of the object to make accessible
- * @param member the Member to make accessible (must not be {@code null}).
- * @throws NullPointerException if {@code member} is {@code null}.
- */
- public static <T extends AccessibleObject & Member> void makeAccessible(final T member) {
- if (!isAccessible(member) && !member.isAccessible()) {
- member.setAccessible(true);
- }
- }
-
- /**
- * Makes a {@link Field} {@link AccessibleObject#isAccessible() accessible} if it is not public or if it is final.
- *
- * <p>Note that using this method to make a {@code final} field writable will most likely not work very well due to
- * compiler optimizations and the like.</p>
- *
- * @param field the Field to make accessible (must not be {@code null}).
- * @throws NullPointerException if {@code field} is {@code null}.
- */
- public static void makeAccessible(final Field field) {
- Objects.requireNonNull(field, "No field provided");
- if ((!isAccessible(field) || Modifier.isFinal(field.getModifiers())) && !field.isAccessible()) {
- field.setAccessible(true);
- }
- }
-
- /**
- * Gets the value of a {@link Field}, making it accessible if required.
- *
- * @param field the Field to obtain a value from (must not be {@code null}).
- * @param instance the instance to obtain the field value from or {@code null} only if the field is static.
- * @return the value stored by the field.
- * @throws NullPointerException if {@code field} is {@code null}, or if {@code instance} is {@code null} but
- * {@code field} is not {@code static}.
- * @see Field#get(Object)
- */
- public static Object getFieldValue(final Field field, final Object instance) {
- makeAccessible(field);
- if (!Modifier.isStatic(field.getModifiers())) {
- Objects.requireNonNull(instance, "No instance given for non-static field");
- }
- try {
- return field.get(instance);
- } catch (final IllegalAccessException e) {
- throw new UnsupportedOperationException(e);
- }
- }
-
- /**
- * Gets the value of a static {@link Field}, making it accessible if required.
- *
- * @param field the Field to obtain a value from (must not be {@code null}).
- * @return the value stored by the static field.
- * @throws NullPointerException if {@code field} is {@code null}, or if {@code field} is not {@code static}.
- * @see Field#get(Object)
- */
- public static Object getStaticFieldValue(final Field field) {
- return getFieldValue(field, null);
- }
-
- /**
- * Sets the value of a {@link Field}, making it accessible if required.
- *
- * @param field the Field to write a value to (must not be {@code null}).
- * @param instance the instance to write the value to or {@code null} only if the field is static.
- * @param value the (possibly wrapped) value to write to the field.
- * @throws NullPointerException if {@code field} is {@code null}, or if {@code instance} is {@code null} but
- * {@code field} is not {@code static}.
- * @see Field#set(Object, Object)
- */
- public static void setFieldValue(final Field field, final Object instance, final Object value) {
- makeAccessible(field);
- if (!Modifier.isStatic(field.getModifiers())) {
- Objects.requireNonNull(instance, "No instance given for non-static field");
- }
- try {
- field.set(instance, value);
- } catch (final IllegalAccessException e) {
- throw new UnsupportedOperationException(e);
- }
- }
-
- /**
- * Sets the value of a static {@link Field}, making it accessible if required.
- *
- * @param field the Field to write a value to (must not be {@code null}).
- * @param value the (possibly wrapped) value to write to the field.
- * @throws NullPointerException if {@code field} is {@code null}, or if {@code field} is not {@code static}.
- * @see Field#set(Object, Object)
- */
- public static void setStaticFieldValue(final Field field, final Object value) {
- setFieldValue(field, null, value);
- }
-
- /**
- * Gets the default (no-arg) constructor for a given class.
- *
- * @param clazz the class to find a constructor for
- * @param <T> the type made by the constructor
- * @return the default constructor for the given class
- * @throws IllegalStateException if no default constructor can be found
- */
- public static <T> Constructor<T> getDefaultConstructor(final Class<T> clazz) {
- Objects.requireNonNull(clazz, "No class provided");
- try {
- final Constructor<T> constructor = clazz.getDeclaredConstructor();
- makeAccessible(constructor);
- return constructor;
- } catch (final NoSuchMethodException ignored) {
- try {
- final Constructor<T> constructor = clazz.getConstructor();
- makeAccessible(constructor);
- return constructor;
- } catch (final NoSuchMethodException e) {
- throw new IllegalStateException(e);
- }
- }
- }
-
- /**
- * Constructs a new {@code T} object using the default constructor of its class. Any exceptions thrown by the
- * constructor will be rethrown by this method, possibly wrapped in an
- * {@link java.lang.reflect.UndeclaredThrowableException}.
- *
- * @param clazz the class to use for instantiation.
- * @param <T> the type of the object to construct.
- * @return a new instance of T made from its default constructor.
- * @throws IllegalArgumentException if the given class is abstract, an interface, an array class, a primitive type,
- * or void
- * @throws IllegalStateException if access is denied to the constructor, or there are no default constructors
- */
- public static <T> T instantiate(final Class<T> clazz) {
- Objects.requireNonNull(clazz, "No class provided");
- final Constructor<T> constructor = getDefaultConstructor(clazz);
- try {
- return constructor.newInstance();
- } catch (final LinkageError | InstantiationException e) {
- // LOG4J2-1051
- // On platforms like Google App Engine and Android, some JRE classes are not supported: JMX, JNDI, etc.
- throw new IllegalArgumentException(e);
- } catch (final IllegalAccessException e) {
- throw new IllegalStateException(e);
- } catch (final InvocationTargetException e) {
- Throwables.rethrow(e.getCause());
- throw new InternalError("Unreachable");
- }
- }
-}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/Source.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/Source.java
new file mode 100644
index 0000000..a13fa65
--- /dev/null
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/Source.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.util;
+
+import org.apache.logging.log4j.core.config.ConfigurationSource;
+
+import java.io.File;
+import java.net.URI;
+import java.util.Objects;
+
+/**
+ * Represents the source for the logging configuration.
+ */
+public class Source {
+
+ /**
+ * Captures a URI or File.
+ */
+
+ private final File file;
+ private final URI uri;
+ private final String location;
+
+ /**
+ * Constructs a Source from a ConfigurationSource.
+ * @param source The ConfigurationSource.
+ */
+ public Source(ConfigurationSource source) {
+ this.file = source.getFile();
+ this.uri = source.getURI();
+ this.location = source.getLocation();
+ }
+
+ /**
+ * Constructs a new {@code Source} with the specified file.
+ * file.
+ *
+ * @param file the file where the input stream originated
+ */
+ public Source(final File file) {
+ this.file = Objects.requireNonNull(file, "file is null");
+ this.location = file.getAbsolutePath();
+ this.uri = null;
+ }
+
+ /**
+ * Constructs a new {@code Source} from the specified URI.
+ *
+ * @param uri the URL where the input stream originated
+ */
+ public Source(final URI uri, final long lastModified) {
+ this.uri = Objects.requireNonNull(uri, "URI is null");
+ this.location = uri.toString();
+ this.file = null;
+ }
+
+ /**
+ * Returns the file configuration source, or {@code null} if this configuration source is based on an URL or has
+ * neither a file nor an URL.
+ *
+ * @return the configuration source file, or {@code null}
+ */
+ public File getFile() {
+ return file;
+ }
+
+ /**
+ * Returns the configuration source URL, or {@code null} if this configuration source is based on a file or has
+ * neither a file nor an URL.
+ *
+ * @return the configuration source URL, or {@code null}
+ */
+ public URI getURI() {
+ return uri;
+ }
+
+ /**
+ * Returns a string describing the configuration source file or URL, or {@code null} if this configuration source
+ * has neither a file nor an URL.
+ *
+ * @return a string describing the configuration source file or URL, or {@code null}
+ */
+ public String getLocation() {
+ return location;
+ }
+
+ @Override
+ public String toString() {
+ return location;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof Source)) {
+ return false;
+ }
+ Source source = (Source) o;
+ return Objects.equals(location, source.location);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(location);
+ }
+}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/SystemNanoClock.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/SystemNanoClock.java
deleted file mode 100644
index d4ce19b..0000000
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/SystemNanoClock.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.core.util;
-
-import org.apache.logging.log4j.core.time.NanoClock;
-
-/**
- * @deprecated Use {@link org.apache.logging.log4j.core.time.SystemNanoClock} instead.
- */
-@Deprecated
-public final class SystemNanoClock implements NanoClock {
-
- /**
- * @see org.apache.logging.log4j.core.time.SystemNanoClock#nanoTime()
- * @deprecated use {@link org.apache.logging.log4j.core.time.SystemNanoClock} instead.
- */
- @Deprecated
- @Override
- public long nanoTime() {
- return System.nanoTime();
- }
-
-}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/Throwables.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/Throwables.java
index 0d56ef1..e6c758e 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/Throwables.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/Throwables.java
@@ -40,12 +40,26 @@
* @return the deepest throwable or the given throwable
*/
public static Throwable getRootCause(final Throwable throwable) {
+
+ // Keep a second pointer that slowly walks the causal chain. If the fast
+ // pointer ever catches the slower pointer, then there's a loop.
+ Throwable slowPointer = throwable;
+ boolean advanceSlowPointer = false;
+
+ Throwable parent = throwable;
Throwable cause;
- Throwable root = throwable;
- while ((cause = root.getCause()) != null) {
- root = cause;
+ while ((cause = parent.getCause()) != null) {
+ parent = cause;
+ if (parent == slowPointer) {
+ throw new IllegalArgumentException("loop in causal chain");
+ }
+ if (advanceSlowPointer) {
+ slowPointer = slowPointer.getCause();
+ }
+ advanceSlowPointer = !advanceSlowPointer; // only advance every other iteration
}
- return root;
+ return parent;
+
}
/**
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/UuidUtil.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/UuidUtil.java
index 5203649..cf1d9d3 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/UuidUtil.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/UuidUtil.java
@@ -52,8 +52,6 @@
private static final long NUM_100NS_INTERVALS_SINCE_UUID_EPOCH = 0x01b21dd213814000L;
private static final long INITIAL_UUID_SEQNO = PropertiesUtil.getProperties().getLongProperty(UUID_SEQUENCE, 0);
- private static final long LEAST;
-
private static final long LOW_MASK = 0xffffffffL;
private static final long MID_MASK = 0xffff00000000L;
private static final long HIGH_MASK = 0xfff000000000000L;
@@ -63,8 +61,19 @@
private static final int SHIFT_6 = 48;
private static final int HUNDRED_NANOS_PER_MILLI = 10000;
- static {
- byte[] mac = getLocalMacAddress();
+ private static final long LEAST = initialize(NetUtils.getMacAddress());
+
+ /* This class cannot be instantiated */
+ private UuidUtil() {
+ }
+
+ /**
+ * Initializes this class
+ *
+ * @param mac MAC address
+ * @return Least
+ */
+ static long initialize(byte[] mac) {
final Random randomGenerator = new SecureRandom();
if (mac == null || mac.length == 0) {
mac = new byte[6];
@@ -78,7 +87,7 @@
for (int i = 2; i < NODE_SIZE; ++i) {
node[i] = 0;
}
- System.arraycopy(mac, index, node, index + 2, length);
+ System.arraycopy(mac, index, node, 2, length);
final ByteBuffer buf = ByteBuffer.wrap(node);
long rand = INITIAL_UUID_SEQNO;
String assigned = PropertiesUtil.getProperties().getStringProperty(ASSIGNED_SEQUENCES);
@@ -114,12 +123,7 @@
assigned = assigned == null ? Long.toString(rand) : assigned + ',' + Long.toString(rand);
System.setProperty(ASSIGNED_SEQUENCES, assigned);
- LEAST = buf.getLong() | rand << SHIFT_6;
- }
-
-
- /* This class cannot be instantiated */
- private UuidUtil() {
+ return buf.getLong() | rand << SHIFT_6;
}
/**
@@ -148,46 +152,5 @@
final long most = timeLow | timeMid | TYPE1 | timeHi;
return new UUID(most, LEAST);
}
-
- /**
- * Returns the local network interface's MAC address if possible. The local network interface is defined here as
- * the {@link java.net.NetworkInterface} that is both up and not a loopback interface.
- *
- * @return the MAC address of the local network interface or {@code null} if no MAC address could be determined.
- * @since 2.1
- */
- private static byte[] getLocalMacAddress() {
- byte[] mac = null;
- try {
- final InetAddress localHost = InetAddress.getLocalHost();
- try {
- final NetworkInterface localInterface = NetworkInterface.getByInetAddress(localHost);
- if (isUpAndNotLoopback(localInterface)) {
- mac = localInterface.getHardwareAddress();
- }
- if (mac == null) {
- final Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces();
- while (networkInterfaces.hasMoreElements() && mac == null) {
- final NetworkInterface nic = networkInterfaces.nextElement();
- if (isUpAndNotLoopback(nic)) {
- mac = nic.getHardwareAddress();
- }
- }
- }
- } catch (final SocketException e) {
- LOGGER.catching(e);
- }
- if (mac == null || mac.length == 0) {
- mac = localHost.getAddress();
- }
- } catch (final UnknownHostException ignored) {
- // ignored
- }
- return mac;
- }
-
- private static boolean isUpAndNotLoopback(final NetworkInterface ni) throws SocketException {
- return ni != null && !ni.isLoopback() && ni.isUp();
- }
}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/WatchEventService.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/WatchEventService.java
new file mode 100644
index 0000000..65d9df2
--- /dev/null
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/WatchEventService.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;
+
+/**
+ *
+ */
+public interface WatchEventService {
+
+ void subscribe(WatchManager manager);
+
+ void unsubscribe(WatchManager manager);
+}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/WatchManager.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/WatchManager.java
index b38d899..33f5bb9 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/WatchManager.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/WatchManager.java
@@ -16,36 +16,55 @@
*/
package org.apache.logging.log4j.core.util;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.core.AbstractLifeCycle;
+import org.apache.logging.log4j.core.config.ConfigurationFileWatcher;
+import org.apache.logging.log4j.core.config.ConfigurationScheduler;
+import org.apache.logging.log4j.status.StatusLogger;
+import org.apache.logging.log4j.util.LoaderUtil;
+
import java.io.File;
+import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
+import java.util.ServiceLoader;
+import java.util.UUID;
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.Logger;
-import org.apache.logging.log4j.core.AbstractLifeCycle;
-import org.apache.logging.log4j.core.config.ConfigurationScheduler;
-import org.apache.logging.log4j.status.StatusLogger;
+import java.util.concurrent.atomic.AtomicInteger;
/**
* Manages {@link FileWatcher}s.
- *
+ *
* @see FileWatcher
* @see ConfigurationScheduler
*/
public class WatchManager extends AbstractLifeCycle {
- private static Logger logger = StatusLogger.getLogger();
- private final ConcurrentMap<File, FileMonitor> watchers = new ConcurrentHashMap<>();
+ private static final Logger logger = StatusLogger.getLogger();
+ private final ConcurrentMap<Source, ConfigurationMonitor> watchers = new ConcurrentHashMap<>();
private int intervalSeconds = 0;
private ScheduledFuture<?> future;
private final ConfigurationScheduler scheduler;
+ private final List<WatchEventService> eventServiceList;
+ // This just needs to be a unique key within the WatchEventManager.
+ private final UUID id = LocalUUID.get();
public WatchManager(final ConfigurationScheduler scheduler) {
this.scheduler = scheduler;
+ eventServiceList = getEventServices();
+ }
+
+ public UUID getId() {
+ return this.id;
+ }
+
+ public boolean hasEventListeners() {
+ return eventServiceList.size() > 0;
}
/**
@@ -55,13 +74,13 @@
* This allows you to start, stop, reset and start again a manager, without triggering file modified events if the a
* watched file has changed during the period of time when the manager was stopped.
* </p>
- *
+ *
* @since 2.11.0
*/
public void reset() {
logger.debug("Resetting {}", this);
- for (final File file : watchers.keySet()) {
- reset(file);
+ for (final Source source : watchers.keySet()) {
+ reset(source);
}
}
@@ -72,25 +91,45 @@
* This allows you to start, stop, reset and start again a manager, without triggering file modified events if the
* given watched file has changed during the period of time when the manager was stopped.
* </p>
- *
- * @param file
- * the file for the monitor to reset.
+ *
+ * @param file the file for the monitor to reset.
* @since 2.11.0
*/
public void reset(final File file) {
if (file == null) {
return;
}
- final FileMonitor fileMonitor = watchers.get(file);
- if (fileMonitor != null) {
- final long lastModifiedMillis = file.lastModified();
- if (lastModifiedMillis != fileMonitor.lastModifiedMillis) {
+ Source source = new Source(file);
+ reset(source);
+ }
+
+
+ /**
+ * Resets the configuration monitor for the given file being watched to its current last modified time. If this
+ * manager does not watch the given configuration, nothing happens.
+ * <p>
+ * This allows you to start, stop, reset and start again a manager, without triggering file modified events if the
+ * given watched configuration has changed during the period of time when the manager was stopped.
+ * </p>
+ *
+ * @param source the Source for the monitor to reset.
+ * @since 2.12.0
+ */
+ public void reset(final Source source) {
+ if (source == null) {
+ return;
+ }
+ final ConfigurationMonitor monitor = watchers.get(source);
+ if (monitor != null) {
+ Watcher watcher = monitor.getWatcher();
+ if (watcher.isModified()) {
+ final long lastModifiedMillis = watcher.getLastModified();
if (logger.isDebugEnabled()) {
- logger.debug("Resetting file monitor for '{}' from {} ({}) to {} ({})", file,
- millisToString(fileMonitor.lastModifiedMillis), fileMonitor.lastModifiedMillis,
+ logger.debug("Resetting file monitor for '{}' from {} ({}) to {} ({})", source.getLocation(),
+ millisToString(monitor.lastModifiedMillis), monitor.lastModifiedMillis,
millisToString(lastModifiedMillis), lastModifiedMillis);
}
- fileMonitor.setLastModifiedMillis(lastModifiedMillis);
+ monitor.setLastModifiedMillis(lastModifiedMillis);
}
}
}
@@ -108,7 +147,7 @@
/**
* Gets how often this manager checks for file modifications.
- *
+ *
* @return how often, in seconds, this manager checks for file modifications.
*/
public int getIntervalSeconds() {
@@ -118,15 +157,22 @@
@Override
public void start() {
super.start();
+
if (intervalSeconds > 0) {
future = scheduler.scheduleWithFixedDelay(new WatchRunnable(), intervalSeconds, intervalSeconds,
TimeUnit.SECONDS);
}
+ for (WatchEventService service : eventServiceList) {
+ service.subscribe(this);
+ }
}
@Override
public boolean stop(final long timeout, final TimeUnit timeUnit) {
setStopping();
+ for (WatchEventService service : eventServiceList) {
+ service.unsubscribe(this);
+ }
final boolean stopped = stop(future);
setStopped();
return stopped;
@@ -134,36 +180,91 @@
/**
* Unwatches the given file.
- *
- * @param file
- * the file to stop watching.
+ *
+ * @param file the file to stop watching.
* @since 2.11.0
*/
public void unwatchFile(final File file) {
- logger.debug("Unwatching file '{}'", file);
- watchers.remove(file);
+ Source source = new Source(file);
+ unwatch(source);
+ }
+
+ /**
+ * Unwatches the given file.
+ *
+ * @param source the Source to stop watching.
+ * the file to stop watching.
+ * @since 2.12.0
+ */
+ public void unwatch(final Source source) {
+ logger.debug("Unwatching configuration {}", source);
+ watchers.remove(source);
+ }
+
+ public void checkFiles() {
+ new WatchRunnable().run();
}
/**
* Watches the given file.
- *
- * @param file
- * the file to watch.
- * @param watcher
- * the watcher to notify of file changes.
+ *
+ * @param file the file to watch.
+ * @param fileWatcher the watcher to notify of file changes.
*/
- public void watchFile(final File file, final FileWatcher watcher) {
- final long lastModified = file.lastModified();
- if (logger.isDebugEnabled()) {
- logger.debug("Watching file '{}' for lastModified {} ({})", file, millisToString(lastModified), lastModified);
+ public void watchFile(final File file, final FileWatcher fileWatcher) {
+ Watcher watcher;
+ if (fileWatcher instanceof Watcher) {
+ watcher = (Watcher) fileWatcher;
+ } else {
+ watcher = new WrappedFileWatcher(fileWatcher);
}
- watchers.put(file, new FileMonitor(lastModified, watcher));
+ Source source = new Source(file);
+ watch(source, watcher);
}
+ /**
+ * Watches the given file.
+ *
+ * @param source the source to watch.
+ * @param watcher the watcher to notify of file changes.
+ */
+ public void watch(final Source source, final Watcher watcher) {
+ watcher.watching(source);
+ final long lastModified = watcher.getLastModified();
+ if (logger.isDebugEnabled()) {
+ logger.debug("Watching configuration '{}' for lastModified {} ({})", source, millisToString(lastModified), lastModified);
+ }
+ watchers.put(source, new ConfigurationMonitor(lastModified, watcher));
+ }
+
+ /**
+ * Returns a Map of the file watchers.
+ *
+ * @return A Map of the file watchers.
+ * @deprecated use getConfigurationWatchers.
+ */
public Map<File, FileWatcher> getWatchers() {
final Map<File, FileWatcher> map = new HashMap<>(watchers.size());
- for (final Map.Entry<File, FileMonitor> entry : watchers.entrySet()) {
- map.put(entry.getKey(), entry.getValue().fileWatcher);
+ for (Map.Entry<Source, ConfigurationMonitor> entry : watchers.entrySet()) {
+ if (entry.getValue().getWatcher() instanceof ConfigurationFileWatcher) {
+ map.put(entry.getKey().getFile(), (FileWatcher) entry.getValue().getWatcher());
+ } else {
+ map.put(entry.getKey().getFile(), new WrappedFileWatcher((FileWatcher) entry.getValue().getWatcher()));
+ }
+ }
+ return map;
+ }
+
+ /**
+ * Return the ConfigurationWaatchers.
+ *
+ * @return the ConfigurationWatchers.
+ * @since 2.11.2
+ */
+ public Map<Source, Watcher> getConfigurationWatchers() {
+ final Map<Source, Watcher> map = new HashMap<>(watchers.size());
+ for (final Map.Entry<Source, ConfigurationMonitor> entry : watchers.entrySet()) {
+ map.put(entry.getKey(), entry.getValue().getWatcher());
}
return map;
}
@@ -171,7 +272,23 @@
private String millisToString(final long millis) {
return new Date(millis).toString();
}
-
+
+ private List<WatchEventService> getEventServices() {
+ List<WatchEventService> list = new ArrayList<>();
+ for (final ClassLoader classLoader : LoaderUtil.getClassLoaders()) {
+ try {
+ final ServiceLoader<WatchEventService> serviceLoader =
+ ServiceLoader.load(WatchEventService.class, classLoader);
+ for (final WatchEventService service : serviceLoader) {
+ list.add(service);
+ }
+ } catch (final Throwable ex) {
+ LOGGER.debug("Unable to retrieve WatchEventService from ClassLoader {}", classLoader, ex);
+ }
+ }
+ return list;
+ }
+
private final class WatchRunnable implements Runnable {
// Use a hard class reference here in case a refactoring changes the class name.
@@ -180,34 +297,34 @@
@Override
public void run() {
logger.trace("{} run triggered.", SIMPLE_NAME);
- for (final Map.Entry<File, FileMonitor> entry : watchers.entrySet()) {
- final File file = entry.getKey();
- final FileMonitor fileMonitor = entry.getValue();
- final long lastModfied = file.lastModified();
- if (fileModified(fileMonitor, lastModfied)) {
+ for (final Map.Entry<Source, ConfigurationMonitor> entry : watchers.entrySet()) {
+ final Source source = entry.getKey();
+ final ConfigurationMonitor monitor = entry.getValue();
+ if (monitor.getWatcher().isModified()) {
+ final long lastModified = monitor.getWatcher().getLastModified();
if (logger.isInfoEnabled()) {
- logger.info("File '{}' was modified on {} ({}), previous modification was on {} ({})", file,
- millisToString(lastModfied), lastModfied, millisToString(fileMonitor.lastModifiedMillis),
- fileMonitor.lastModifiedMillis);
+ logger.info("Source '{}' was modified on {} ({}), previous modification was on {} ({})", source,
+ millisToString(lastModified), lastModified, millisToString(monitor.lastModifiedMillis),
+ monitor.lastModifiedMillis);
}
- fileMonitor.lastModifiedMillis = lastModfied;
- fileMonitor.fileWatcher.fileModified(file);
+ monitor.lastModifiedMillis = lastModified;
+ monitor.getWatcher().modified();
}
}
logger.trace("{} run ended.", SIMPLE_NAME);
}
-
- private boolean fileModified(final FileMonitor fileMonitor, final long lastModifiedMillis) {
- return lastModifiedMillis != fileMonitor.lastModifiedMillis;
- }
}
- private final class FileMonitor {
- private final FileWatcher fileWatcher;
+ private final class ConfigurationMonitor {
+ private final Watcher watcher;
private volatile long lastModifiedMillis;
- public FileMonitor(final long lastModifiedMillis, final FileWatcher fileWatcher) {
- this.fileWatcher = fileWatcher;
+ public Watcher getWatcher() {
+ return watcher;
+ }
+
+ public ConfigurationMonitor(final long lastModifiedMillis, final Watcher watcher) {
+ this.watcher = watcher;
this.lastModifiedMillis = lastModifiedMillis;
}
@@ -217,7 +334,7 @@
@Override
public String toString() {
- return "FileMonitor [fileWatcher=" + fileWatcher + ", lastModifiedMillis=" + lastModifiedMillis + "]";
+ return "ConfigurationMonitor [watcher=" + watcher + ", lastModifiedMillis=" + lastModifiedMillis + "]";
}
}
@@ -227,4 +344,31 @@
return "WatchManager [intervalSeconds=" + intervalSeconds + ", watchers=" + watchers + ", scheduler="
+ scheduler + ", future=" + future + "]";
}
+
+ private static class LocalUUID {
+ private static final long LOW_MASK = 0xffffffffL;
+ private static final long MID_MASK = 0xffff00000000L;
+ private static final long HIGH_MASK = 0xfff000000000000L;
+ private static final int NODE_SIZE = 8;
+ private static final int SHIFT_2 = 16;
+ private static final int SHIFT_4 = 32;
+ private static final int SHIFT_6 = 48;
+ private static final int HUNDRED_NANOS_PER_MILLI = 10000;
+ private static final long NUM_100NS_INTERVALS_SINCE_UUID_EPOCH = 0x01b21dd213814000L;
+ private static final AtomicInteger COUNT = new AtomicInteger(0);
+ private static final long TYPE1 = 0x1000L;
+ private static final byte VARIANT = (byte) 0x80;
+ private static final int SEQUENCE_MASK = 0x3FFF;
+
+
+ public static UUID get() {
+ final long time = ((System.currentTimeMillis() * HUNDRED_NANOS_PER_MILLI) +
+ NUM_100NS_INTERVALS_SINCE_UUID_EPOCH) + (COUNT.incrementAndGet() % HUNDRED_NANOS_PER_MILLI);
+ final long timeLow = (time & LOW_MASK) << SHIFT_4;
+ final long timeMid = (time & MID_MASK) >> SHIFT_2;
+ final long timeHi = (time & HIGH_MASK) >> SHIFT_6;
+ final long most = timeLow | timeMid | TYPE1 | timeHi;
+ return new UUID(most, COUNT.incrementAndGet());
+ }
+ }
}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/Watcher.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/Watcher.java
new file mode 100644
index 0000000..61552a9
--- /dev/null
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/Watcher.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.util;
+
+import java.util.List;
+
+import org.apache.logging.log4j.core.config.ConfigurationListener;
+import org.apache.logging.log4j.core.config.Reconfigurable;
+
+/**
+ * Watches for changes in a Source and performs an action when it is modified.
+ *
+ * @see WatchManager
+ */
+public interface Watcher {
+
+ String CATEGORY = "Watcher";
+ String ELEMENT_TYPE = "watcher";
+
+ /**
+ * Returns the list of listeners for this configuration.
+ * @return The list of listeners.
+ */
+ List<ConfigurationListener> getListeners();
+
+ /**
+ * Called when the configuration has been modified.
+ */
+ void modified();
+
+ /**
+ * Periodically called to determine if the configuration has been modified.
+ * @return true if the configuration was modified, false otherwise.
+ */
+ boolean isModified();
+
+ /**
+ * Returns the time the source was last modified or 0 if it is not available.
+ * @return the time the source was last modified.
+ */
+ long getLastModified();
+
+ /**
+ * Called when the Watcher is registered.
+ * @param source the Source that is being watched.
+ */
+ void watching(Source source);
+
+ /**
+ * Returns the Source being monitored.
+ * @return the Source.
+ */
+ Source getSource();
+
+ /**
+ * Creates a new Watcher by copying the original and using the new Reconfigurable and listeners.
+ * @param reconfigurable The Reconfigurable.
+ * @param listeners the listeners.
+ * @param lastModifiedMillis The time the resource was last modified in milliseconds.
+ * @return A new Watcher.
+ */
+ Watcher newWatcher(Reconfigurable reconfigurable, List<ConfigurationListener> listeners, long lastModifiedMillis);
+}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/WatcherFactory.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/WatcherFactory.java
new file mode 100644
index 0000000..31acd5a
--- /dev/null
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/WatcherFactory.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.util;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.core.config.Configuration;
+import org.apache.logging.log4j.core.config.ConfigurationFileWatcher;
+import org.apache.logging.log4j.core.config.ConfigurationListener;
+import org.apache.logging.log4j.core.config.Reconfigurable;
+import org.apache.logging.log4j.plugins.util.PluginManager;
+import org.apache.logging.log4j.plugins.util.PluginType;
+import org.apache.logging.log4j.status.StatusLogger;
+
+/**
+ * Creates Watchers of various types.
+ */
+public class WatcherFactory {
+
+ private static final Logger LOGGER = StatusLogger.getLogger();
+ private static final PluginManager pluginManager = new PluginManager(Watcher.CATEGORY);
+
+ private static volatile WatcherFactory factory;
+
+ private final Map<String, PluginType<?>> plugins;
+
+ private WatcherFactory(List<String> packages) {
+ pluginManager.collectPlugins(packages);
+ plugins = pluginManager.getPlugins();
+ }
+
+ public static WatcherFactory getInstance(List<String> packages) {
+ if (factory == null) {
+ synchronized (pluginManager) {
+ if (factory == null) {
+ factory = new WatcherFactory(packages);
+ }
+ }
+ }
+ return factory;
+ }
+
+ public Watcher newWatcher(Source source, final Configuration configuration, final Reconfigurable reconfigurable,
+ final List<ConfigurationListener> configurationListeners, long lastModifiedMillis) {
+ if (source.getFile() != null) {
+ return new ConfigurationFileWatcher(configuration, reconfigurable, configurationListeners,
+ lastModifiedMillis);
+ } else {
+ String name = source.getURI().getScheme();
+ PluginType<?> pluginType = plugins.get(name);
+ if (pluginType != null) {
+ return instantiate(name, pluginType.getPluginClass().asSubclass(Watcher.class), configuration,
+ reconfigurable, configurationListeners, lastModifiedMillis);
+ }
+ LOGGER.info("No Watcher plugin is available for protocol '{}'", name);
+ return null;
+ }
+ }
+
+ public static <T extends Watcher> T instantiate(String name, final Class<T> clazz,
+ final Configuration configuration, final Reconfigurable reconfigurable,
+ final List<ConfigurationListener> listeners, long lastModifiedMillis) {
+ Objects.requireNonNull(clazz, "No class provided");
+ try {
+ Constructor<T> constructor = clazz
+ .getConstructor(Configuration.class, Reconfigurable.class, List.class, long.class);
+ return constructor.newInstance(configuration, reconfigurable, listeners, lastModifiedMillis);
+ } catch (NoSuchMethodException ex) {
+ throw new IllegalArgumentException("No valid constructor for Watcher plugin " + name, ex);
+ } catch (final LinkageError | InstantiationException e) {
+ // LOG4J2-1051
+ // On platforms like Google App Engine and Android, some JRE classes are not supported: JMX, JNDI, etc.
+ throw new IllegalArgumentException(e);
+ } catch (final IllegalAccessException e) {
+ throw new IllegalStateException(e);
+ } catch (final InvocationTargetException e) {
+ Throwables.rethrow(e.getCause());
+ throw new InternalError("Unreachable");
+ }
+ }
+}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/WrappedFileWatcher.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/WrappedFileWatcher.java
new file mode 100644
index 0000000..54933b2
--- /dev/null
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/WrappedFileWatcher.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.util;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.logging.log4j.core.config.Configuration;
+import org.apache.logging.log4j.core.config.ConfigurationListener;
+import org.apache.logging.log4j.core.config.Reconfigurable;
+
+/**
+ *
+ */
+public class WrappedFileWatcher extends AbstractWatcher implements FileWatcher {
+
+ private final FileWatcher watcher;
+ private volatile long lastModifiedMillis;
+
+ public WrappedFileWatcher(FileWatcher watcher, final Configuration configuration,
+ final Reconfigurable reconfigurable, final List<ConfigurationListener> configurationListeners,
+ final long lastModifiedMillis) {
+ super(configuration, reconfigurable, configurationListeners);
+ this.watcher = watcher;
+ this.lastModifiedMillis = lastModifiedMillis;
+ }
+
+
+ public WrappedFileWatcher(FileWatcher watcher) {
+ super(null, null, null);
+ this.watcher = watcher;
+ }
+
+ public long getLastModified() {
+ return lastModifiedMillis;
+ }
+
+ @Override
+ public void fileModified(File file) {
+ watcher.fileModified(file);
+ }
+
+ @Override
+ public boolean isModified() {
+ long lastModified = getSource().getFile().lastModified();
+ if (lastModifiedMillis != lastModified) {
+ lastModifiedMillis = lastModified;
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public List<ConfigurationListener> getListeners() {
+ if (super.getListeners() != null) {
+ return Collections.unmodifiableList(super.getListeners());
+ } else {
+ return null;
+ }
+ }
+
+ @Override
+ public void modified() {
+ if (getListeners() != null) {
+ super.modified();
+ }
+ fileModified(getSource().getFile());
+ lastModifiedMillis = getSource().getFile().lastModified();
+ }
+
+ @Override
+ public void watching(Source source) {
+ lastModifiedMillis = source.getFile().lastModified();
+ super.watching(source);
+ }
+
+ @Override
+ public Watcher newWatcher(final Reconfigurable reconfigurable, final List<ConfigurationListener> listeners,
+ long lastModifiedMillis) {
+ WrappedFileWatcher watcher = new WrappedFileWatcher(this.watcher, 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/util/datetime/FastDateFormat.java b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/datetime/FastDateFormat.java
new file mode 100644
index 0000000..691b268
--- /dev/null
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/datetime/FastDateFormat.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.util.datetime;
+
+import java.text.FieldPosition;
+import java.text.ParseException;
+import java.text.ParsePosition;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.Locale;
+import java.util.TimeZone;
+
+import org.apache.logging.log4j.core.time.internal.format.*;
+
+/**
+ *
+ * @deprecated Use {@link org.apache.logging.log4j.core.time.internal.format.FastDateFormat}
+ */
+public class FastDateFormat extends Format implements DateParser, DatePrinter {
+
+ private org.apache.logging.log4j.core.time.internal.format.FastDateFormat formatter = null;
+
+ public static FastDateFormat getInstance() {
+ return new FastDateFormat(org.apache.logging.log4j.core.time.internal.format.FastDateFormat.getInstance());
+ }
+
+ public static FastDateFormat getInstance(final String pattern) {
+ return new FastDateFormat(org.apache.logging.log4j.core.time.internal.format.FastDateFormat.getInstance(pattern));
+ }
+
+ public static FastDateFormat getInstance(final String pattern, final TimeZone timeZone) {
+ return new FastDateFormat(org.apache.logging.log4j.core.time.internal.format.FastDateFormat.getInstance(pattern, timeZone));
+ }
+
+ public static FastDateFormat getInstance(final String pattern, final Locale locale) {
+ return new FastDateFormat(org.apache.logging.log4j.core.time.internal.format.FastDateFormat.getInstance(pattern, locale));
+ }
+
+ public static FastDateFormat getInstance(final String pattern, final TimeZone timeZone, final Locale locale) {
+ return new FastDateFormat(org.apache.logging.log4j.core.time.internal.format.FastDateFormat.getInstance(pattern, timeZone, locale));
+ }
+
+ public static FastDateFormat getDateInstance(final int style) {
+ return new FastDateFormat(org.apache.logging.log4j.core.time.internal.format.FastDateFormat.getDateInstance(style));
+ }
+
+ public static FastDateFormat getDateInstance(final int style, final Locale locale) {
+ return new FastDateFormat(org.apache.logging.log4j.core.time.internal.format.FastDateFormat.getDateInstance(style, locale));
+ }
+
+ public static FastDateFormat getDateInstance(final int style, final TimeZone timeZone) {
+ return new FastDateFormat(org.apache.logging.log4j.core.time.internal.format.FastDateFormat.getDateInstance(style, timeZone));
+ }
+
+ public static FastDateFormat getDateInstance(final int style, final TimeZone timeZone, final Locale locale) {
+ return new FastDateFormat(org.apache.logging.log4j.core.time.internal.format.FastDateFormat.getDateInstance(style, timeZone, locale));
+ }
+
+ public static FastDateFormat getTimeInstance(final int style) {
+ return new FastDateFormat(org.apache.logging.log4j.core.time.internal.format.FastDateFormat.getTimeInstance(style));
+ }
+
+ public static FastDateFormat getTimeInstance(final int style, final Locale locale) {
+ return new FastDateFormat(org.apache.logging.log4j.core.time.internal.format.FastDateFormat.getTimeInstance(style, locale));
+ }
+
+ public static FastDateFormat getTimeInstance(final int style, final TimeZone timeZone) {
+ return new FastDateFormat(org.apache.logging.log4j.core.time.internal.format.FastDateFormat.getTimeInstance(style, timeZone));
+ }
+
+ public static FastDateFormat getTimeInstance(final int style, final TimeZone timeZone, final Locale locale) {
+ return new FastDateFormat(org.apache.logging.log4j.core.time.internal.format.FastDateFormat.getTimeInstance(style, timeZone, locale));
+ }
+
+ public static FastDateFormat getDateTimeInstance(final int dateStyle, final int timeStyle) {
+ return new FastDateFormat(org.apache.logging.log4j.core.time.internal.format.FastDateFormat.getDateTimeInstance(dateStyle, timeStyle));
+ }
+
+ public static FastDateFormat getDateTimeInstance(final int dateStyle, final int timeStyle, final Locale locale) {
+ return new FastDateFormat(org.apache.logging.log4j.core.time.internal.format.FastDateFormat.getDateTimeInstance(dateStyle, timeStyle, locale));
+ }
+
+ public static FastDateFormat getDateTimeInstance(final int dateStyle, final int timeStyle, final TimeZone timeZone) {
+ return new FastDateFormat(org.apache.logging.log4j.core.time.internal.format.FastDateFormat.getDateTimeInstance(dateStyle, timeStyle, timeZone));
+ }
+
+ public static FastDateFormat getDateTimeInstance(
+ final int dateStyle, final int timeStyle, final TimeZone timeZone, final Locale locale) {
+ return new FastDateFormat(org.apache.logging.log4j.core.time.internal.format.FastDateFormat.getDateTimeInstance(dateStyle, timeStyle, timeZone, locale));
+ }
+
+ private FastDateFormat(org.apache.logging.log4j.core.time.internal.format.FastDateFormat formatter) {
+ this.formatter = formatter;
+ }
+
+ protected FastDateFormat(final String pattern, final TimeZone timeZone, final Locale locale) {
+ formatter = org.apache.logging.log4j.core.time.internal.format.FastDateFormat.getInstance(pattern,
+ timeZone, locale);
+ }
+
+ protected FastDateFormat(final String pattern, final TimeZone timeZone, final Locale locale, final Date centuryStart) {
+ formatter = org.apache.logging.log4j.core.time.internal.format.FastDateFormat.getDateTimeInstance(pattern,
+ timeZone, locale, centuryStart);
+ }
+
+ @Override
+ public StringBuilder format(final Object obj, final StringBuilder toAppendTo, final FieldPosition pos) {
+ return formatter.format(obj, toAppendTo, pos);
+ }
+
+ @Override
+ public String format(final long millis) {
+ return formatter.format(millis);
+ }
+
+ @Override
+ public String format(final Date date) {
+ return formatter.format(date);
+ }
+
+ @Override
+ public String format(final Calendar calendar) {
+ return formatter.format(calendar);
+ }
+
+ @Override
+ public <B extends Appendable> B format(final long millis, final B buf) {
+ return formatter.format(millis, buf);
+ }
+
+ @Override
+ public <B extends Appendable> B format(final Date date, final B buf) {
+ return formatter.format(date, buf);
+ }
+
+ @Override
+ public <B extends Appendable> B format(final Calendar calendar, final B buf) {
+ return formatter.format(calendar, buf);
+ }
+
+ @Override
+ public Date parse(final String source) throws ParseException {
+ return formatter.parse(source);
+ }
+
+ @Override
+ public Date parse(final String source, final ParsePosition pos) {
+ return formatter.parse(source, pos);
+ }
+
+ @Override
+ public boolean parse(final String source, final ParsePosition pos, final Calendar calendar) {
+ return formatter.parse(source, pos, calendar);
+ }
+
+ @Override
+ public Object parseObject(final String source, final ParsePosition pos) {
+ return formatter.parseObject(source, pos);
+ }
+
+ @Override
+ public String getPattern() {
+ return formatter.getPattern();
+ }
+
+ @Override
+ public TimeZone getTimeZone() {
+ return formatter.getTimeZone();
+ }
+
+ @Override
+ public Locale getLocale() {
+ return formatter.getLocale();
+ }
+
+ public int getMaxLengthEstimate() {
+ return formatter.getMaxLengthEstimate();
+ }
+
+ @Override
+ public boolean equals(final Object obj) {
+ if (obj instanceof FastDateFormat == false) {
+ return false;
+ }
+ final FastDateFormat other = (FastDateFormat) obj;
+ // no need to check parser, as it has same invariants as printer
+ return formatter.equals(other.formatter);
+ }
+
+ @Override
+ public int hashCode() {
+ return formatter.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return formatter.toString();
+ }
+}
diff --git a/log4j-core/src/main/java/org/apache/logging/log4j/util/ProcessIdUtil.java b/log4j-core/src/main/java/org/apache/logging/log4j/util/ProcessIdUtil.java
deleted file mode 100644
index 685aabe..0000000
--- a/log4j-core/src/main/java/org/apache/logging/log4j/util/ProcessIdUtil.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.io.IOException;
-import java.lang.reflect.Method;
-
-/**
- * @Since 2.9
- */
-public class ProcessIdUtil {
-
- public static final String DEFAULT_PROCESSID = "-";
-
- public static String getProcessId() {
- try {
- // LOG4J2-2126 use reflection to improve compatibility with Android Platform which does not support JMX extensions
- Class<?> managementFactoryClass = Class.forName("java.lang.management.ManagementFactory");
- Method getRuntimeMXBean = managementFactoryClass.getDeclaredMethod("getRuntimeMXBean");
- Class<?> runtimeMXBeanClass = Class.forName("java.lang.management.RuntimeMXBean");
- Method getName = runtimeMXBeanClass.getDeclaredMethod("getName");
-
- Object runtimeMXBean = getRuntimeMXBean.invoke(null);
- String name = (String) getName.invoke(runtimeMXBean);
- //String name = ManagementFactory.getRuntimeMXBean().getName(); //JMX not allowed on Android
- return name.split("@")[0]; // likely works on most platforms
- } catch (final Exception ex) {
- try {
- return new File("/proc/self").getCanonicalFile().getName(); // try a Linux-specific way
- } catch (final IOException ignoredUseDefault) {
- // Ignore exception.
- }
- }
- return DEFAULT_PROCESSID;
- }
-}
diff --git a/log4j-core/src/main/resources/META-INF/services/javax.annotation.processing.Processor b/log4j-core/src/main/resources/META-INF/services/javax.annotation.processing.Processor
deleted file mode 100644
index bb9dcb9..0000000
--- a/log4j-core/src/main/resources/META-INF/services/javax.annotation.processing.Processor
+++ /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.core.config.plugins.processor.PluginProcessor
diff --git a/log4j-core/src/main/resources/META-INF/services/org.apache.logging.log4j.core.util.ContextDataProvider b/log4j-core/src/main/resources/META-INF/services/org.apache.logging.log4j.core.util.ContextDataProvider
new file mode 100644
index 0000000..7917658
--- /dev/null
+++ b/log4j-core/src/main/resources/META-INF/services/org.apache.logging.log4j.core.util.ContextDataProvider
@@ -0,0 +1 @@
+org.apache.logging.log4j.core.impl.ThreadContextDataProvider
\ No newline at end of file
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/categories/package-info.java b/log4j-core/src/test/java/org/apache/logging/log4j/categories/package-info.java
index 8b59f59..82d3474 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/categories/package-info.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/categories/package-info.java
@@ -19,4 +19,4 @@
* JUnit test categories. Unit tests should not specify a category as most tests are unit tests. For performance and
* integration tests, an appropriate category interface should be specified.
*/
-package org.apache.logging.log4j.categories;
\ No newline at end of file
+package org.apache.logging.log4j.categories;
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/CronRolloverApp.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/CronRolloverApp.java
index d149148..6bf68c4 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/CronRolloverApp.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/CronRolloverApp.java
@@ -37,7 +37,7 @@
}
} catch (final Exception e) {
//e.printStackTrace();
- logger.error("Excepcion general", e);
+ logger.error("Exception general", e);
}
}
}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/CustomLevelsOverrideTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/CustomLevelsOverrideTest.java
index 628d73f..2fed8af 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/CustomLevelsOverrideTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/CustomLevelsOverrideTest.java
@@ -22,8 +22,6 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
-import java.util.List;
-
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.junit.LoggerContextRule;
import org.apache.logging.log4j.test.appender.ListAppender;
@@ -31,9 +29,6 @@
import org.junit.ClassRule;
import org.junit.Test;
-/**
- *
- */
public class CustomLevelsOverrideTest {
private static final String CONFIG = "log4j-customLevels.xml";
@@ -81,13 +76,12 @@
@Test
public void testLog() {
final Logger logger = context.getLogger();
- final List<LogEvent> events = listAppender.getEvents();
- assertThat(events, hasSize(0));
+ assertThat(listAppender.getEvents(), hasSize(0));
logger.debug("Hello, {}", "World");
- assertThat(events, hasSize(1));
+ assertThat(listAppender.getEvents(), hasSize(1));
logger.log(warnLevel, "Hello DIAG");
- assertThat(events, hasSize(2));
- assertEquals(events.get(1).getLevel(), warnLevel);
+ assertThat(listAppender.getEvents(), hasSize(2));
+ assertEquals(listAppender.getEvents().get(1).getLevel(), warnLevel);
}
}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/CustomLevelsTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/CustomLevelsTest.java
index 7bb7297..826b397 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/CustomLevelsTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/CustomLevelsTest.java
@@ -21,8 +21,6 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
-import java.util.List;
-
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.junit.LoggerContextRule;
import org.apache.logging.log4j.test.appender.ListAppender;
@@ -30,9 +28,6 @@
import org.junit.ClassRule;
import org.junit.Test;
-/**
- *
- */
public class CustomLevelsTest {
private static final String CONFIG = "log4j-customLevels.xml";
@@ -70,13 +65,12 @@
@Test
public void testLog() {
final Logger logger = context.getLogger();
- final List<LogEvent> events = listAppender.getEvents();
- assertThat(events, hasSize(0));
+ assertThat(listAppender.getEvents(), hasSize(0));
logger.debug("Hello, {}", "World");
- assertThat(events, hasSize(1));
+ assertThat(listAppender.getEvents(), hasSize(1));
logger.log(diagLevel, "Hello DIAG");
- assertThat(events, hasSize(2));
- assertEquals(events.get(1).getLevel(), diagLevel);
+ assertThat(listAppender.getEvents(), hasSize(2));
+ assertEquals(listAppender.getEvents().get(1).getLevel(), diagLevel);
}
}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/GarbageCollectionHelper.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/GarbageCollectionHelper.java
index 01b9d8a..afb3817 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/GarbageCollectionHelper.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/GarbageCollectionHelper.java
@@ -27,9 +27,9 @@
import static org.junit.Assert.assertTrue;
-final class GarbageCollectionHelper implements Closeable, Runnable {
+public final class GarbageCollectionHelper implements Closeable, Runnable {
private static final OutputStream sink = ByteStreams.nullOutputStream();
- public final AtomicBoolean running = new AtomicBoolean();
+ private final AtomicBoolean running = new AtomicBoolean();
private final CountDownLatch latch = new CountDownLatch(1);
private final Thread gcThread = new Thread(new Runnable() {
@Override
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/GcFreeLoggingTestUtil.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/GcFreeLoggingTestUtil.java
index 69ffe2c..938234f 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/GcFreeLoggingTestUtil.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/GcFreeLoggingTestUtil.java
@@ -16,35 +16,33 @@
*/
package org.apache.logging.log4j.core;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import java.io.File;
-import java.net.URL;
-import java.nio.charset.Charset;
-import java.nio.file.Files;
-import java.util.List;
-import java.util.concurrent.atomic.AtomicBoolean;
-
+import com.google.monitoring.runtime.instrumentation.AllocationRecorder;
+import com.google.monitoring.runtime.instrumentation.Sampler;
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;
-import org.apache.logging.log4j.util.Strings;
-import com.google.monitoring.runtime.instrumentation.AllocationRecorder;
-import com.google.monitoring.runtime.instrumentation.Sampler;
+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 static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
/**
- * Utily methods for the GC-free logging tests.s.
+ * Utility methods for the GC-free logging tests.
*/
-public class GcFreeLoggingTestUtil {
+public enum GcFreeLoggingTestUtil {;
public static void executeLogging(final String configurationFile,
- final Class<?> testClass) throws Exception {
+ final Class<?> testClass) throws Exception {
System.setProperty("log4j2.enable.threadlocals", "true");
System.setProperty("log4j2.enable.direct.encoders", "true");
@@ -148,27 +146,47 @@
process.waitFor();
process.exitValue();
- final String text = new String(Files.readAllBytes(tempFile.toPath()));
- final List<String> lines = Files.readAllLines(tempFile.toPath(), Charset.defaultCharset());
- final String className = cls.getSimpleName();
- assertEquals(text, "FATAL o.a.l.l.c." + className + " [main] value1 {aKey=value1, key2=value2, prop1=value1, prop2=value2} This message is logged to the console",
- lines.get(0));
+ final AtomicInteger lineCounter = new AtomicInteger(0);
+ Files.lines(tempFile.toPath(), Charset.defaultCharset()).forEach(line -> {
- for (int i = 1; i < lines.size(); i++) {
- final String line = lines.get(i);
- assertFalse(i + ": " + line + Strings.LINE_SEPARATOR + text, line.contains("allocated") || line.contains("array"));
- }
+ // Trim the line.
+ line = line.trim();
+
+ // Check the first line.
+ final int lineNumber = lineCounter.incrementAndGet();
+ if (lineNumber == 1) {
+ final String className = cls.getSimpleName();
+ final String firstLinePattern = String.format(
+ "^FATAL .*\\.%s %s",
+ className,
+ Pattern.quote("[main] value1 {aKey=value1, " +
+ "key2=value2, prop1=value1, prop2=value2} " +
+ "This message is logged to the console"));
+ assertTrue(
+ "pattern mismatch at line 1: " + line,
+ line.matches(firstLinePattern));
+ }
+
+ // Check the rest of the lines.
+ else {
+ assertFalse(
+ "(allocated|array) pattern matches at line " + lineNumber + ": " + line,
+ line.contains("allocated") || line.contains("array"));
+ }
+
+ });
+
}
- private static File agentJar() {
+ private static File agentJar() throws Exception {
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);
+ final URL jarurl = new URL(temp.substring("jar:".length(), temp.indexOf('!')));
+ return new File(jarurl.toURI());
}
public static class MyCharSeq implements CharSequence {
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/LoggerTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/LoggerTest.java
index 4cafb7b..e385277 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/LoggerTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/LoggerTest.java
@@ -16,12 +16,6 @@
*/
package org.apache.logging.log4j.core;
-import static org.junit.Assert.assertArrayEquals;
-import static org.junit.Assert.assertEquals;
-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.Date;
import java.util.HashMap;
@@ -32,6 +26,7 @@
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;
@@ -41,19 +36,21 @@
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.apache.logging.log4j.test.appender.ListAppender;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TestName;
-/**
- *
- */
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.startsWith;
+import static org.junit.Assert.*;
+import static org.junit.Assert.assertThat;
+
public class LoggerTest {
private static final String CONFIG = "log4j-test2.xml";
@@ -87,6 +84,20 @@
org.apache.logging.log4j.Logger loggerGrandchild;
@Test
+ public void builder() {
+ logger.atDebug().withLocation().log("Hello");
+ Marker marker = MarkerManager.getMarker("test");
+ logger.atError().withMarker(marker).log("Hello {}", "John");
+ logger.atWarn().withThrowable(new Throwable("This is a test")).log((Message) new SimpleMessage("Log4j rocks!"));
+ final List<LogEvent> events = app.getEvents();
+ assertEventCount(events, 3);
+ assertEquals("Incorrect location", "org.apache.logging.log4j.core.LoggerTest.builder(LoggerTest.java:88)", events.get(0).getSource().toString());
+ assertEquals("Incorrect Level", Level.DEBUG, events.get(0).getLevel());
+ assertThat("Incorrect message", events.get(1).getMessage().getFormattedMessage(), equalTo("Hello John"));
+ assertNotNull("Missing Throwable", events.get(2).getThrown());
+ }
+
+ @Test
public void basicFlow() {
logger.traceEntry();
logger.traceExit();
@@ -96,21 +107,13 @@
@Test
public void simpleFlow() {
- logger.entry(CONFIG);
+ logger.traceEntry(CONFIG);
logger.traceExit(0);
final List<LogEvent> events = app.getEvents();
assertEventCount(events, 2);
}
@Test
- public void simpleFlowDepreacted() {
- logger.entry(CONFIG);
- logger.exit(0);
- final List<LogEvent> events = app.getEvents();
- assertEventCount(events, 2);
- }
-
- @Test
public void throwing() {
logger.throwing(new IllegalArgumentException("Test Exception"));
final List<LogEvent> events = app.getEvents();
@@ -138,14 +141,13 @@
@Test
public void debugChangeLevel() {
logger.debug("Debug message 1");
- final List<LogEvent> events = app.getEvents();
- assertEventCount(events, 1);
+ assertEventCount(app.getEvents(), 1);
Configurator.setLevel(logger.getName(), Level.OFF);
logger.debug("Debug message 2");
- assertEventCount(events, 1);
+ assertEventCount(app.getEvents(), 1);
Configurator.setLevel(logger.getName(), Level.DEBUG);
logger.debug("Debug message 3");
- assertEventCount(events, 2);
+ assertEventCount(app.getEvents(), 2);
}
@Test
@@ -154,18 +156,17 @@
logger.debug("Debug message 1");
loggerChild.debug("Debug message 1 child");
loggerGrandchild.debug("Debug message 1 grandchild");
- final List<LogEvent> events = app.getEvents();
- assertEventCount(events, 3);
+ 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(events, 3);
+ 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(events, 5);
+ assertEventCount(app.getEvents(), 5);
}
@Test
@@ -174,18 +175,17 @@
logger.debug("Debug message 1");
loggerChild.debug("Debug message 1 child");
loggerGrandchild.debug("Debug message 1 grandchild");
- final List<LogEvent> events = app.getEvents();
- assertEventCount(events, 3);
+ 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(events, 3);
+ 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(events, 6);
+ assertEventCount(app.getEvents(), 6);
}
@Test
@@ -194,33 +194,31 @@
// Use logger AND loggerChild
logger.debug("Debug message 1");
loggerChild.debug("Debug message 1 child");
- final List<LogEvent> events = app.getEvents();
- assertEventCount(events, 2);
+ 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(events, 3);
+ assertEventCount(app.getEvents(), 3);
Configurator.setLevel(logger.getName(), Level.DEBUG);
logger.debug("Debug message 3");
loggerChild.debug("Debug message 3 child");
- assertEventCount(events, 5);
+ assertEventCount(app.getEvents(), 5);
}
@Test
public void debugChangeLevelsMap() {
logger.debug("Debug message 1");
- final List<LogEvent> events = app.getEvents();
- assertEventCount(events, 1);
+ assertEventCount(app.getEvents(), 1);
final Map<String, Level> map = new HashMap<>();
map.put(logger.getName(), Level.OFF);
Configurator.setLevel(map);
logger.debug("Debug message 2");
- assertEventCount(events, 1);
+ assertEventCount(app.getEvents(), 1);
map.put(logger.getName(), Level.DEBUG);
Configurator.setLevel(map);
logger.debug("Debug message 3");
- assertEventCount(events, 2);
+ assertEventCount(app.getEvents(), 2);
}
@Test
@@ -228,8 +226,7 @@
logger.debug("Debug message 1");
loggerChild.debug("Debug message 1 C");
loggerGrandchild.debug("Debug message 1 GC");
- final List<LogEvent> events = app.getEvents();
- assertEventCount(events, 3);
+ assertEventCount(app.getEvents(), 3);
final Map<String, Level> map = new HashMap<>();
map.put(logger.getName(), Level.OFF);
map.put(loggerChild.getName(), Level.DEBUG);
@@ -238,7 +235,7 @@
logger.debug("Debug message 2");
loggerChild.debug("Debug message 2 C");
loggerGrandchild.debug("Debug message 2 GC");
- assertEventCount(events, 4);
+ assertEventCount(app.getEvents(), 4);
map.put(logger.getName(), Level.DEBUG);
map.put(loggerChild.getName(), Level.OFF);
map.put(loggerGrandchild.getName(), Level.DEBUG);
@@ -246,20 +243,19 @@
logger.debug("Debug message 3");
loggerChild.debug("Debug message 3 C");
loggerGrandchild.debug("Debug message 3 GC");
- assertEventCount(events, 6);
+ assertEventCount(app.getEvents(), 6);
}
@Test
public void debugChangeRootLevel() {
logger.debug("Debug message 1");
- final List<LogEvent> events = app.getEvents();
- assertEventCount(events, 1);
+ assertEventCount(app.getEvents(), 1);
Configurator.setRootLevel(Level.OFF);
logger.debug("Debug message 2");
- assertEventCount(events, 1);
+ assertEventCount(app.getEvents(), 1);
Configurator.setRootLevel(Level.DEBUG);
logger.debug("Debug message 3");
- assertEventCount(events, 2);
+ assertEventCount(app.getEvents(), 2);
}
@Test
@@ -293,15 +289,12 @@
return testLogger1;
}
- private static void checkMessageFactory(final MessageFactory messageFactory1, final Logger testLogger1) {
- if (messageFactory1 == null) {
- assertEquals(AbstractLogger.DEFAULT_MESSAGE_FACTORY_CLASS, testLogger1.getMessageFactory().getClass());
+ private static void checkMessageFactory(final MessageFactory messageFactory, final Logger testLogger) {
+ if (messageFactory == null) {
+ assertEquals(AbstractLogger.DEFAULT_MESSAGE_FACTORY_CLASS, testLogger.getMessageFactory().getClass());
} else {
- MessageFactory actual = testLogger1.getMessageFactory();
- if (actual instanceof MessageFactory2Adapter) {
- actual = ((MessageFactory2Adapter) actual).getOriginal();
- }
- assertEquals(messageFactory1, actual);
+ MessageFactory actual = testLogger.getMessageFactory();
+ assertEquals(messageFactory, actual);
}
}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/LoggingTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/LoggingTest.java
new file mode 100644
index 0000000..1365a08
--- /dev/null
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/LoggingTest.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 java.util.List;
+
+import org.apache.logging.log4j.junit.LoggerContextRule;
+import org.apache.logging.log4j.test.appender.ListAppender;
+import org.apache.logging.log4j.util.Timer;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestName;
+
+import static org.junit.Assert.assertEquals;
+
+
+public class LoggingTest {
+
+ private static final String CONFIG = "log4j-list.xml";
+
+ @Rule
+ public final TestName testName = new TestName();
+ private ListAppender list;
+
+
+ @Rule
+ public LoggerContextRule context = new LoggerContextRule(CONFIG);
+
+ private void assertEventCount(final List<LogEvent> events, final int expected) {
+ assertEquals("Incorrect number of events.", expected, events.size());
+ }
+
+ @Before
+ public void before() {
+ logger = context.getLogger("LoggerTest");
+ }
+
+ org.apache.logging.log4j.Logger logger;
+
+ @Test
+ public void logTime() {
+ Timer timer = new Timer("initial");
+ timer.start();
+ logger.info("This is a test");
+ System.out.println(timer.stop());
+ timer = new Timer("more", 100);
+/* timer.start();
+ for (int i=0; i < 100; ++i) {
+ logger.info("This is another test");
+ }
+ System.out.println(timer.stop());*/
+ }
+}
+
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/StrictXmlConfigTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/StrictXmlConfigTest.java
index c9af2c9..0b6af49 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/StrictXmlConfigTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/StrictXmlConfigTest.java
@@ -68,8 +68,8 @@
@Test
public void simpleFlow() {
- logger.entry(CONFIG);
- logger.exit(0);
+ logger.traceEntry(CONFIG);
+ logger.traceExit(0);
final List<LogEvent> events = app.getEvents();
assertEquals("Incorrect number of events. Expected 2, actual " + events.size(), 2, events.size());
}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/TestPatternConverters.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/TestPatternConverters.java
index 5cba8e0..e70d7e8 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/TestPatternConverters.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/TestPatternConverters.java
@@ -16,7 +16,7 @@
*/
package org.apache.logging.log4j.core;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.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;
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/TimestampMessageTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/TimestampMessageTest.java
index 188c6f8..728c038 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/TimestampMessageTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/TimestampMessageTest.java
@@ -79,7 +79,7 @@
public static class PoisonClock implements Clock {
public PoisonClock() {
super();
- // Breakpoint here for debuging.
+ // Breakpoint here for debugging.
}
@Override
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderBuilderTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderBuilderTest.java
index 01025b4..d0ff679 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderBuilderTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderBuilderTest.java
@@ -18,6 +18,7 @@
import java.nio.charset.Charset;
+import org.apache.logging.log4j.core.ErrorHandler;
import org.apache.logging.log4j.core.layout.PatternLayout;
import org.junit.Assert;
import org.junit.Test;
@@ -39,11 +40,24 @@
*/
@Test
public void testDefaultLayoutDefaultCharset() {
- final ConsoleAppender appender = ConsoleAppender.newBuilder().withName("test").build();
+ 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();
Assert.assertEquals(expectedName, layout.getCharset().name());
}
+ /**
+ * Tests https://issues.apache.org/jira/browse/LOG4J2-2441
+ */
+ @Test
+ public void testSetNullErrorHandlerIsNotAllowed() {
+ final ConsoleAppender appender = ConsoleAppender.newBuilder().setName("test").build();
+ ErrorHandler handler = appender.getHandler();
+ Assert.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);
+ Assert.assertSame(handler, appender.getHandler());
+ }
}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderTest.java
index e203db1..f75f903 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/ConsoleAppenderTest.java
@@ -91,9 +91,9 @@
private void testConsoleStreamManagerDoesNotClose(final PrintStream ps, final Target targetName, final SystemSetter systemSetter) {
try {
systemSetter.systemSet(psMock);
- final Layout<String> layout = PatternLayout.newBuilder().withAlwaysWriteExceptions(true).build();
- final ConsoleAppender app = ConsoleAppender.newBuilder().withLayout(layout).setTarget(targetName)
- .withName("Console").withIgnoreExceptions(false).build();
+ final Layout<String> layout = PatternLayout.newBuilder().setAlwaysWriteExceptions(true).build();
+ final ConsoleAppender app = ConsoleAppender.newBuilder().setLayout(layout).setTarget(targetName)
+ .setName("Console").setIgnoreExceptions(false).build();
app.start();
assertTrue("Appender did not start", app.isStarted());
@@ -126,7 +126,7 @@
private void testFollowSystemPrintStream(final PrintStream ps, final Target target, final SystemSetter systemSetter) {
final ConsoleAppender app = ConsoleAppender.newBuilder().setTarget(target).setFollow(true)
- .withIgnoreExceptions(false).withName("test").build();
+ .setIgnoreExceptions(false).setName("test").build();
Assert.assertEquals(target, app.getTarget());
app.start();
try {
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/FileAppenderPermissionsTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/FileAppenderPermissionsTest.java
index 716c9de..396be82 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/FileAppenderPermissionsTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/FileAppenderPermissionsTest.java
@@ -88,19 +88,19 @@
public void testFilePermissionsAPI() throws Exception {
final File file = new File(DIR, "AppenderTest-" + fileIndex + ".log");
final Path path = file.toPath();
- final Layout<String> layout = PatternLayout.newBuilder().withPattern(PatternLayout.SIMPLE_CONVERSION_PATTERN)
+ final Layout<String> layout = PatternLayout.newBuilder().setPattern(PatternLayout.SIMPLE_CONVERSION_PATTERN)
.build();
// @formatter:off
final FileAppender appender = FileAppender.newBuilder()
- .withFileName(file.getAbsolutePath())
- .withName("test")
- .withImmediateFlush(false)
- .withIgnoreExceptions(false)
- .withBufferedIo(false)
- .withBufferSize(1)
- .withLayout(layout)
- .withCreateOnDemand(createOnDemand)
- .withFilePermissions(filePermissions)
+ .setFileName(file.getAbsolutePath())
+ .setName("test")
+ .setImmediateFlush(false)
+ .setIgnoreExceptions(false)
+ .setBufferedIo(false)
+ .setBufferSize(1)
+ .setLayout(layout)
+ .setCreateOnDemand(createOnDemand)
+ .setFilePermissions(filePermissions)
.build();
// @formatter:on
try {
@@ -143,20 +143,20 @@
final String group = findAGroup(user);
assertNotNull(group);
- final Layout<String> layout = PatternLayout.newBuilder().withPattern(PatternLayout.SIMPLE_CONVERSION_PATTERN)
+ final Layout<String> layout = PatternLayout.newBuilder().setPattern(PatternLayout.SIMPLE_CONVERSION_PATTERN)
.build();
// @formatter:off
final FileAppender appender = FileAppender.newBuilder()
- .withFileName(file.getAbsolutePath())
- .withName("test")
- .withImmediateFlush(true)
- .withIgnoreExceptions(false)
- .withBufferedIo(false)
- .withBufferSize(1)
- .withLayout(layout)
- .withFilePermissions(filePermissions)
- .withFileOwner(user)
- .withFileGroup(group)
+ .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 {
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/FileAppenderPermissionsXmlConfigTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/FileAppenderPermissionsXmlConfigTest.java
index b041aa6..dd1dc81 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/FileAppenderPermissionsXmlConfigTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/FileAppenderPermissionsXmlConfigTest.java
@@ -63,4 +63,4 @@
}
-}
\ No newline at end of file
+}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/FileAppenderTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/FileAppenderTest.java
index 066b560..465918a 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/FileAppenderTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/FileAppenderTest.java
@@ -93,14 +93,14 @@
final Layout<String> layout = createPatternLayout();
// @formatter:off
final FileAppender appender = FileAppender.newBuilder()
- .withFileName(FILE_NAME)
- .withName("test")
- .withImmediateFlush(false)
- .withIgnoreExceptions(false)
- .withBufferedIo(false)
- .withBufferSize(1)
- .withLayout(layout)
- .withCreateOnDemand(createOnDemand)
+ .setFileName(FILE_NAME)
+ .setName("test")
+ .setImmediateFlush(false)
+ .setIgnoreExceptions(false)
+ .setBufferedIo(false)
+ .setBufferSize(1)
+ .setLayout(layout)
+ .setCreateOnDemand(createOnDemand)
.build();
// @formatter:on
Assert.assertEquals(createOnDemand, appender.getManager().isCreateOnDemand());
@@ -115,7 +115,7 @@
}
private static PatternLayout createPatternLayout() {
- return PatternLayout.newBuilder().withPattern(PatternLayout.SIMPLE_CONVERSION_PATTERN)
+ return PatternLayout.newBuilder().setPattern(PatternLayout.SIMPLE_CONVERSION_PATTERN)
.build();
}
@@ -124,14 +124,14 @@
final Layout<String> layout = createPatternLayout();
// @formatter:off
final FileAppender appender = FileAppender.newBuilder()
- .withFileName(FILE_NAME)
- .withName("test")
- .withImmediateFlush(false)
- .withIgnoreExceptions(false)
- .withBufferedIo(false)
- .withBufferSize(1)
- .withLayout(layout)
- .withCreateOnDemand(createOnDemand)
+ .setFileName(FILE_NAME)
+ .setName("test")
+ .setImmediateFlush(false)
+ .setIgnoreExceptions(false)
+ .setBufferedIo(false)
+ .setBufferSize(1)
+ .setLayout(layout)
+ .setCreateOnDemand(createOnDemand)
.build();
// @formatter:on
try {
@@ -233,14 +233,14 @@
final Layout<String> layout = createPatternLayout();
// @formatter:off
final FileAppender appender = FileAppender.newBuilder()
- .withFileName(FILE_NAME)
- .withName("test")
- .withImmediateFlush(false)
- .withIgnoreExceptions(false)
- .withLocking(locking)
- .withBufferedIo(false)
- .withLayout(layout)
- .withCreateOnDemand(createOnDemand)
+ .setFileName(FILE_NAME)
+ .setName("test")
+ .setImmediateFlush(false)
+ .setIgnoreExceptions(false)
+ .setLocking(locking)
+ .setBufferedIo(false)
+ .setLayout(layout)
+ .setCreateOnDemand(createOnDemand)
.build();
// @formatter:on
Assert.assertEquals(createOnDemand, appender.getManager().isCreateOnDemand());
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/HangingAppender.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/HangingAppender.java
index 1c7b237..a371c49 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/HangingAppender.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/HangingAppender.java
@@ -24,11 +24,12 @@
import org.apache.logging.log4j.core.Filter;
import org.apache.logging.log4j.core.Layout;
import org.apache.logging.log4j.core.LogEvent;
-import org.apache.logging.log4j.core.config.plugins.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.config.Property;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginAttribute;
+import org.apache.logging.log4j.plugins.PluginElement;
+import org.apache.logging.log4j.plugins.PluginFactory;
+import org.apache.logging.log4j.plugins.validation.constraints.Required;
@Plugin(name = "Hanging", category = Core.CATEGORY_NAME, elementType = Appender.ELEMENT_TYPE, printObject = true)
public class HangingAppender extends AbstractAppender {
@@ -40,7 +41,7 @@
private final long shutdownDelay;
public HangingAppender(final String name, final long delay, final long startupDelay, final long shutdownDelay) {
- super(name, null, null);
+ super(name, null, null, true, Property.EMPTY_ARRAY);
this.delay = delay;
this.startupDelay = startupDelay;
this.shutdownDelay = shutdownDelay;
@@ -57,14 +58,14 @@
@PluginFactory
public static HangingAppender createAppender(
- @PluginAttribute("name")
+ @PluginAttribute
@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<? extends Serializable> layout,
- @PluginElement("Filter") final Filter filter) {
+ @PluginAttribute final long delay,
+ @PluginAttribute final long startupDelay,
+ @PluginAttribute final long shutdownDelay,
+ @PluginElement final Layout<? extends Serializable> layout,
+ @PluginElement final Filter filter) {
return new HangingAppender(name, delay, startupDelay, shutdownDelay);
}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/InMemoryAppenderTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/InMemoryAppenderTest.java
index 75d4929..21af139 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/InMemoryAppenderTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/InMemoryAppenderTest.java
@@ -37,25 +37,25 @@
public void testAppender() {
final Layout<String> layout = PatternLayout.createDefaultLayout();
final boolean writeHeader = true;
- final InMemoryAppender app = new InMemoryAppender("test", layout, null, false, writeHeader);
+ final InMemoryAppender app = new InMemoryAppender("test", layout, null, false, writeHeader, null);
final String expectedHeader = null;
assertMessage("Test", app, expectedHeader);
}
@Test
public void testHeaderRequested() {
- final PatternLayout layout = PatternLayout.newBuilder().withHeader("HEADERHEADER").build();
+ final PatternLayout layout = PatternLayout.newBuilder().setHeader("HEADERHEADER").build();
final boolean writeHeader = true;
- final InMemoryAppender app = new InMemoryAppender("test", layout, null, false, writeHeader);
+ final InMemoryAppender app = new InMemoryAppender("test", layout, null, false, writeHeader, null);
final String expectedHeader = "HEADERHEADER";
assertMessage("Test", app, expectedHeader);
}
@Test
public void testHeaderSuppressed() {
- final PatternLayout layout = PatternLayout.newBuilder().withHeader("HEADERHEADER").build();
+ final PatternLayout layout = PatternLayout.newBuilder().setHeader("HEADERHEADER").build();
final boolean writeHeader = false;
- final InMemoryAppender app = new InMemoryAppender("test", layout, null, false, writeHeader);
+ final InMemoryAppender app = new InMemoryAppender("test", layout, null, false, writeHeader, null);
final String expectedHeader = null;
assertMessage("Test", app, expectedHeader);
}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/MemoryMappedFileManagerTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/MemoryMappedFileManagerTest.java
index 84f7a4e..0e04699 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/MemoryMappedFileManagerTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/MemoryMappedFileManagerTest.java
@@ -88,4 +88,4 @@
final int expected = initialLength * 2;
assertEquals("appended, not overwritten", expected, file.length());
}
-}
\ No newline at end of file
+}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/OutputStreamAppenderTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/OutputStreamAppenderTest.java
index 8dcc97f..af194b8 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/OutputStreamAppenderTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/OutputStreamAppenderTest.java
@@ -1,109 +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;
-
-import java.io.BufferedOutputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.OutputStream;
-
-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.Assert;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TestName;
-
-/**
- * Tests {@link OutputStreamAppender}.
- */
-public class OutputStreamAppenderTest {
-
- private static final String TEST_MSG = "FOO ERROR";
-
- @Rule
- public TestName testName = new TestName();
-
- private String getName(final OutputStream out) {
- return out.getClass().getSimpleName() + "." + testName.getMethodName();
- }
-
- /**
- * 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
- public 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();
- Assert.assertTrue(actual, actual.contains(TEST_MSG));
- }
-
- @Test
- public 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();
- Assert.assertTrue(actual, actual.contains(TEST_MSG));
- }
-
- /**
- * 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
- public void testUpdatePatternWithFileAppender() {
- final LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
- final Configuration config = ctx.getConfiguration();
- // @formatter:off
- final Appender appender = FileAppender.newBuilder()
- .withFileName("target/" + getClass().getName() + ".log")
- .withAppend(false)
- .withName("File")
- .withIgnoreExceptions(false)
- .withBufferedIo(false)
- .withBufferSize(4000)
- .setConfiguration(config)
- .build();
- // @formatter:on
- appender.start();
- config.addAppender(appender);
- ConfigurationTestUtils.updateLoggers(appender, config);
- LogManager.getLogger().error("FOO MSG");
- }
-}
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 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.BufferedOutputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.OutputStream;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.core.Appender;
+import org.apache.logging.log4j.core.LoggerContext;
+import org.apache.logging.log4j.core.config.Configuration;
+import org.apache.logging.log4j.core.filter.NoMarkerFilter;
+import org.apache.logging.log4j.core.layout.PatternLayout;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestName;
+
+/**
+ * Tests {@link OutputStreamAppender}.
+ */
+public class OutputStreamAppenderTest {
+
+ private static final String TEST_MSG = "FOO ERROR";
+
+ @Rule
+ public TestName testName = new TestName();
+
+ private String getName(final OutputStream out) {
+ return out.getClass().getSimpleName() + "." + testName.getMethodName();
+ }
+
+ /**
+ * 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
+ public void testBuildFilter() {
+ final NoMarkerFilter filter = NoMarkerFilter.newBuilder().build();
+ // @formatter:off
+ final OutputStreamAppender.Builder builder = OutputStreamAppender.newBuilder()
+ .setName("test")
+ .setFilter(filter);
+ // @formatter:on
+ Assert.assertEquals(filter, builder.getFilter());
+ final OutputStreamAppender appender = builder.build();
+ Assert.assertEquals(filter, appender.getFilter());
+ }
+
+ @Test
+ public void testOutputStreamAppenderToBufferedOutputStream() {
+ final ByteArrayOutputStream out = new ByteArrayOutputStream();
+ final OutputStream os = new BufferedOutputStream(out);
+ final String name = getName(out);
+ final Logger logger = LogManager.getLogger(name);
+ addAppender(os, name);
+ logger.error(TEST_MSG);
+ final String actual = out.toString();
+ Assert.assertTrue(actual, actual.contains(TEST_MSG));
+ }
+
+ @Test
+ public 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();
+ Assert.assertTrue(actual, actual.contains(TEST_MSG));
+ }
+
+ /**
+ * 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
+ public 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");
+ }
+}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/OutputStreamManagerTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/OutputStreamManagerTest.java
index 884d6b8..e13e075 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/OutputStreamManagerTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/OutputStreamManagerTest.java
@@ -55,4 +55,4 @@
data.getThrowable().toString());
}
-}
\ No newline at end of file
+}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/ReconfigureAppenderTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/ReconfigureAppenderTest.java
new file mode 100644
index 0000000..01c5869
--- /dev/null
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/ReconfigureAppenderTest.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;
+
+import java.lang.reflect.Field;
+import java.util.Map;
+
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.LogManager;
+
+import org.apache.logging.log4j.core.Logger;
+import org.apache.logging.log4j.core.LoggerContext;
+import org.apache.logging.log4j.core.appender.rolling.DirectWriteRolloverStrategy;
+import org.apache.logging.log4j.core.appender.rolling.SizeBasedTriggeringPolicy;
+import org.apache.logging.log4j.core.config.Configurator;
+import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilder;
+import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilderFactory;
+import org.apache.logging.log4j.core.config.builder.impl.BuiltConfiguration;
+import org.apache.logging.log4j.core.layout.PatternLayout;
+import org.apache.logging.log4j.plugins.util.Builder;
+import org.junit.Test;
+
+/**
+ * Class Description goes here.
+ * Created by rgoers on 2019-02-02
+ */
+public class ReconfigureAppenderTest {
+ private RollingFileAppender appender;
+
+ @Test
+ public void addAndRemoveAppenderTest()
+ {
+ // this will create a rolling file appender and add it to the logger
+ // of this class. The file manager is created for the first time.
+ // see AbstractManager.getManager(...).
+ this.createAndAddAppender();
+
+ // let's write something to the logger to ensure the output stream is opened.
+ // We expect this call to create a a new output stream (which is does).
+ // see OutputStreamManager.writeToDestination(...).
+ Logger logger = (Logger)LogManager.getLogger(this.getClass());
+ logger.info("test message 1");
+
+ // this will close the rolling file appender and remove it from the logger
+ // of this class. We expect the file output stream to be closed (which is it)
+ // however the FileManager instance is kept in AbstractManager.MAP. This means that
+ // when we create a new rolling file appender with the DirectWriteRolloverStrategy
+ // this OLD file manager will be retrieved from the map (since it has the SAME file pattern)
+ // and this is a problem as the output stream on that file manager is CLOSED. The problem
+ // here is that we attempt to remove a file manager call NULL instead of FILE PATTERN
+ this.removeAppender();
+
+ // create a new rolling file appender for this logger. We expect this to create a new file
+ // manager as the old one should have been removed. Since the instance of this file manager
+ // is still in AbstractManager.MAP, it is returned and assigned to our new rolling file
+ // appender instance. The problem here is that the file manager is create with the name
+ // FILE PATTERN and that its output stream is closed.
+ this.createAndAddAppender();
+
+ // try and log something. This will not be logged anywhere. An exception will be thrown:
+ // Caused by: java.io.IOException: Stream Closed
+ logger.info("test message 2");
+
+ // remove the appender as before.
+ this.removeAppender();
+
+ // this method will use reflection to go and remove the instance of FileManager from the AbstractManager.MAP
+ // ourselves. This means that the rolling file appender has been stopped (previous method) AND its
+ // manager has been removed.
+ this.removeManagerUsingReflection();
+
+ // now that the instance of FileManager is not present in MAP, creating the appender will actually
+ // create a new rolling file manager, and put this in the map (keyed on file pattern again).
+ this.createAndAddAppender();
+
+ // because we have a new instance of file manager, this will create a new output stream. We can verify
+ // this by looking inside the filepattern.1.log file inside the working directory, and noticing that
+ // we have 'test message 1' followed by 'test message 3'. 'test message 2' is missing because we attempted
+ // to write while the output stream was closed.
+ logger.info("test message 3");
+
+ // possible fixes:
+ // 1) create the RollingFileManager and set it's name to FILE PATTERN when using DirectWriteRolloverStrategy
+ // 2) when stopping the appender (and thus the manager), remove on FILE PATTERN if DirectWriteRolloverStrategy
+ // 3) on OutputStreamManager.getOutputStream(), determine if the output stream is closed, and if it is create
+ // a new one. Note that this isn't really desirable as the only fix as if the file pattern had to change
+ // an instance of file manager would still exist in MAP, causing a resource leak.
+
+ // now the obvious problem here is that if multiple file appenders use the same rolling file manager. We may run
+ // into a case where the file manager is removed and the output stream is closed, and the remaining appenders
+ // may not work correctly. I'm not sure of the use case in this scenario, and if people actually do this
+ // but based on the code it would be possible. I have also not tested this scenario out as it is not the
+ // scenario we would ever use, but it should be considered while fixing this issue.
+ }
+ private void removeManagerUsingReflection()
+ {
+ try
+ {
+ Field field = AbstractManager.class.getDeclaredField("MAP");
+ field.setAccessible(true);
+
+ // Retrieve the map itself.
+ Map<String, AbstractManager> map =
+ (Map<String, AbstractManager>)field.get(null);
+
+ // Remove the file manager keyed on file pattern.
+ map.remove(appender.getFilePattern());
+ }
+ catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ private void removeAppender()
+ {
+ Logger logger = (Logger)LogManager.getLogger(this.getClass());
+
+ // This call attempts to remove the file manager, but uses the name of the appender
+ // (NULL in this case) instead of PATTERN.
+ // see AbstractManager.stop(...).
+ appender.stop();
+ logger.removeAppender(appender);
+ }
+
+ private void createAndAddAppender()
+ {
+ ConfigurationBuilder<BuiltConfiguration> 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 the
+ // appenders on the logger.
+ config_builder.add(config_builder.newRootLogger(Level.INFO));
+
+ // Initialise the logger context.
+ LoggerContext logger_context =
+ Configurator.initialize(config_builder.build());
+
+ // Retrieve the logger.
+ Logger logger = (Logger) LogManager.getLogger(this.getClass());
+
+ Builder pattern_builder = PatternLayout.newBuilder().setPattern(
+ "[%d{dd-MM-yy HH:mm:ss}] %p %m %throwable %n");
+
+ PatternLayout pattern_layout = (PatternLayout) pattern_builder.build();
+
+ appender = RollingFileAppender
+ .newBuilder()
+ .setLayout(pattern_layout)
+ .setName("rollingfileappender")
+ .setFilePattern("target/filepattern.%i.log")
+ .setPolicy(SizeBasedTriggeringPolicy.createPolicy("5 MB"))
+ .setAppend(true)
+ .setStrategy(
+ DirectWriteRolloverStrategy
+ .newBuilder()
+ .setConfig(logger_context.getConfiguration())
+ .setMaxFiles("5")
+ .build())
+ .setConfiguration(logger_context.getConfiguration())
+ .build();
+
+ appender.start();
+
+ logger.addAppender(appender);
+ }
+}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/ScriptAppenderSelectorTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/ScriptAppenderSelectorTest.java
index a1fe45c..62a29b6 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/ScriptAppenderSelectorTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/ScriptAppenderSelectorTest.java
@@ -16,12 +16,9 @@
*/
package org.apache.logging.log4j.core.appender;
-import java.util.List;
-
import org.apache.logging.log4j.Marker;
import org.apache.logging.log4j.MarkerManager;
import org.apache.logging.log4j.categories.Scripts;
-import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.Logger;
import org.apache.logging.log4j.junit.LoggerContextRule;
import org.apache.logging.log4j.test.appender.ListAppender;
@@ -33,9 +30,6 @@
import static org.junit.Assert.*;
-/**
- *
- */
@RunWith(Parameterized.class)
@Category(Scripts.Groovy.class)
public class ScriptAppenderSelectorTest {
@@ -66,13 +60,11 @@
final Logger logger = loggerContextRule.getLogger(ScriptAppenderSelectorTest.class);
logger.error("Hello");
final ListAppender listAppender = getListAppender();
- final List<LogEvent> list = listAppender.getEvents();
- assertNotNull("No events generated", list);
- assertTrue("Incorrect number of events. Expected 1, got " + list.size(), list.size() == 1);
+ assertEquals("Incorrect number of events", 1, listAppender.getEvents().size());
logger.error("World");
- assertTrue("Incorrect number of events. Expected 2, got " + list.size(), list.size() == 2);
+ assertEquals("Incorrect number of events", 2, listAppender.getEvents().size());
logger.error(marker, "DEADBEEF");
- assertTrue("Incorrect number of events. Expected 3, got " + list.size(), list.size() == 3);
+ assertEquals("Incorrect number of events", 3, listAppender.getEvents().size());
}
@Test(expected = AssertionError.class)
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/SyslogAppenderCustomLayoutTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/SyslogAppenderCustomLayoutTest.java
index 716223e..68b2f00 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/SyslogAppenderCustomLayoutTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/SyslogAppenderCustomLayoutTest.java
@@ -31,7 +31,7 @@
@Override
protected Builder newSyslogAppenderBuilder(final String protocol, final String format, final boolean newLine) {
final Builder builder = super.newSyslogAppenderBuilder(protocol, format, newLine);
- builder.withLayout(SyslogLayout.newBuilder().setFacility(Facility.LOCAL3).setIncludeNewLine(true).build());
+ builder.setLayout(SyslogLayout.newBuilder().setFacility(Facility.LOCAL3).setIncludeNewLine(true).build());
return builder;
}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/SyslogAppenderTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/SyslogAppenderTest.java
index 741a51f..80cdfc9 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/SyslogAppenderTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/SyslogAppenderTest.java
@@ -116,11 +116,11 @@
protected Builder newSyslogAppenderBuilder(final String protocol, final String format, final boolean newLine) {
// @formatter:off
return SyslogAppender.newSyslogAppenderBuilder()
- .withPort(PORTNUM)
- .withProtocol(EnglishEnums.valueOf(Protocol.class, protocol))
- .withReconnectDelayMillis(-1)
- .withName("TestApp")
- .withIgnoreExceptions(false)
+ .setPort(PORTNUM)
+ .setProtocol(EnglishEnums.valueOf(Protocol.class, protocol))
+ .setReconnectDelayMillis(-1)
+ .setName("TestApp")
+ .setIgnoreExceptions(false)
.setId("Audit")
.setEnterpriseNumber(18060)
.setMdcId("RequestContext")
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/TlsSyslogAppenderTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/TlsSyslogAppenderTest.java
index ae567a0..57e69db 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/TlsSyslogAppenderTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/TlsSyslogAppenderTest.java
@@ -23,6 +23,7 @@
import javax.net.ssl.SSLServerSocketFactory;
import org.apache.logging.log4j.core.net.Facility;
+import org.apache.logging.log4j.core.net.Protocol;
import org.apache.logging.log4j.core.net.mock.MockSyslogServerFactory;
import org.apache.logging.log4j.core.net.ssl.KeyStoreConfiguration;
import org.apache.logging.log4j.core.net.ssl.SslConfiguration;
@@ -31,6 +32,7 @@
import org.apache.logging.log4j.core.net.ssl.TlsSyslogMessageFormat;
import org.apache.logging.log4j.core.net.ssl.TlsSyslogTestUtil;
import org.apache.logging.log4j.core.net.ssl.TrustStoreConfiguration;
+import org.apache.logging.log4j.util.EnglishEnums;
import org.junit.Test;
public class TlsSyslogAppenderTest extends SyslogAppenderTest {
@@ -92,10 +94,43 @@
} else {
format = "RFC5424";
}
+ final SslConfiguration sslConfiguration1 = sslConfiguration;
+ final boolean newLine = includeNewLine;
+ final String format1 = format;
- return SyslogAppender.createAppender("localhost", PORTNUM, "SSL", sslConfiguration, 0, -1, true, "Test", true,
- false, Facility.LOCAL0, "Audit", 18060, true, "RequestContext", null, null, includeNewLine, null,
- "TestApp", "Test", null, "ipAddress,loginId", null, format, null, null, null, null, null, false);
+ // @formatter:off
+ return SyslogAppender.newSyslogAppenderBuilder()
+ .setHost("localhost")
+ .setPort(PORTNUM)
+ .setProtocol(EnglishEnums.valueOf(Protocol.class, "SSL"))
+ .setSslConfiguration(sslConfiguration1)
+ .setConnectTimeoutMillis(0)
+ .setReconnectDelayMillis(-1)
+ .setImmediateFail(true)
+ .setName("TestApp")
+ .setImmediateFlush(true)
+ .setIgnoreExceptions(false).setFilter(null)
+ .setConfiguration(null)
+ .setAdvertise(false)
+ .setFacility(Facility.LOCAL0)
+ .setId("Audit")
+ .setEnterpriseNumber(18060)
+ .setIncludeMdc(true)
+ .setMdcId("RequestContext")
+ .setMdcPrefix(null)
+ .setEventPrefix(null)
+ .setNewLine(newLine)
+ .setAppName("TestApp")
+ .setMsgId("Test")
+ .setExcludes(null)
+ .setIncludeMdc(true)
+ .setRequired(null)
+ .setFormat(format1)
+ .setCharsetName(null)
+ .setExceptionPattern(null)
+ .setLoggerFields(null)
+ .build();
+ // @formatter:on
}
private void initTlsTestEnvironment(final int numberOfMessages, final TlsSyslogMessageFormat messageFormat) throws IOException {
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/TlsSyslogFrameTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/TlsSyslogFrameTest.java
index 887ba99..32ad5a9 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/TlsSyslogFrameTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/TlsSyslogFrameTest.java
@@ -48,4 +48,4 @@
final String expected = Integer.toString(length) + Chars.SPACE + TEST_MESSAGE;
Assert.assertEquals(expected, frame.toString());
}
-}
\ No newline at end of file
+}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/WriterAppenderTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/WriterAppenderTest.java
index abde0d6..ac29fdf 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/WriterAppenderTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/WriterAppenderTest.java
@@ -100,4 +100,9 @@
test(new StringWriter());
}
+ @Test
+ public void testBuilder() {
+ // This should compile
+ WriterAppender.newBuilder().setTarget(new StringWriter()).setName("testWriterAppender").build();
+ }
}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/db/AbstractDatabaseAppenderTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/db/AbstractDatabaseAppenderTest.java
index 2460cf5..6b5d7ef 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/db/AbstractDatabaseAppenderTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/db/AbstractDatabaseAppenderTest.java
@@ -31,14 +31,29 @@
import org.apache.logging.log4j.core.Filter;
import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.config.Property;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
-import org.mockito.runners.MockitoJUnitRunner;
+import org.mockito.junit.MockitoJUnitRunner;
@RunWith(MockitoJUnitRunner.class)
public class AbstractDatabaseAppenderTest {
+ private static class LocalAbstractDatabaseAppender extends AbstractDatabaseAppender<LocalAbstractDatabaseManager> {
+
+ public LocalAbstractDatabaseAppender(final String name, final Filter filter, final boolean ignoreExceptions,
+ final LocalAbstractDatabaseManager manager) {
+ super(name, filter, null, ignoreExceptions, Property.EMPTY_ARRAY, manager);
+ }
+ }
+ private static abstract class LocalAbstractDatabaseManager extends AbstractDatabaseManager {
+ public LocalAbstractDatabaseManager(final String name, final int bufferSize) {
+ super(name, bufferSize);
+ }
+ }
+
private LocalAbstractDatabaseAppender appender;
+
@Mock
private LocalAbstractDatabaseManager manager;
@@ -47,6 +62,25 @@
}
@Test
+ public void testAppend() {
+ setUp("name");
+ given(manager.commitAndClose()).willReturn(true);
+
+ final LogEvent event1 = mock(LogEvent.class);
+ final LogEvent event2 = mock(LogEvent.class);
+
+ appender.append(event1);
+ then(manager).should().isBuffered();
+ then(manager).should().writeThrough(same(event1), (Serializable) isNull());
+ reset(manager);
+
+ appender.append(event2);
+ then(manager).should().isBuffered();
+ then(manager).should().writeThrough(same(event2), (Serializable) isNull());
+ reset(manager);
+ }
+
+ @Test
public void testNameAndGetLayout01() {
setUp("testName01");
@@ -63,17 +97,6 @@
}
@Test
- public void testStartAndStop() throws Exception {
- setUp("name");
-
- appender.start();
- then(manager).should().startupInternal();
-
- appender.stop();
- then(manager).should().stop(0L, TimeUnit.MILLISECONDS);
- }
-
- @Test
public void testReplaceManager() throws Exception {
setUp("name");
@@ -90,37 +113,13 @@
}
@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.start();
+ then(manager).should().startupInternal();
- 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<LocalAbstractDatabaseManager> {
-
- public LocalAbstractDatabaseAppender(final String name, final Filter filter, final boolean exceptionSuppressed,
- final LocalAbstractDatabaseManager manager) {
- super(name, filter, exceptionSuppressed, manager);
- }
+ appender.stop();
+ then(manager).should().stop(0L, TimeUnit.MILLISECONDS);
}
}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/db/AbstractDatabaseManagerTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/db/AbstractDatabaseManagerTest.java
index 932724d..dfb498e 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/db/AbstractDatabaseManagerTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/db/AbstractDatabaseManagerTest.java
@@ -25,6 +25,8 @@
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import java.io.Serializable;
@@ -33,6 +35,40 @@
import org.junit.Test;
public 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() throws Exception {
+ return true;
+ }
+
+ @Override
+ protected void startupInternal() throws Exception {
+ // noop
+ }
+
+ @Override
+ protected void writeInternal(final LogEvent event, final Serializable serializable) {
+ // noop
+ }
+
+ }
+
private AbstractDatabaseManager manager;
public void setUp(final String name, final int buffer) {
@@ -40,6 +76,160 @@
}
@Test
+ public 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), (Serializable) isNull());
+ then(manager).should().connectAndStart();
+ then(manager).should().isBuffered();
+ then(manager).should().writeInternal(same(event1), (Serializable) isNull());
+ then(manager).should().commitAndClose();
+ then(manager).shouldHaveNoMoreInteractions();
+ reset(manager);
+
+ manager.write(event2, null);
+ then(manager).should().writeThrough(same(event2), (Serializable) isNull());
+ then(manager).should().connectAndStart();
+ then(manager).should().isBuffered();
+ then(manager).should().writeInternal(same(event2), (Serializable) isNull());
+ then(manager).should().commitAndClose();
+ then(manager).shouldHaveNoMoreInteractions();
+ reset(manager);
+
+ manager.write(event3, null);
+ then(manager).should().writeThrough(same(event3), (Serializable) isNull());
+ then(manager).should().connectAndStart();
+ then(manager).should().isBuffered();
+ then(manager).should().writeInternal(same(event3), (Serializable) isNull());
+ then(manager).should().commitAndClose();
+ then(manager).shouldHaveNoMoreInteractions();
+ reset(manager);
+ }
+
+ @Test
+ public 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), (Serializable) isNull());
+ then(manager).should().buffer(event1);
+ then(manager).should().writeInternal(same(event2copy), (Serializable) isNull());
+ then(manager).should().buffer(event2);
+ then(manager).should().writeInternal(same(event3copy), (Serializable) isNull());
+ then(manager).should().buffer(event3);
+ then(manager).should().writeInternal(same(event4copy), (Serializable) isNull());
+ then(manager).should().buffer(event4);
+ then(manager).should().commitAndClose();
+ then(manager).shouldHaveNoMoreInteractions();
+ }
+
+ @Test
+ public 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), (Serializable) isNull());
+ then(manager).should().buffer(event1);
+ then(manager).should().writeInternal(same(event2copy), (Serializable) isNull());
+ then(manager).should().buffer(event2);
+ then(manager).should().writeInternal(same(event3copy), (Serializable) isNull());
+ then(manager).should().buffer(event3);
+ then(manager).should().commitAndClose();
+ then(manager).shouldHaveNoMoreInteractions();
+ }
+
+ @Test
+ public 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), (Serializable) isNull());
+ then(manager).should().buffer(event1);
+ then(manager).should().writeInternal(same(event2copy), (Serializable) isNull());
+ then(manager).should().buffer(event2);
+ then(manager).should().writeInternal(same(event3copy), (Serializable) isNull());
+ then(manager).should().buffer(event3);
+ then(manager).should().commitAndClose();
+ then(manager).should().shutdownInternal();
+ then(manager).shouldHaveNoMoreInteractions();
+ }
+
+ @Test
public void testStartupShutdown01() throws Exception {
setUp("testName01", 0);
@@ -84,175 +274,4 @@
assertEquals("The string is not correct.", "bufferSize=12, anotherKey02=coolValue02", manager.toString());
}
-
- @Test
- public 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().connectAndStart();
- then(manager).should().writeInternal(same(event1), (Serializable) isNull());
- then(manager).should().commitAndClose();
- reset(manager);
-
- manager.write(event2, null);
- then(manager).should().connectAndStart();
- then(manager).should().writeInternal(same(event2), (Serializable) isNull());
- then(manager).should().commitAndClose();
- reset(manager);
-
- manager.write(event3, null);
- then(manager).should().connectAndStart();
- then(manager).should().writeInternal(same(event3), (Serializable) isNull());
- then(manager).should().commitAndClose();
- then(manager).shouldHaveNoMoreInteractions();
- }
-
- @Test
- public 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();
- then(manager).should().writeInternal(same(event1copy), (Serializable) isNull());
- then(manager).should().writeInternal(same(event2copy), (Serializable) isNull());
- then(manager).should().writeInternal(same(event3copy), (Serializable) isNull());
- then(manager).should().writeInternal(same(event4copy), (Serializable) isNull());
- then(manager).should().commitAndClose();
- then(manager).shouldHaveNoMoreInteractions();
- }
-
- @Test
- public 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();
- then(manager).should().writeInternal(same(event1copy), (Serializable) isNull());
- then(manager).should().writeInternal(same(event2copy), (Serializable) isNull());
- then(manager).should().writeInternal(same(event3copy), (Serializable) isNull());
- then(manager).should().commitAndClose();
- then(manager).shouldHaveNoMoreInteractions();
- }
-
- @Test
- public 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();
- then(manager).should().writeInternal(same(event1copy), (Serializable) isNull());
- then(manager).should().writeInternal(same(event2copy), (Serializable) isNull());
- then(manager).should().writeInternal(same(event3copy), (Serializable) isNull());
- then(manager).should().commitAndClose();
- then(manager).should().shutdownInternal();
- then(manager).shouldHaveNoMoreInteractions();
- }
-
- // this stub is provided because mocking constructors is hard
- private static class StubDatabaseManager extends AbstractDatabaseManager {
-
- protected StubDatabaseManager(final String name, final int bufferSize) {
- super(name, bufferSize);
- }
-
- @Override
- protected void startupInternal() throws Exception {
- // noop
- }
-
- @Override
- protected boolean shutdownInternal() throws Exception {
- return true;
- }
-
- @Override
- protected void connectAndStart() {
- // noop
- }
-
- @Deprecated
- @Override
- protected void writeInternal(final LogEvent event) {
- // noop
- }
-
- @Override
- protected void writeInternal(LogEvent event, Serializable serializable) {
- // noop
- }
- @Override
- protected boolean commitAndClose() {
- return true;
- }
-
- }
}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/nosql/NoSqlAppenderTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/nosql/NoSqlAppenderTest.java
index 6ab9419..24e01ac 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/nosql/NoSqlAppenderTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/nosql/NoSqlAppenderTest.java
@@ -31,14 +31,14 @@
@Test
public void testNoProvider() {
- final NoSqlAppender appender = NoSqlAppender.createAppender("myName01", null, null, null, null);
+ final NoSqlAppender appender = NoSqlAppender.newBuilder().setName("myName01").build();
assertNull("The appender should be null.", appender);
}
@Test
public void testProvider() {
- final NoSqlAppender appender = NoSqlAppender.createAppender("myName01", null, null, null, provider);
+ final NoSqlAppender appender = NoSqlAppender.newBuilder().setName("myName01").setProvider(provider).build();
assertNotNull("The appender should not be null.", appender);
assertEquals("The toString value is not correct.",
@@ -50,12 +50,14 @@
@Test
public void testProviderBuffer() {
- final NoSqlAppender appender = NoSqlAppender.createAppender("anotherName02", null, null, "25", provider);
+ final NoSqlAppender appender = NoSqlAppender.newBuilder().setName("anotherName02").setProvider(provider)
+ .setBufferSize(25).build();
assertNotNull("The appender should not be null.", appender);
assertEquals("The toString value is not correct.",
"anotherName02{ manager=noSqlManager{ description=anotherName02, bufferSize=25, provider=" + provider
- + " } }", appender.toString());
+ + " } }",
+ appender.toString());
appender.stop();
}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rewrite/MapRewritePolicyTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rewrite/MapRewritePolicyTest.java
index 3247f82..f311007 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rewrite/MapRewritePolicyTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rewrite/MapRewritePolicyTest.java
@@ -172,7 +172,6 @@
orig.getThrown() == null ? null : orig.getThrownProxy().getExtendedStackTrace(),
changed.getThrown() == null ? null : changed.getThrownProxy().getExtendedStackTrace()
);
- assertEquals("ContextMap changed", orig.getContextMap(), changed.getContextMap());
assertEquals("ContextData changed", orig.getContextData(), changed.getContextData());
assertEquals("ContextStack changed", orig.getContextStack(), changed.getContextStack());
assertEquals("ThreadName changed", orig.getThreadName(), changed.getThreadName());
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rewrite/RewriteAppenderTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rewrite/RewriteAppenderTest.java
index fb87ad9..73082e9 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rewrite/RewriteAppenderTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rewrite/RewriteAppenderTest.java
@@ -55,8 +55,12 @@
@After
public void tearDown() throws Exception {
- app.clear();
- app2.clear();
+ if (app != null) {
+ app.clear();
+ }
+ if (app2 != null) {
+ app2.clear();
+ }
}
@Test
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rewrite/TestRewritePolicy.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rewrite/TestRewritePolicy.java
index 4380f98..3013a3f 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rewrite/TestRewritePolicy.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rewrite/TestRewritePolicy.java
@@ -18,8 +18,8 @@
import org.apache.logging.log4j.core.Core;
import org.apache.logging.log4j.core.LogEvent;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginFactory;
import org.apache.logging.log4j.core.impl.Log4jLogEvent;
/**
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/CronTriggeringPolicyTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/CronTriggeringPolicyTest.java
index f6bf296..04085ae 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/CronTriggeringPolicyTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/CronTriggeringPolicyTest.java
@@ -29,12 +29,12 @@
public class CronTriggeringPolicyTest {
private static final String CRON_EXPRESSION = "0 0 0 * * ?";
-
+
private NullConfiguration configuration;
- // TODO Need a CleanRegexFiles("testcmd.\\.log\\..*");
- //@Rule
- //public CleanFiles cleanFiles = new CleanFiles("testcmd1.log", "testcmd2.log", "testcmd3.log");
+ // TODO Need a CleanRegexFiles("testcmd.\\.log\\..*");
+ // @Rule
+ // public CleanFiles cleanFiles = new CleanFiles("testcmd1.log", "testcmd2.log", "testcmd3.log");
@Before
public void before() {
@@ -46,17 +46,18 @@
}
private DefaultRolloverStrategy createStrategy() {
- return DefaultRolloverStrategy.createStrategy("7", "1", "max", null, null, false, configuration);
+ return DefaultRolloverStrategy.newBuilder().setMax("7").setMin("1").setFileIndex("max")
+ .setStopCustomActionsOnError(false).setConfig(configuration).build();
}
private void testBuilder() {
// @formatter:off
final RollingFileAppender raf = RollingFileAppender.newBuilder()
- .withName("test1")
- .withFileName("target/testcmd1.log")
- .withFilePattern("target/testcmd1.log.%d{yyyy-MM-dd}")
- .withPolicy(createPolicy())
- .withStrategy(createStrategy())
+ .setName("test1")
+ .setFileName("target/testcmd1.log")
+ .setFilePattern("target/testcmd1.log.%d{yyyy-MM-dd}")
+ .setPolicy(createPolicy())
+ .setStrategy(createStrategy())
.setConfiguration(configuration)
.build();
// @formatter:on
@@ -88,17 +89,16 @@
public void testRollingRandomAccessFileAppender() {
// @formatter:off
RollingRandomAccessFileAppender.newBuilder()
- .withName("test2")
- .withFileName("target/testcmd2.log")
- .withFilePattern("target/testcmd2.log.%d{yyyy-MM-dd}")
- .withPolicy(createPolicy())
- .withStrategy(createStrategy())
+ .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.
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/OnStartupTriggeringPolicyTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/OnStartupTriggeringPolicyTest.java
index 7ef9d92..28353c3 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/OnStartupTriggeringPolicyTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/OnStartupTriggeringPolicyTest.java
@@ -26,6 +26,8 @@
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.util.Arrays;
import org.apache.logging.log4j.core.config.Configuration;
@@ -33,14 +35,13 @@
import org.apache.logging.log4j.core.layout.PatternLayout;
import org.apache.logging.log4j.core.time.internal.format.FastDateFormat;
import org.apache.logging.log4j.junit.CleanFolders;
-import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
/**
* Tests {@link OnStartupTriggeringPolicy}.
*/
-//@Ignore
+// @Ignore
public class OnStartupTriggeringPolicyTest {
private static final String TARGET_FOLDER = "target/rollOnStartup";
@@ -52,10 +53,12 @@
private static final FastDateFormat formatter = FastDateFormat.getInstance("MM-dd-yyyy");
@Rule
- public CleanFolders rule = new CleanFolders("target/rollOnStartup");
+ public CleanFolders rule = new CleanFolders(TARGET_FOLDER);
@Test
public void testPolicy() throws Exception {
+ //System.setProperty("log4j2.debug", "true");
+ //System.setProperty("log4j2.StatusLogger.level", "trace");
final Configuration configuration = new DefaultConfiguration();
final Path target = Paths.get(TARGET_FILE);
target.toFile().getParentFile().mkdirs();
@@ -71,14 +74,16 @@
assertTrue(size > 0);
assertEquals(copied, size);
- Assert.assertTrue(target.toFile().setLastModified(timeStamp));
- final PatternLayout layout = PatternLayout.newBuilder().withPattern("%msg").withConfiguration(configuration)
+ final FileTime fileTime = FileTime.fromMillis(timeStamp);
+ final BasicFileAttributeView attrs = Files.getFileAttributeView(target, BasicFileAttributeView.class);
+ attrs.setTimes(fileTime, fileTime, fileTime);
+ final PatternLayout layout = PatternLayout.newBuilder().setPattern("%msg").setConfiguration(configuration)
.build();
- final RolloverStrategy strategy = DefaultRolloverStrategy.createStrategy(null, null, null, "0", null, true,
- configuration);
+ 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_FILE, TARGET_PATTERN, true, false,
- policy, strategy, null, layout, 8192, true, false, null, null, null, configuration)) {
+ try (final RollingFileManager manager = RollingFileManager.getFileManager(TARGET_FILE, TARGET_PATTERN, true,
+ false, policy, strategy, null, layout, 8192, true, false, null, null, null, configuration)) {
manager.initialize();
final String files = Arrays.toString(new File(TARGET_FOLDER).listFiles());
assertTrue(target.toString() + ", files = " + files, Files.exists(target));
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderCountTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderCountTest.java
new file mode 100644
index 0000000..2ce95c9
--- /dev/null
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderCountTest.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.appender.rolling;
+
+import org.apache.commons.lang3.RandomStringUtils;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.junit.LoggerContextRule;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.io.File;
+import java.nio.file.DirectoryStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardCopyOption;
+import java.util.Objects;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Validate rolling with a file pattern that contains leading zeros for the increment.
+ */
+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() throws Exception {
+ this.logger = this.loggerContextRule.getLogger("LogTest");
+ }
+
+ @BeforeClass
+ public static void beforeClass() throws Exception {
+ if (Files.exists(Paths.get(DIR))) {
+ try (DirectoryStream<Path> directoryStream = Files.newDirectoryStream(Paths.get(DIR))) {
+ for (final Path path : directoryStream) {
+ Files.delete(path);
+ }
+ Files.delete(Paths.get(DIR));
+ }
+ }
+ File dir = new File(DIR);
+ if (!dir.exists()) {
+ Files.createDirectory(new File(DIR).toPath());
+ }
+ 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 {
+ int count = Objects.requireNonNull(new File(DIR).listFiles()).length;
+ assertEquals("Expected 16 files, got " + count, 17, count);
+ try (DirectoryStream<Path> 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/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderCronAndSizeTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderCronAndSizeTest.java
new file mode 100644
index 0000000..652389a
--- /dev/null
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderCronAndSizeTest.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.rolling;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.Random;
+
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.junit.LoggerContextRule;
+import org.junit.Before;
+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.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * 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() throws Exception {
+ this.logger = loggerContextRule.getLogger(RollingAppenderCronAndSizeTest.class.getName());
+ }
+
+ @Test
+ public void testAppender() throws Exception {
+ Random rand = new Random();
+ for (int j=0; j < 100; ++j) {
+ int count = rand.nextInt(100);
+ for (int i = 0; i < count; ++i) {
+ logger.debug("This is test message number " + i);
+ }
+ 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"))))));
+ int found = 0;
+ int fileCounter = 0;
+ String previous = "";
+ for (final File file: files) {
+ final String actual = file.getName();
+ StringBuilder padding = new StringBuilder();
+ 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/RollingAppenderDirectWrite1906Test.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderDirectWrite1906Test.java
new file mode 100644
index 0000000..a055fc2
--- /dev/null
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderDirectWrite1906Test.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.appender.rolling;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.List;
+
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.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;
+
+import static org.apache.logging.log4j.hamcrest.Descriptors.that;
+import static org.apache.logging.log4j.hamcrest.FileMatchers.hasName;
+import static org.hamcrest.Matchers.endsWith;
+import static org.hamcrest.Matchers.hasItemInArray;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertEquals;
+
+/**
+ *
+ */
+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);
+
+ @BeforeClass
+ public static void setupClass() throws Exception {
+ StatusLogger.getLogger().registerListener(new NoopStatusListener());
+ }
+
+ private Logger logger;
+
+ @Before
+ public void setUp() throws Exception {
+ this.logger = loggerContextRule.getLogger(RollingAppenderDirectWrite1906Test.class.getName());
+ }
+
+ @Test
+ public void testAppender() throws Exception {
+ 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 (File file: files) {
+ String actual = file.getName();
+ BufferedReader reader = new BufferedReader(new FileReader(file));
+ String line;
+ while ((line = reader.readLine()) != null) {
+ assertNotNull("No log event in file " + actual, line);
+ String[] parts = line.split((" "));
+ 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(String expected, String actual) {
+ final List<StatusData> 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(StatusData data) {
+
+ }
+
+ @Override
+ public Level getStatusLevel() {
+ return Level.TRACE;
+ }
+
+ @Override
+ public void close() throws IOException {
+
+ }
+ }
+}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderDirectWriteStartupSizeTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderDirectWriteStartupSizeTest.java
new file mode 100644
index 0000000..0e997ef
--- /dev/null
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderDirectWriteStartupSizeTest.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.logging.log4j.core.appender.rolling;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import org.apache.logging.log4j.core.appender.RollingFileAppender;
+import org.apache.logging.log4j.junit.CleanFolders;
+import org.apache.logging.log4j.junit.LoggerContextRule;
+import org.junit.Assert;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+
+/**
+ * Test LOG4J2-2485.
+ */
+public class RollingAppenderDirectWriteStartupSizeTest {
+
+ private static final String CONFIG = "log4j-rolling-direct-startup-size.xml";
+
+ private static final String DIR = "target/rolling-direct-startup-size";
+
+ private static final String FILE = "size-test.log";
+
+ private static final String MESSAGE = "test message";
+
+ @Rule
+ public LoggerContextRule loggerContextRule = LoggerContextRule
+ .createShutdownTimeoutLoggerContextRule(CONFIG);
+
+ @Rule
+ public CleanFolders cleanFolders = new CleanFolders(false, true, 10, DIR);
+
+ @BeforeClass
+ public static void beforeClass() throws Exception {
+ Path log = Paths.get(DIR, FILE);
+ if (Files.exists(log)) {
+ Files.delete(log);
+ }
+
+ Files.createDirectories(log.getParent());
+ Files.createFile(log);
+ Files.write(log, MESSAGE.getBytes());
+ }
+
+ @Test
+ public void testRollingFileAppenderWithReconfigure() throws Exception {
+ final RollingFileAppender rfAppender = loggerContextRule.getRequiredAppender("RollingFile",
+ RollingFileAppender.class);
+ final RollingFileManager manager = rfAppender.getManager();
+
+ Assert.assertNotNull(manager);
+ Assert.assertEquals("Existing file size not preserved on startup", MESSAGE.getBytes().length, manager.size);
+ }
+}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderDirectWriteTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderDirectWriteTest.java
index b2c3606..379f083 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderDirectWriteTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderDirectWriteTest.java
@@ -16,7 +16,12 @@
*/
package org.apache.logging.log4j.core.appender.rolling;
+import java.io.BufferedReader;
import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileReader;
+import java.io.InputStreamReader;
+import java.util.zip.GZIPInputStream;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.junit.LoggerContextRule;
@@ -53,7 +58,8 @@
@Test
public void testAppender() throws Exception {
- for (int i=0; i < 100; ++i) {
+ int count = 100;
+ for (int i=0; i < count; ++i) {
logger.debug("This is test message number " + i);
}
Thread.sleep(50);
@@ -62,5 +68,26 @@
final File[] files = dir.listFiles();
assertNotNull(files);
assertThat(files, hasItemInArray(that(hasName(that(endsWith(".gz"))))));
+ int found = 0;
+ for (File file: files) {
+ String actual = file.getName();
+ BufferedReader reader;
+ if (file.getName().endsWith(".gz")) {
+ reader = new BufferedReader(new InputStreamReader(new GZIPInputStream(new FileInputStream(file))));
+ } else {
+ reader = new BufferedReader(new FileReader(file));
+ }
+ String line;
+ while ((line = reader.readLine()) != null) {
+ assertNotNull("No log event in file " + actual, line);
+ String[] parts = line.split((" "));
+ String expected = "test1-" + parts[0];
+ assertTrue("Incorrect file name. Expected file prefix: " + expected + " Actual: " + actual,
+ actual.startsWith(expected));
+ ++found;
+ }
+ reader.close();
+ }
+ assertEquals("Incorrect number of events read. Expected " + count + ", Actual " + found, count, found);
}
}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderDirectWriteWithHtmlLayoutTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderDirectWriteWithHtmlLayoutTest.java
new file mode 100644
index 0000000..24799ed
--- /dev/null
+++ b/log4j-core/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 org.apache.logging.log4j.Logger;
+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.util.IOUtils;
+import org.apache.logging.log4j.junit.CleanFolders;
+import org.apache.logging.log4j.junit.LoggerContextRule;
+import org.apache.logging.log4j.message.SimpleMessage;
+import org.hamcrest.Matchers;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.RuleChain;
+
+import java.io.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.zip.GZIPInputStream;
+
+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.*;
+
+/**
+ * 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(boolean append) throws InterruptedException, IOException {
+ String prefix = "testHtml_" + (append ? "append_" : "noAppend_");
+ Configuration config = loggerContextRule.getConfiguration();
+ RollingFileAppender appender = RollingFileAppender.newBuilder()
+ .setName("RollingHtml")
+ .setFilePattern(DIR + "/" + prefix + "_-%d{MM-dd-yy-HH-mm}-%i.html")
+ .setPolicy(new SizeBasedTriggeringPolicy(500))
+ .setStrategy(DirectWriteRolloverStrategy.newBuilder()
+ .setConfig(config)
+ .build())
+ .setLayout(HtmlLayout.createDefaultLayout())
+ .setAppend(append)
+ .build();
+ boolean stopped = false;
+ try {
+ int count = 100;
+ for (int i = 0; i < count; ++i) {
+ appender.append(Log4jLogEvent.newBuilder()
+ .setMessage(new SimpleMessage("This is test message number " + i))
+ .build()
+ );
+ }
+ appender.getManager().flush();
+ appender.stop();
+ stopped = true;
+ Thread.sleep(50);
+ final File dir = new File(DIR);
+ assertTrue("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 (BufferedReader reader = new BufferedReader(new FileReader(file))) {
+ String data = IOUtils.toString(reader).trim();
+ // check that every file starts with the header
+ assertThat("header in file " + file, data, Matchers.startsWith("<!DOCTYPE"));
+ assertThat("footer in file " + file, data, Matchers.endsWith("</html>"));
+ 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/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderOnStartupTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderOnStartupTest.java
index aed2d19..5b5a683 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderOnStartupTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderOnStartupTest.java
@@ -16,12 +16,22 @@
*/
package org.apache.logging.log4j.core.appender.rolling;
+import java.io.File;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
+import java.nio.file.StandardCopyOption;
+import java.nio.file.attribute.BasicFileAttributeView;
+import java.nio.file.attribute.FileTime;
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
import java.util.Arrays;
import java.util.Collection;
+import java.util.List;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.junit.LoggerContextRule;
import org.junit.AfterClass;
@@ -37,27 +47,20 @@
/**
*
*/
-@RunWith(Parameterized.class)
public class RollingAppenderOnStartupTest {
+ private static final String SOURCE = "src/test/resources/__files";
private static final String DIR = "target/onStartup";
+ private static final String CONFIG = "log4j-test4.xml";
+ private static final String FILENAME = "onStartup.log";
private Logger logger;
- @Parameterized.Parameters(name = "{0} \u2192 {1}")
- public static Collection<Object[]> data() {
- return Arrays.asList(new Object[][] { //
- // @formatter:off
- {"log4j-test4.xml"},
- {"log4j-test4.xml"},});
- // @formatter:on
- }
-
@Rule
public LoggerContextRule loggerContextRule;
- public RollingAppenderOnStartupTest(final String configFile) {
- this.loggerContextRule = LoggerContextRule.createShutdownTimeoutLoggerContextRule(configFile);
+ public RollingAppenderOnStartupTest() {
+ this.loggerContextRule = LoggerContextRule.createShutdownTimeoutLoggerContextRule(CONFIG);
}
@Before
@@ -75,24 +78,30 @@
Files.delete(Paths.get(DIR));
}
}
+ Files.createDirectory(new File(DIR).toPath());
+ Path target = Paths.get(DIR, FILENAME);
+ Files.copy(Paths.get(SOURCE, FILENAME), target, StandardCopyOption.COPY_ATTRIBUTES);
+ FileTime newTime = FileTime.from(Instant.now().minus(1, ChronoUnit.DAYS));
+ Files.getFileAttributeView(target, BasicFileAttributeView.class).setTimes(newTime, newTime, newTime);
}
@AfterClass
public static void afterClass() throws Exception {
- long size = 0;
try (DirectoryStream<Path> directoryStream = Files.newDirectoryStream(Paths.get(DIR))) {
+ boolean rolled = false;
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);
+ if (!path.toFile().getName().endsWith(FILENAME)) {
+ rolled = true;
+ }
+ try (Stream<String> stream = Files.lines(path)) {
+ List<String> lines = stream.collect(Collectors.toList());
+ assertTrue("No header present for " + path.toFile().getName(), lines.get(0).startsWith("<!DOCTYPE HTML"));
}
Files.delete(path);
}
- Files.delete(Paths.get("target/onStartup"));
+ assertTrue("File did not roll", rolled);
}
+ Files.delete(Paths.get("target/onStartup"));
}
@Test
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderRestartTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderRestartTest.java
new file mode 100644
index 0000000..80474b6
--- /dev/null
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderRestartTest.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.appender.rolling;
+
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.junit.LoggerContextRule;
+import org.hamcrest.Matcher;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.RuleChain;
+
+import java.io.File;
+import java.nio.file.Files;
+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 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;
+
+/**
+ *
+ */
+public class RollingAppenderRestartTest {
+
+ private static final String CONFIG = "log4j-rolling-restart.xml";
+ private static final String DIR = "target/rolling-restart";
+
+ private final LoggerContextRule loggerContextRule = LoggerContextRule.createShutdownTimeoutLoggerContextRule(CONFIG);
+
+ @Rule
+ public RuleChain chain = loggerContextRule.withCleanFoldersRule(false, true, 5, DIR);
+
+ @BeforeClass
+ public static void setup() throws Exception {
+ File file = new File("target/rolling-restart/test.log");
+ Files.createDirectories(file.toPath().getParent());
+ Files.write(file.toPath(), "Hello, world".getBytes(), StandardOpenOption.CREATE);
+ FileTime newTime = FileTime.from(Instant.now().minus(2, ChronoUnit.DAYS));
+ Files.getFileAttributeView(file.toPath(), BasicFileAttributeView.class).setTimes(newTime, newTime, newTime);
+ }
+
+ @Test
+ public void testAppender() throws Exception {
+ final Logger logger = loggerContextRule.getLogger();
+ logger.info("This is test message number 1");
+
+ final File dir = new File(DIR);
+
+ final Matcher<File[]> hasGzippedFile = hasItemInArray(that(hasName(that(endsWith(".gz")))));
+ final File[] files = dir.listFiles();
+ assertTrue("No gzipped files found", hasGzippedFile.matches(files));
+ }
+}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderSizeTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderSizeTest.java
index 103a001..625c2eb 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderSizeTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderSizeTest.java
@@ -75,6 +75,7 @@
// @formatter:off
{"log4j-rolling-gz-lazy.xml", ".gz", true},
{"log4j-rolling-gz.xml", ".gz", false},
+ {"log4j-rolling-numbered-gz.xml", ".gz", false},
{"log4j-rolling-zip-lazy.xml", ".zip", true},
{"log4j-rolling-zip.xml", ".zip", false},
// Apache Commons Compress
@@ -150,7 +151,7 @@
in = new CompressorStreamFactory().createCompressorInputStream(ext.name().toLowerCase(), fis);
} catch (final CompressorException ce) {
ce.printStackTrace();
- fail("Error creating intput stream from " + file.toString() + ": " + ce.getMessage());
+ fail("Error creating input stream from " + file.toString() + ": " + ce.getMessage());
}
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
assertNotNull("No input stream for " + file.getName(), in);
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderSizeWithTimeTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderSizeWithTimeTest.java
new file mode 100644
index 0000000..7370060
--- /dev/null
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderSizeWithTimeTest.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.rolling;
+
+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.junit.LoggerContextRule;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.RuleChain;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+/**
+ * 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() throws Exception {
+ this.logger = loggerContextRule.getLogger(RollingAppenderSizeWithTimeTest.class.getName());
+ }
+
+ @Test
+ public void testAppender() throws Exception {
+ final List<String> 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 (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/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderTimeAndSizeTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderTimeAndSizeTest.java
index ad0ea64..b0235b4 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderTimeAndSizeTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingAppenderTimeAndSizeTest.java
@@ -16,16 +16,6 @@
*/
package org.apache.logging.log4j.core.appender.rolling;
-import static org.apache.logging.log4j.hamcrest.Descriptors.that;
-import static org.apache.logging.log4j.hamcrest.FileMatchers.hasName;
-import static org.hamcrest.Matchers.endsWith;
-import static org.hamcrest.Matchers.hasItemInArray;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertThat;
-import static org.junit.Assert.assertTrue;
-
-import java.io.File;
-
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.junit.LoggerContextRule;
import org.junit.Before;
@@ -33,6 +23,18 @@
import org.junit.Test;
import org.junit.rules.RuleChain;
+import java.io.File;
+import java.nio.file.Files;
+import java.nio.file.attribute.FileTime;
+import java.util.Arrays;
+import java.util.Random;
+
+import static org.apache.logging.log4j.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.*;
+
/**
*
*/
@@ -56,14 +58,41 @@
@Test
public void testAppender() throws Exception {
- for (int i=0; i < 100; ++i) {
- logger.debug("This is test message number " + i);
+ Random rand = new Random();
+ final File logFile = new File("target/rolling3/rollingtest.log");
+ assertTrue("target/rolling3/rollingtest.log does not exist", logFile.exists());
+ FileTime time = (FileTime) Files.getAttribute(logFile.toPath(), "creationTime");
+ for (int j = 0; j < 100; ++j) {
+ int count = rand.nextInt(50);
+ for (int i = 0; i < count; ++i) {
+ logger.debug("This is test message number " + i);
+ }
+ Thread.sleep(rand.nextInt(50));
}
Thread.sleep(50);
final File dir = new File(DIR);
assertTrue("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(".gz"))))));
+ assertThat(files, hasItemInArray(that(hasName(that(endsWith(".log"))))));
+ int found = 0;
+ int fileCounter = 0;
+ String previous = "";
+ for (final File file : files) {
+ final String actual = file.getName();
+ StringBuilder padding = new StringBuilder();
+ 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]);
+ }
+ 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/RollingFileAppenderAccessTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingFileAppenderAccessTest.java
index 8af78b2..4a1e9dc 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingFileAppenderAccessTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingFileAppenderAccessTest.java
@@ -40,10 +40,10 @@
file.deleteOnExit();
// @formatter:off
final RollingFileAppender appender = RollingFileAppender.newBuilder()
- .withFileName(file.getCanonicalPath())
- .withFilePattern("FilePattern")
- .withName("Name")
- .withPolicy(OnStartupTriggeringPolicy.createPolicy(1))
+ .setFileName(file.getCanonicalPath())
+ .setFilePattern("FilePattern")
+ .setName("Name")
+ .setPolicy(OnStartupTriggeringPolicy.createPolicy(1))
.setConfiguration(config)
.build();
// @formatter:on
@@ -66,9 +66,15 @@
final Configuration config = ctx.getConfiguration();
final File file = File.createTempFile("RollingFileAppenderAccessTest", ".tmp");
file.deleteOnExit();
- final RollingFileAppender appender = RollingFileAppender.createAppender(file.getCanonicalPath(),
- "FilePattern", null, "Name", null, null, null, OnStartupTriggeringPolicy.createPolicy(1), null,
- null, null, null, null, null, config);
+ // @formatter:off
+ final RollingFileAppender appender = RollingFileAppender.newBuilder()
+ .setFileName(file.getCanonicalPath())
+ .setFilePattern("FilePattern")
+ .setName("Name")
+ .setPolicy(OnStartupTriggeringPolicy.createPolicy(1))
+ .setConfiguration(config)
+ .build();
+ // @formatter:on
final RollingFileManager manager = appender.getManager();
// Since the RolloverStrategy and TriggeringPolicy are immutable, we could also use generics to type their
// access.
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingFileAppenderLayoutTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingFileAppenderLayoutTest.java
index 5fe9b68..98c64f3 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingFileAppenderLayoutTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingFileAppenderLayoutTest.java
@@ -27,12 +27,12 @@
public void testDefaultLayout() throws Exception {
// @formatter:off
Assert.assertNotNull(RollingFileAppender.newBuilder()
- .withName(RollingFileAppenderLayoutTest.class.getName())
+ .setName(RollingFileAppenderLayoutTest.class.getName())
.setConfiguration(new DefaultConfiguration())
- .withFileName("log.txt")
- .withFilePattern("FilePattern")
- .withPolicy(OnStartupTriggeringPolicy.createPolicy(1))
- .withCreateOnDemand(true) // no need to clutter up test folder with another file
+ .setFileName("log.txt")
+ .setFilePattern("FilePattern")
+ .setPolicy(OnStartupTriggeringPolicy.createPolicy(1))
+ .setCreateOnDemand(true) // no need to clutter up test folder with another file
.build().getLayout());
// @formatter:on
}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingFileAppenderUpdateDataTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingFileAppenderUpdateDataTest.java
index b73b6b1..16900ca 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingFileAppenderUpdateDataTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingFileAppenderUpdateDataTest.java
@@ -26,7 +26,9 @@
import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilder;
import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilderFactory;
import org.apache.logging.log4j.core.config.builder.impl.BuiltConfiguration;
+import org.junit.After;
import org.junit.Assert;
+import org.junit.BeforeClass;
import org.junit.Ignore;
import org.junit.Test;
@@ -63,6 +65,21 @@
return builder;
}
+ private LoggerContext loggerContext1 = null;
+ private LoggerContext loggerContext2 = null;
+
+ @After
+ public void after() {
+ if (loggerContext1 != null) {
+ loggerContext1.close();
+ loggerContext1 = null;
+ }
+ if (loggerContext2 != null) {
+ loggerContext2.close();
+ loggerContext2 = null;
+ }
+ }
+
@Test
public void testClosingLoggerContext() {
// initial config with indexed rollover
@@ -77,17 +94,29 @@
}
@Test
- @Ignore
public void testNotClosingLoggerContext() {
// initial config with indexed rollover
- final LoggerContext loggerContext1 = Configurator.initialize(buildConfigA().build());
- validateAppender(loggerContext1, "target-rolling-update-date/foo.log.%i");
+ loggerContext1 = Configurator.initialize(buildConfigA().build());
+ validateAppender(loggerContext1, "target/rolling-update-date/foo.log.%i");
// rebuild config with date based rollover
- final LoggerContext loggerContext2 = Configurator.initialize(buildConfigB().build());
- validateAppender(loggerContext2, "target/rolling-update-date/foo.log.%d{yyyy-MM-dd-HH:mm:ss}.%i");
+ loggerContext2 = Configurator.initialize(buildConfigB().build());
+ Assert.assertNotNull("No LoggerContext", loggerContext2);
+ Assert.assertTrue("Expected same logger context to be returned", loggerContext1 == loggerContext2);
+ validateAppender(loggerContext1, "target/rolling-update-date/foo.log.%i");
}
+ @Test
+ public void testReconfigure() {
+ // initial config with indexed rollover
+ loggerContext1 = Configurator.initialize(buildConfigA().build());
+ validateAppender(loggerContext1, "target/rolling-update-date/foo.log.%i");
+
+ // rebuild config with date based rollover
+ loggerContext1.setConfiguration(buildConfigB().build());
+ validateAppender(loggerContext1, "target/rolling-update-date/foo.log.%d{yyyy-MM-dd-HH:mm:ss}.%i");
+ }
+
private void validateAppender(final LoggerContext loggerContext, final String expectedFilePattern) {
final RollingFileAppender appender = loggerContext.getConfiguration().getAppender("fooAppender");
Assert.assertNotNull(appender);
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingFileManagerTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingFileManagerTest.java
new file mode 100644
index 0000000..9619a18
--- /dev/null
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingFileManagerTest.java
@@ -0,0 +1,87 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.logging.log4j.core.appender.rolling;
+
+import org.apache.logging.log4j.core.LoggerContext;
+import org.apache.logging.log4j.core.appender.RollingFileAppender;
+import org.apache.logging.log4j.core.config.Configuration;
+import org.apache.logging.log4j.core.lookup.StrSubstitutor;
+import org.apache.logging.log4j.core.util.IOUtils;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.io.*;
+import java.nio.charset.StandardCharsets;
+
+public class RollingFileManagerTest {
+
+ /**
+ * Test the RollingFileManager with a custom DirectFileRolloverStrategy
+ *
+ * @throws IOException
+ */
+ @Test
+ public void testCustomDirectFileRolloverStrategy() throws IOException {
+ class CustomDirectFileRolloverStrategy extends AbstractRolloverStrategy implements DirectFileRolloverStrategy {
+ final File file;
+
+ CustomDirectFileRolloverStrategy(File file, StrSubstitutor strSubstitutor) {
+ super(strSubstitutor);
+ this.file = file;
+ }
+
+ @Override
+ public String getCurrentFileName(RollingFileManager manager) {
+ return file.getAbsolutePath();
+ }
+
+ @Override
+ public void clearCurrentFileName() {
+ // do nothing
+ }
+
+ @Override
+ public RolloverDescription rollover(RollingFileManager manager) throws SecurityException {
+ return null; // do nothing
+ }
+ }
+
+ try (final LoggerContext ctx = LoggerContext.getContext(false)) {
+ final Configuration config = ctx.getConfiguration();
+ final File file = File.createTempFile("RollingFileAppenderAccessTest", ".tmp");
+ file.deleteOnExit();
+
+ final RollingFileAppender appender = RollingFileAppender.newBuilder()
+ .setFilePattern("FilePattern")
+ .setName("RollingFileAppender")
+ .setConfiguration(config)
+ .setStrategy(new CustomDirectFileRolloverStrategy(file, config.getStrSubstitutor()))
+ .setPolicy(new SizeBasedTriggeringPolicy(100))
+ .build();
+
+ Assert.assertNotNull(appender);
+ final String testContent = "Test";
+ try(final RollingFileManager manager = appender.getManager()) {
+ Assert.assertEquals(file.getAbsolutePath(), manager.getFileName());
+ manager.writeToDestination(testContent.getBytes(StandardCharsets.US_ASCII), 0, testContent.length());
+ }
+ try (final Reader reader = new InputStreamReader(new FileInputStream(file), StandardCharsets.US_ASCII)) {
+ Assert.assertEquals(testContent, IOUtils.toString(reader));
+ }
+ }
+ }
+}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingRandomAccessFileManagerTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingRandomAccessFileManagerTest.java
index 8fa07b9..b365322 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingRandomAccessFileManagerTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RollingRandomAccessFileManagerTest.java
@@ -17,6 +17,26 @@
package org.apache.logging.log4j.core.appender.rolling;
+import org.apache.logging.log4j.core.config.DefaultConfiguration;
+import org.apache.logging.log4j.core.util.Closer;
+import org.apache.logging.log4j.core.util.FileUtils;
+import org.apache.logging.log4j.core.util.NullOutputStream;
+import org.apache.logging.log4j.util.Strings;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.RandomAccessFile;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.nio.file.attribute.PosixFileAttributeView;
+import java.nio.file.attribute.PosixFilePermission;
+import java.nio.file.attribute.PosixFilePermissions;
+import java.util.Set;
+import java.util.concurrent.locks.LockSupport;
+
import static org.apache.logging.log4j.hamcrest.FileMatchers.beforeNow;
import static org.apache.logging.log4j.hamcrest.FileMatchers.hasLength;
import static org.apache.logging.log4j.hamcrest.FileMatchers.isEmpty;
@@ -25,21 +45,10 @@
import static org.hamcrest.Matchers.lessThanOrEqualTo;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.OutputStream;
-import java.io.RandomAccessFile;
-import java.util.concurrent.locks.LockSupport;
-
-import org.apache.logging.log4j.core.util.Closer;
-import org.apache.logging.log4j.core.util.NullOutputStream;
-import org.apache.logging.log4j.util.Strings;
-import org.junit.Test;
-
/**
* Tests the RollingRandomAccessFileManager class.
*/
@@ -58,12 +67,12 @@
final boolean append = false;
final boolean flushNow = false;
final long triggerSize = Long.MAX_VALUE;
- final long time = System.currentTimeMillis();
+ final long initialTime = System.currentTimeMillis();
final TriggeringPolicy triggerPolicy = new SizeBasedTriggeringPolicy(triggerSize);
final RolloverStrategy rolloverStrategy = null;
final RollingRandomAccessFileManager manager = new RollingRandomAccessFileManager(null, raf,
file.getName(), Strings.EMPTY, os, append, flushNow,
- RollingRandomAccessFileManager.DEFAULT_BUFFER_SIZE, triggerSize, time, triggerPolicy, rolloverStrategy,
+ RollingRandomAccessFileManager.DEFAULT_BUFFER_SIZE, triggerSize, initialTime, triggerPolicy, rolloverStrategy,
null, null, null, null, null, true);
final int size = RollingRandomAccessFileManager.DEFAULT_BUFFER_SIZE * 3;
@@ -198,4 +207,63 @@
assertThat(file, lastModified(equalTo(manager.getFileTime())));
}
+ @Test
+ public void testRolloverRetainsFileAttributes() throws Exception {
+
+ // Short-circuit if host doesn't support file attributes.
+ if (!FileUtils.isFilePosixAttributeViewSupported()) {
+ return;
+ }
+
+ // Create the initial file.
+ final File file = File.createTempFile("log4j2", "test");
+ LockSupport.parkNanos(1000000); // 1 millisec
+
+ // Set the initial file attributes.
+ final String filePermissionsString = "rwxrwxrwx";
+ final Set<PosixFilePermission> 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<PosixFilePermission> actualFilePermissions = Files
+ .getFileAttributeView(
+ Paths.get(manager.getFileName()),
+ PosixFileAttributeView.class)
+ .readAttributes()
+ .permissions();
+ assertEquals(filePermissions, actualFilePermissions);
+
+ }
+
}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RolloverFilePatternTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RolloverFilePatternTest.java
new file mode 100644
index 0000000..7d7ba91
--- /dev/null
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RolloverFilePatternTest.java
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.logging.log4j.core.appender.rolling;
+
+import java.util.regex.Matcher;
+
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+/**
+ * Test getEligibleFiles method.
+ */
+public class RolloverFilePatternTest {
+
+ @Test
+ public void testFilePatternWithoutPadding() throws Exception {
+ final Matcher matcher = AbstractRolloverStrategy.PATTERN_COUNTER.matcher("target/logs/test-%i.log.gz");
+ assertTrue(matcher.matches());
+ assertNull(matcher.group("ZEROPAD"));
+ assertNull(matcher.group("PADDING"));
+ }
+
+ @Test
+ public void testFilePatternWithSpacePadding() throws Exception {
+ final Matcher matcher = AbstractRolloverStrategy.PATTERN_COUNTER.matcher("target/logs/test-%3i.log.gz");
+ assertTrue(matcher.matches());
+ assertNull(matcher.group("ZEROPAD"));
+ assertEquals("3", matcher.group("PADDING"));
+ }
+
+ @Test
+ public void testFilePatternWithZeroPadding() throws Exception {
+ final Matcher matcher = AbstractRolloverStrategy.PATTERN_COUNTER.matcher("target/logs/test-%03i.log.gz");
+ assertTrue(matcher.matches());
+ assertEquals("0", matcher.group("ZEROPAD"));
+ assertEquals("3", matcher.group("PADDING"));
+ }
+
+ @Test
+ public void testFilePatternUnmatched() throws Exception {
+ final Matcher matcher = AbstractRolloverStrategy.PATTERN_COUNTER.matcher("target/logs/test-%n.log.gz");
+ assertFalse(matcher.matches());
+ }
+}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RolloverWithDeletedOldFileTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RolloverWithDeletedOldFileTest.java
new file mode 100644
index 0000000..7956024
--- /dev/null
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RolloverWithDeletedOldFileTest.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.appender.rolling;
+
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.junit.LoggerContextRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.RuleChain;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.List;
+
+import static org.junit.Assert.*;
+
+/**
+ * Tests that files were rolled correctly if an old log file was deleted from the directory.
+ */
+public class RolloverWithDeletedOldFileTest {
+ private static final String CONFIG = "log4j-rolling-with-padding.xml";
+ private static final String DIR = "target/rolling-with-padding";
+
+ private final LoggerContextRule loggerContextRule = LoggerContextRule.createShutdownTimeoutLoggerContextRule(CONFIG);
+
+ @Rule
+ public RuleChain chain = loggerContextRule.withCleanFoldersRule(DIR);
+
+ @Test
+ public void testAppender() throws Exception {
+ final Logger logger = loggerContextRule.getLogger();
+ for (int i = 0; i < 10; ++i) {
+ // 30 chars per message: each message triggers a rollover
+ logger.fatal("This is a test message number " + i); // 30 chars:
+ }
+ Thread.sleep(100); // Allow time for rollover to complete
+
+ final File dir = new File(DIR);
+ assertTrue("Dir " + DIR + " should exist", dir.exists());
+ assertTrue("Dir " + DIR + " should contain files", dir.listFiles().length == 6);
+
+ File[] files = dir.listFiles();
+ final List<String> expected = Arrays.asList("rollingtest.log", "test-001.log", "test-002.log", "test-003.log", "test-004.log", "test-005.log");
+ assertEquals("Unexpected number of files", expected.size(), files.length);
+ File fileToRemove = null;
+ for (final File file : files) {
+ if (!expected.contains(file.getName())) {
+ fail("unexpected file" + file);
+ }
+ if (file.getName().equals("test-001.log")) {
+ fileToRemove = file;
+ }
+ }
+ fileToRemove.delete();
+ for (int i = 0; i < 10; ++i) {
+ // 30 chars per message: each message triggers a rollover
+ logger.fatal("This is a test message number " + i); // 30 chars:
+ }
+ Thread.sleep(100); // Allow time for rollover to complete again
+ files = dir.listFiles();
+ assertEquals("Unexpected number of files", expected.size(), files.length);
+ for (final File file : files) {
+ if (!expected.contains(file.getName())) {
+ fail("unexpected file" + file);
+ }
+ }
+ }
+}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RolloverWithPaddingTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RolloverWithPaddingTest.java
new file mode 100644
index 0000000..7b0fb22
--- /dev/null
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/RolloverWithPaddingTest.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.logging.log4j.core.appender.rolling;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.List;
+
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.junit.LoggerContextRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.RuleChain;
+
+/**
+ * Tests that zero-padding in rolled files works correctly.
+ */
+public class RolloverWithPaddingTest {
+ private static final String CONFIG = "log4j-rolling-with-padding.xml";
+ private static final String DIR = "target/rolling-with-padding";
+
+ private final LoggerContextRule loggerContextRule = LoggerContextRule.createShutdownTimeoutLoggerContextRule(CONFIG);
+
+ @Rule
+ public RuleChain chain = loggerContextRule.withCleanFoldersRule(DIR);
+
+ @Test
+ public void testAppender() throws Exception {
+ final Logger logger = loggerContextRule.getLogger();
+ for (int i = 0; i < 10; ++i) {
+ // 30 chars per message: each message triggers a rollover
+ logger.fatal("This is a test message number " + i); // 30 chars:
+ }
+ Thread.sleep(100); // Allow time for rollover to complete
+
+ final File dir = new File(DIR);
+ assertTrue("Dir " + DIR + " should exist", dir.exists());
+ assertTrue("Dir " + DIR + " should contain files", dir.listFiles().length == 6);
+
+ final File[] files = dir.listFiles();
+ final List<String> expected = Arrays.asList("rollingtest.log", "test-001.log", "test-002.log", "test-003.log", "test-004.log", "test-005.log");
+ assertEquals("Unexpected number of files", expected.size(), files.length);
+ for (final File file : files) {
+ if (!expected.contains(file.getName())) {
+ fail("unexpected file" + file);
+ }
+ }
+ }
+}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/AbstractActionTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/AbstractActionTest.java
new file mode 100644
index 0000000..d6038d6
--- /dev/null
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/AbstractActionTest.java
@@ -0,0 +1,81 @@
+package org.apache.logging.log4j.core.appender.rolling.action;
+
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.junit.StatusLoggerRule;
+import org.apache.logging.log4j.status.StatusData;
+import org.apache.logging.log4j.status.StatusLogger;
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+public class AbstractActionTest {
+
+ @Rule
+ public StatusLoggerRule statusLogger = new StatusLoggerRule(Level.WARN);
+
+ // Test for LOG4J2-2658
+ @Test
+ public void testExceptionsAreLoggedToStatusLogger() {
+ StatusLogger statusLogger = StatusLogger.getLogger();
+ statusLogger.clear();
+ new TestAction().run();
+ List<StatusData> statusDataList = statusLogger.getStatusData();
+ assertEquals(1, statusDataList.size());
+ StatusData statusData = statusDataList.get(0);
+ assertEquals(Level.WARN, statusData.getLevel());
+ String formattedMessage = statusData.getFormattedStatus();
+ assertTrue(formattedMessage, formattedMessage.contains("Exception reported by action 'class org.apache."
+ + "logging.log4j.core.appender.rolling.action.AbstractActionTest$TestAction' java.io.IOException: "
+ + "failed" + System.lineSeparator()
+ + "\tat org.apache.logging.log4j.core.appender.rolling.action.AbstractActionTest"
+ + "$TestAction.execute(AbstractActionTest.java:"));
+ }
+
+ @Test
+ public void testRuntimeExceptionsAreLoggedToStatusLogger() {
+ StatusLogger statusLogger = StatusLogger.getLogger();
+ statusLogger.clear();
+ new AbstractAction() {
+ @Override
+ public boolean execute() {
+ throw new IllegalStateException();
+ }
+ }.run();
+ List<StatusData> statusDataList = statusLogger.getStatusData();
+ assertEquals(1, statusDataList.size());
+ StatusData statusData = statusDataList.get(0);
+ assertEquals(Level.WARN, statusData.getLevel());
+ String formattedMessage = statusData.getFormattedStatus();
+ assertTrue(formattedMessage.contains("Exception reported by action"));
+ }
+
+ @Test
+ public void testErrorsAreLoggedToStatusLogger() {
+ StatusLogger statusLogger = StatusLogger.getLogger();
+ statusLogger.clear();
+ new AbstractAction() {
+ @Override
+ public boolean execute() {
+ throw new AssertionError();
+ }
+ }.run();
+ List<StatusData> statusDataList = statusLogger.getStatusData();
+ assertEquals(1, statusDataList.size());
+ StatusData statusData = statusDataList.get(0);
+ assertEquals(Level.WARN, statusData.getLevel());
+ String formattedMessage = statusData.getFormattedStatus();
+ assertTrue(formattedMessage.contains("Exception reported by action"));
+ }
+
+ private static final class TestAction extends AbstractAction {
+ @Override
+ public boolean execute() throws IOException {
+ throw new IOException("failed");
+ }
+ }
+}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/DeletingVisitorTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/DeletingVisitorTest.java
index 7258c23..ed472ad 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/DeletingVisitorTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/DeletingVisitorTest.java
@@ -18,11 +18,14 @@
package org.apache.logging.log4j.core.appender.rolling.action;
import java.io.IOException;
+import java.nio.file.FileVisitResult;
+import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
import org.junit.Test;
@@ -132,4 +135,26 @@
final Path child = Paths.get("/a/b/c/relative");
visitor.visitFile(child, null);
}
+
+ @Test
+ public void testNoSuchFileFailure() throws IOException {
+ final DeletingVisitorHelper visitor =
+ new DeletingVisitorHelper(Paths.get("/a/b/c"), Collections.emptyList(), true);
+ assertEquals(
+ FileVisitResult.CONTINUE,
+ visitor.visitFileFailed(Paths.get("doesNotExist"), new NoSuchFileException("doesNotExist")));
+ }
+
+ @Test
+ public void testIOException() {
+ final DeletingVisitorHelper visitor =
+ new DeletingVisitorHelper(Paths.get("/a/b/c"), Collections.emptyList(), true);
+ IOException exception = new IOException();
+ try {
+ visitor.visitFileFailed(Paths.get("doesNotExist"), exception);
+ fail();
+ } catch (IOException e) {
+ assertSame(exception, e);
+ }
+ }
}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/FileSizeTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/FileSizeTest.java
new file mode 100644
index 0000000..dfa7cec
--- /dev/null
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/FileSizeTest.java
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.logging.log4j.core.appender.rolling.action;
+
+import org.apache.logging.log4j.core.appender.rolling.FileSize;
+import org.junit.Test;
+
+import java.text.NumberFormat;
+import java.util.Locale;
+
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.is;
+import static org.junit.Assert.assertThat;
+
+public class FileSizeTest {
+
+ @Test
+ public void testParse() {
+ assertThat(FileSize.parse("5k", 0), is(5L * 1024));
+ }
+
+ @Test
+ public void testParseInEurope() {
+ // Caveat: Breaks the ability for this test to run in parallel with other tests :(
+ Locale previousDefault = Locale.getDefault();
+ try {
+ Locale.setDefault(new Locale("de", "DE"));
+ assertThat(FileSize.parse("1,000", 0), is(1000L));
+ } finally {
+ Locale.setDefault(previousDefault);
+ }
+ }
+}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/SortingVisitorTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/SortingVisitorTest.java
index dd32a0c..06ee1ce 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/SortingVisitorTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/rolling/action/SortingVisitorTest.java
@@ -17,9 +17,13 @@
package org.apache.logging.log4j.core.appender.rolling.action;
+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.FileAttribute;
import java.nio.file.attribute.FileTime;
import java.util.Collections;
@@ -91,4 +95,24 @@
assertEquals("2nd; sorted=" + found, bbb, found.get(1).getPath());
assertEquals("3rd: most recent sorted; list=" + found, ccc, found.get(2).getPath());
}
+
+ @Test
+ public void testNoSuchFileFailure() throws IOException {
+ SortingVisitor visitor = new SortingVisitor(new PathSortByModificationTime(false));
+ assertEquals(
+ FileVisitResult.CONTINUE,
+ visitor.visitFileFailed(Paths.get("doesNotExist"), new NoSuchFileException("doesNotExist")));
+ }
+
+ @Test
+ public void testIOException() {
+ SortingVisitor visitor = new SortingVisitor(new PathSortByModificationTime(false));
+ IOException exception = new IOException();
+ try {
+ visitor.visitFileFailed(Paths.get("doesNotExist"), exception);
+ fail();
+ } catch (IOException e) {
+ assertSame(exception, e);
+ }
+ }
}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/DefaultRouteScriptAppenderTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/DefaultRouteScriptAppenderTest.java
index b112fb7..4e14e8d 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/DefaultRouteScriptAppenderTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/DefaultRouteScriptAppenderTest.java
@@ -16,16 +16,11 @@
*/
package org.apache.logging.log4j.core.appender.routing;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
-
-import java.util.List;
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.LogEvent;
import org.apache.logging.log4j.core.Logger;
import org.apache.logging.log4j.core.config.AppenderControl;
import org.apache.logging.log4j.junit.LoggerContextRule;
@@ -36,6 +31,8 @@
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
+import static org.junit.Assert.*;
+
/**
*
*/
@@ -68,8 +65,8 @@
final RoutingAppender routingAppender = getRoutingAppender();
final ConcurrentMap<Object, Object> map = routingAppender.getScriptStaticVariables();
if (expectBindingEntries) {
- Assert.assertEquals("TestValue2", map.get("TestKey"));
- Assert.assertEquals("HEXDUMP", map.get("MarkerName"));
+ assertEquals("TestValue2", map.get("TestKey"));
+ assertEquals("HEXDUMP", map.get("MarkerName"));
}
}
@@ -93,13 +90,11 @@
final Logger logger = loggerContextRule.getLogger(DefaultRouteScriptAppenderTest.class);
logger.error("Hello");
final ListAppender listAppender = getListAppender();
- final List<LogEvent> list = listAppender.getEvents();
- assertNotNull("No events generated", list);
- assertTrue("Incorrect number of events. Expected 1, got " + list.size(), list.size() == 1);
+ assertEquals("Incorrect number of events", 1, listAppender.getEvents().size());
logger.error("World");
- assertTrue("Incorrect number of events. Expected 2, got " + list.size(), list.size() == 2);
+ assertEquals("Incorrect number of events", 2, listAppender.getEvents().size());
logger.error(marker, "DEADBEEF");
- assertTrue("Incorrect number of events. Expected 3, got " + list.size(), list.size() == 3);
+ assertEquals("Incorrect number of events", 3, listAppender.getEvents().size());
}
@Test(expected = AssertionError.class)
@@ -130,7 +125,7 @@
final RoutingAppender routingAppender = getRoutingAppender();
Assert.assertNotNull(routingAppender.getDefaultRouteScript());
Assert.assertNotNull(routingAppender.getDefaultRoute());
- Assert.assertEquals("Service2", routingAppender.getDefaultRoute().getKey());
+ assertEquals("Service2", routingAppender.getDefaultRoute().getKey());
}
@Test
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutesScriptAppenderTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutesScriptAppenderTest.java
index 12b571f..3b20f8f 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutesScriptAppenderTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutesScriptAppenderTest.java
@@ -17,9 +17,8 @@
package org.apache.logging.log4j.core.appender.routing;
import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertEquals;
-import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
@@ -41,9 +40,6 @@
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 {
@@ -74,8 +70,8 @@
final RoutingAppender routingAppender = getRoutingAppender();
final ConcurrentMap<Object, Object> map = routingAppender.getScriptStaticVariables();
if (expectBindingEntries) {
- Assert.assertEquals("TestValue2", map.get("TestKey"));
- Assert.assertEquals("HEXDUMP", map.get("MarkerName"));
+ assertEquals("TestValue2", map.get("TestKey"));
+ assertEquals("HEXDUMP", map.get("MarkerName"));
}
}
private ListAppender getListAppender() {
@@ -85,8 +81,7 @@
final Map<String, AppenderControl> 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;
+ return (ListAppender) appenderControl.getAppender();
}
private RoutingAppender getRoutingAppender() {
@@ -98,13 +93,11 @@
final Logger logger = loggerContextRule.getLogger(RoutesScriptAppenderTest.class);
logger.error("Hello");
final ListAppender listAppender = getListAppender();
- final List<LogEvent> list = listAppender.getEvents();
- assertNotNull("No events generated", list);
- assertTrue("Incorrect number of events. Expected 1, got " + list.size(), list.size() == 1);
+ assertEquals("Incorrect number of events", 1, listAppender.getEvents().size());
logger.error("World");
- assertTrue("Incorrect number of events. Expected 2, got " + list.size(), list.size() == 2);
+ assertEquals("Incorrect number of events", 2, listAppender.getEvents().size());
logger.error(marker, "DEADBEEF");
- assertTrue("Incorrect number of events. Expected 3, got " + list.size(), list.size() == 3);
+ assertEquals("Incorrect number of events", 3, listAppender.getEvents().size());
}
@Test(expected = AssertionError.class)
@@ -133,14 +126,14 @@
@Test
public void testRoutingAppenderRoutes() {
final RoutingAppender routingAppender = getRoutingAppender();
- Assert.assertEquals(expectBindingEntries, routingAppender.getDefaultRouteScript() != null);
- Assert.assertEquals(expectBindingEntries, routingAppender.getDefaultRoute() != null);
+ 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);
- Assert.assertEquals("Service2", routes.getPattern(logEvent, new ConcurrentHashMap<>()));
+ assertEquals("Service2", routes.getPattern(logEvent, new ConcurrentHashMap<>()));
}
@Test
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutingAppender2767Test.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutingAppender2767Test.java
new file mode 100644
index 0000000..1348351
--- /dev/null
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutingAppender2767Test.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.routing;
+
+import org.apache.logging.log4j.EventLogger;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.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;
+
+import java.io.File;
+import java.nio.file.Files;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ *
+ */
+public class RoutingAppender2767Test {
+ private static final String CONFIG = "log4j-routing-2767.xml";
+ private static final String ACTIVITY_LOG_FILE = "target/routing1/routingtest-Service.log";
+
+ private final LoggerContextRule loggerContextRule = new LoggerContextRule(CONFIG);
+
+ @Rule
+ public RuleChain rules = loggerContextRule.withCleanFilesRule(ACTIVITY_LOG_FILE);
+
+ @Before
+ public void setUp() throws Exception {
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ this.loggerContextRule.getLoggerContext().stop();
+ }
+
+ @Test
+ public void routingTest() throws Exception {
+ StructuredDataMessage msg = new StructuredDataMessage("Test", "This is a test", "Service");
+ EventLogger.logEvent(msg);
+ File file = new File(ACTIVITY_LOG_FILE);
+ assertTrue("Activity file was not created", file.exists());
+ List<String> 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/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutingAppenderWithPurgingTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutingAppenderWithPurgingTest.java
index 7dce22d..82a571f 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutingAppenderWithPurgingTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/appender/routing/RoutingAppenderWithPurgingTest.java
@@ -17,11 +17,14 @@
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;
@@ -84,19 +87,26 @@
EventLogger.logEvent(msg);
msg = new StructuredDataMessage("3", "This is a test 3", "Service");
EventLogger.logEvent(msg);
- final String[] files = {IDLE_LOG_FILE1, IDLE_LOG_FILE2, IDLE_LOG_FILE3, MANUAL_LOG_FILE1, MANUAL_LOG_FILE2, MANUAL_LOG_FILE3};
+ // '2' is a referenced list appender
+ final String[] files = {IDLE_LOG_FILE1, IDLE_LOG_FILE3, MANUAL_LOG_FILE1, MANUAL_LOG_FILE3};
assertFileExistance(files);
+ Set<String> expectedAppenderKeys = new HashSet<>(2);
+ expectedAppenderKeys.add("1");
+ expectedAppenderKeys.add("3");
+ assertEquals(expectedAppenderKeys, routingAppenderManual.getAppenders().keySet());
- assertEquals("Incorrect number of appenders with IdlePurgePolicy.", 3, routingAppenderIdle.getAppenders().size());
+ assertFalse(((ListAppender) loggerContextRule.getAppender("ReferencedList")).getEvents().isEmpty());
+
+ assertEquals("Incorrect number of appenders with IdlePurgePolicy.", 2, routingAppenderIdle.getAppenders().size());
assertEquals("Incorrect number of appenders with IdlePurgePolicy with HangingAppender.",
- 3, routingAppenderIdleWithHangingAppender.getAppenders().size());
- assertEquals("Incorrect number of appenders manual purge.", 3, routingAppenderManual.getAppenders().size());
+ 2, routingAppenderIdleWithHangingAppender.getAppenders().size());
+ assertEquals("Incorrect number of appenders manual purge.", 2, routingAppenderManual.getAppenders().size());
Thread.sleep(3000);
EventLogger.logEvent(msg);
assertEquals("Incorrect number of appenders with IdlePurgePolicy.", 1, routingAppenderIdle.getAppenders().size());
- assertEquals("Incorrect number of appenders with manual purge.", 3, routingAppenderManual.getAppenders().size());
+ assertEquals("Incorrect number of appenders with manual purge.", 2, routingAppenderManual.getAppenders().size());
routingAppenderManual.deleteAppender("1");
routingAppenderManual.deleteAppender("2");
@@ -105,6 +115,9 @@
assertEquals("Incorrect number of appenders with IdlePurgePolicy.", 1, routingAppenderIdle.getAppenders().size());
assertEquals("Incorrect number of appenders with manual purge.", 0, routingAppenderManual.getAppenders().size());
+ assertFalse("Reference based routes should not be stoppable",
+ loggerContextRule.getAppender("ReferencedList").isStopped());
+
msg = new StructuredDataMessage("5", "This is a test 5", "Service");
EventLogger.logEvent(msg);
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerConfigTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerConfigTest.java
index aaf3fa6..1e713d5 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerConfigTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerConfigTest.java
@@ -16,10 +16,15 @@
*/
package org.apache.logging.log4j.core.async;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
+import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.categories.AsyncLoggers;
@@ -32,8 +37,6 @@
import org.junit.Test;
import org.junit.experimental.categories.Category;
-import static org.junit.Assert.*;
-
@Category(AsyncLoggers.class)
public class AsyncLoggerConfigTest {
@@ -70,15 +73,15 @@
public void testIncludeLocationDefaultsToFalse() {
final LoggerConfig rootLoggerConfig =
AsyncLoggerConfig.RootLogger.createLogger(
- null, "INFO", null, new AppenderRef[0], null, new DefaultConfiguration(), null);
- assertFalse("Include location should default to false for async logggers",
+ null, Level.INFO, null, new AppenderRef[0], null, new DefaultConfiguration(), null);
+ assertFalse("Include location should default to false for async loggers",
rootLoggerConfig.isIncludeLocation());
final LoggerConfig loggerConfig =
AsyncLoggerConfig.createLogger(
- null, "INFO", "com.foo.Bar", null, new AppenderRef[0], null, new DefaultConfiguration(),
+ false, Level.INFO, "com.foo.Bar", null, new AppenderRef[0], null, new DefaultConfiguration(),
null);
- assertFalse("Include location should default to false for async logggers",
+ assertFalse("Include location should default to false for async loggers",
loggerConfig.isIncludeLocation());
}
}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerTestArgumentFreedOnError.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerTestArgumentFreedOnError.java
new file mode 100644
index 0000000..5f9f4b5
--- /dev/null
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerTestArgumentFreedOnError.java
@@ -0,0 +1,109 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.logging.log4j.core.async;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.categories.AsyncLoggers;
+import org.apache.logging.log4j.core.GarbageCollectionHelper;
+import org.apache.logging.log4j.core.util.Constants;
+import org.apache.logging.log4j.message.Message;
+import org.apache.logging.log4j.util.StringBuilderFormattable;
+import org.apache.logging.log4j.util.Strings;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.Assert.assertTrue;
+
+@Category(AsyncLoggers.class)
+public class AsyncLoggerTestArgumentFreedOnError {
+
+ @BeforeClass
+ public static void beforeClass() {
+ System.setProperty("log4j2.enable.threadlocals", "true");
+ System.setProperty("log4j2.enable.direct.encoders", "true");
+ System.setProperty("log4j2.is.webapp", "false");
+ System.setProperty("log4j.format.msg.async", "true");
+ System.setProperty(Constants.LOG4J_CONTEXT_SELECTOR,
+ AsyncLoggerContextSelector.class.getName());
+ }
+
+ @AfterClass
+ public static void afterClass() {
+ System.setProperty(Constants.LOG4J_CONTEXT_SELECTOR, Strings.EMPTY);
+ }
+
+ // LOG4J2-2725: events are cleared even after failure
+ @Test
+ public void testMessageIsGarbageCollected() throws Exception {
+ final AsyncLogger log = (AsyncLogger) LogManager.getLogger("com.foo.Bar");
+ CountDownLatch garbageCollectionLatch = new CountDownLatch(1);
+ log.fatal(new ThrowingMessage(garbageCollectionLatch));
+ GarbageCollectionHelper gcHelper = new GarbageCollectionHelper();
+ gcHelper.run();
+ try {
+ assertTrue("Parameter should have been garbage collected",
+ garbageCollectionLatch.await(30, TimeUnit.SECONDS));
+ } finally {
+ gcHelper.close();
+ }
+ }
+
+ private static class ThrowingMessage implements Message, StringBuilderFormattable {
+
+ private final CountDownLatch latch;
+
+ ThrowingMessage(CountDownLatch latch) {
+ this.latch = latch;
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ latch.countDown();
+ super.finalize();
+ }
+
+ @Override
+ public String getFormattedMessage() {
+ throw new Error("Expected");
+ }
+
+ @Override
+ public String getFormat() {
+ return "";
+ }
+
+ @Override
+ public Object[] getParameters() {
+ return new Object[0];
+ }
+
+ @Override
+ public Throwable getThrowable() {
+ return null;
+ }
+
+ @Override
+ public void formatTo(StringBuilder buffer) {
+ throw new Error("Expected");
+ }
+ }
+}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerThreadNameStrategyTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerThreadNameStrategyTest.java
index 546cc68..bc5945e 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerThreadNameStrategyTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/async/AsyncLoggerThreadNameStrategyTest.java
@@ -26,10 +26,6 @@
@Category(AsyncLoggers.class)
public class AsyncLoggerThreadNameStrategyTest {
- static final String DEFAULT_STRATEGY = System.getProperty("java.version").compareTo("1.8.0_102") < 0
- ? "CACHED" // LOG4J2-2052 JDK 8u102 removed the String allocation in Thread.getName()
- : "UNCACHED";
-
@After
public void after() {
System.clearProperty("AsyncLogger.ThreadNameStrategy");
@@ -41,16 +37,16 @@
}
@Test
- public void testDefaultThreadNameIsCached() throws Exception {
+ public void testDefaultIfNotConfigured() throws Exception {
final ThreadNameCachingStrategy tns = ThreadNameCachingStrategy.create();
- assertSame(ThreadNameCachingStrategy.valueOf(DEFAULT_STRATEGY), tns);
+ assertSame(ThreadNameCachingStrategy.DEFAULT_STRATEGY, tns);
}
@Test
- public void testUseCachedThreadNameIfInvalidConfig() throws Exception {
+ public void testDefaultIfInvalidConfig() throws Exception {
System.setProperty("AsyncLogger.ThreadNameStrategy", "\\%%InValid ");
final ThreadNameCachingStrategy tns = ThreadNameCachingStrategy.create();
- assertSame(ThreadNameCachingStrategy.valueOf(DEFAULT_STRATEGY), tns);
+ assertSame(ThreadNameCachingStrategy.DEFAULT_STRATEGY, tns);
}
@Test
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/AsyncQueueFullPolicyFactoryTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/async/AsyncQueueFullPolicyFactoryTest.java
index 964bf12..cd5e461 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/AsyncQueueFullPolicyFactoryTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/async/AsyncQueueFullPolicyFactoryTest.java
@@ -19,10 +19,13 @@
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.categories.AsyncLoggers;
import org.apache.logging.log4j.util.PropertiesUtil;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.experimental.categories.Category;
+import java.util.Locale;
+
import static org.junit.Assert.*;
/**
@@ -32,7 +35,8 @@
public class AsyncQueueFullPolicyFactoryTest {
@Before
- public void setUp() throws Exception {
+ @After
+ public void resetProperties() throws Exception {
System.clearProperty(AsyncQueueFullPolicyFactory.PROPERTY_NAME_ASYNC_EVENT_ROUTER);
System.clearProperty(AsyncQueueFullPolicyFactory.PROPERTY_NAME_DISCARDING_THRESHOLD_LEVEL);
PropertiesUtil.getProperties().reload();
@@ -68,6 +72,14 @@
}
@Test
+ public void testCreateDiscardingRouterCaseInsensitive() {
+ System.setProperty(AsyncQueueFullPolicyFactory.PROPERTY_NAME_ASYNC_EVENT_ROUTER,
+ AsyncQueueFullPolicyFactory.PROPERTY_VALUE_DISCARDING_ASYNC_EVENT_ROUTER.toLowerCase(Locale.ENGLISH));
+ assertEquals(Level.INFO, ((DiscardingAsyncQueueFullPolicy) AsyncQueueFullPolicyFactory.create()).
+ getThresholdLevel());
+ }
+
+ @Test
public void testCreateDiscardingRouterThresholdLevelCustomizable() throws Exception {
System.setProperty(AsyncQueueFullPolicyFactory.PROPERTY_NAME_ASYNC_EVENT_ROUTER,
AsyncQueueFullPolicyFactory.PROPERTY_VALUE_DISCARDING_ASYNC_EVENT_ROUTER);
@@ -106,4 +118,4 @@
DoesNotImplementInterface.class.getName());
assertEquals(DefaultAsyncQueueFullPolicy.class, AsyncQueueFullPolicyFactory.create().getClass());
}
-}
\ No newline at end of file
+}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/AsyncRootReloadTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/async/AsyncRootReloadTest.java
index 49dec04..27ee560 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/AsyncRootReloadTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/async/AsyncRootReloadTest.java
@@ -51,7 +51,7 @@
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/src/test/java/org/apache/logging/log4j/core/async/BlockingAppender.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/async/BlockingAppender.java
index eca746b..21e9ed5 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/BlockingAppender.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/async/BlockingAppender.java
@@ -28,11 +28,12 @@
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.config.Property;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginAttribute;
+import org.apache.logging.log4j.plugins.PluginElement;
+import org.apache.logging.log4j.plugins.PluginFactory;
+import org.apache.logging.log4j.plugins.validation.constraints.Required;
import org.apache.logging.log4j.core.impl.Log4jLogEvent;
/**
@@ -41,11 +42,12 @@
@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<LogEvent> logEvents = new CopyOnWriteArrayList<>();
public CountDownLatch countDownLatch = null;
public BlockingAppender(final String name) {
- super(name, null, null);
+ super(name, null, null, true, Property.EMPTY_ARRAY);
}
@Override
@@ -55,7 +57,10 @@
event.getMessage().getFormattedMessage();
// may be a reusable event, make a copy, don't keep a reference to the original event
- logEvents.add(Log4jLogEvent.createMemento(event));
+ List<LogEvent> events = logEvents;
+ if (events != null) {
+ events.add(Log4jLogEvent.createMemento(event));
+ }
if (countDownLatch == null) {
return;
@@ -70,11 +75,11 @@
@PluginFactory
public static BlockingAppender createAppender(
- @PluginAttribute("name")
+ @PluginAttribute
@Required(message = "No name provided for HangingAppender")
final String name,
- @PluginElement("Layout") final Layout<? extends Serializable> layout,
- @PluginElement("Filter") final Filter filter) {
+ @PluginElement final Layout<? extends Serializable> layout,
+ @PluginElement final Filter filter) {
return new BlockingAppender(name);
}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/DefaultAsyncQueueFullPolicyTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/async/DefaultAsyncQueueFullPolicyTest.java
index d6d2b56..63379ee 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/DefaultAsyncQueueFullPolicyTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/async/DefaultAsyncQueueFullPolicyTest.java
@@ -50,4 +50,4 @@
assertEquals(EventRoute.SYNCHRONOUS, router.getRoute(currentThreadId(), Level.ALL));
assertEquals(EventRoute.SYNCHRONOUS, router.getRoute(currentThreadId(), Level.OFF));
}
-}
\ No newline at end of file
+}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/DiscardingAsyncQueueFullPolicyTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/async/DiscardingAsyncQueueFullPolicyTest.java
index 8c5ef5a..ddcb2b6 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/DiscardingAsyncQueueFullPolicyTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/async/DiscardingAsyncQueueFullPolicyTest.java
@@ -117,4 +117,4 @@
assertEquals(EventRoute.DISCARD, router.getRoute(-1L, Level.INFO));
assertEquals("increase", 3, DiscardingAsyncQueueFullPolicy.getDiscardCount(router));
}
-}
\ No newline at end of file
+}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/Log4j2Jira1688AsyncTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/async/Log4j2Jira1688AsyncTest.java
index f63e816..d4de80a 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/Log4j2Jira1688AsyncTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/async/Log4j2Jira1688AsyncTest.java
@@ -93,4 +93,4 @@
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/src/test/java/org/apache/logging/log4j/core/async/Log4j2Jira1688Test.java
index 57d9247..5fda3d0 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/Log4j2Jira1688Test.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/async/Log4j2Jira1688Test.java
@@ -91,4 +91,4 @@
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/QueueFullAbstractTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/async/QueueFullAbstractTest.java
index d0542dc..3297fe4 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/QueueFullAbstractTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/async/QueueFullAbstractTest.java
@@ -36,7 +36,7 @@
* Tests queue full scenarios abstract superclass.
*/
public abstract class QueueFullAbstractTest {
- protected static boolean TRACE = false;
+ protected static boolean TRACE = true;
protected BlockingAppender blockingAppender;
protected Unlocker unlocker;
@@ -126,4 +126,4 @@
f.setAccessible(true);
return f;
}
-}
\ No newline at end of file
+}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncAppenderTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncAppenderTest.java
index dae5095..b0aa3ea 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncAppenderTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncAppenderTest.java
@@ -90,4 +90,4 @@
}
assertTrue(actual.isEmpty());
}
-}
\ No newline at end of file
+}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncAppenderTest2.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncAppenderTest2.java
index d8ce85c..b592dc8 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncAppenderTest2.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncAppenderTest2.java
@@ -67,4 +67,4 @@
QueueFullAsyncAppenderTest.asyncAppenderTest(logger, unlocker, blockingAppender);
}
-}
\ No newline at end of file
+}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerConfigLoggingFromToStringTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerConfigLoggingFromToStringTest.java
index 307a93d..b13bbd3 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerConfigLoggingFromToStringTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerConfigLoggingFromToStringTest.java
@@ -109,4 +109,4 @@
assertEquals("logging naughty object #0 Who's bad?!", actual.pop());
assertTrue(actual.isEmpty());
}
-}
\ No newline at end of file
+}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerConfigLoggingFromToStringTest2.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerConfigLoggingFromToStringTest2.java
index ee11629..c248d99 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerConfigLoggingFromToStringTest2.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerConfigLoggingFromToStringTest2.java
@@ -70,4 +70,4 @@
QueueFullAsyncLoggerConfigLoggingFromToStringTest.asyncLoggerConfigRecursiveTest(logger, unlocker, blockingAppender, this);
}
-}
\ No newline at end of file
+}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerConfigTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerConfigTest.java
index 7d68582..28e6659 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerConfigTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerConfigTest.java
@@ -91,4 +91,4 @@
}
assertTrue(actual.isEmpty());
}
-}
\ No newline at end of file
+}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerConfigTest2.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerConfigTest2.java
index ff5ce7c..6e38421 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerConfigTest2.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerConfigTest2.java
@@ -68,4 +68,4 @@
QueueFullAsyncLoggerConfigTest.asyncLoggerConfigTest(logger, unlocker, blockingAppender);
}
-}
\ No newline at end of file
+}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerLoggingFromToStringTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerLoggingFromToStringTest.java
index 5c12502..2020cbf 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerLoggingFromToStringTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerLoggingFromToStringTest.java
@@ -67,7 +67,7 @@
blockingAppender = context.getRequiredAppender("Blocking", BlockingAppender.class);
}
- @Test(timeout = 5000)
+ @Test(timeout = 50000)
public void testLoggingFromToStringCausesOutOfOrderMessages() throws InterruptedException {
final Logger logger = LogManager.getLogger(this.getClass());
@@ -105,4 +105,4 @@
}
assertTrue(actual.isEmpty());
}
-}
\ No newline at end of file
+}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerLoggingFromToStringTest2.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerLoggingFromToStringTest2.java
index 1691838..0ff31af 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerLoggingFromToStringTest2.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerLoggingFromToStringTest2.java
@@ -105,4 +105,4 @@
}
assertTrue(actual.isEmpty());
}
-}
\ No newline at end of file
+}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerTest.java
index e5ba78d..3bc8be4 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerTest.java
@@ -99,4 +99,4 @@
}
assertTrue(actual.isEmpty());
}
-}
\ No newline at end of file
+}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerTest2.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerTest2.java
index 022e295..3f06a5c 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerTest2.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerTest2.java
@@ -76,4 +76,4 @@
QueueFullAsyncLoggerTest.asyncLoggerTest(logger, unlocker, blockingAppender);
}
-}
\ No newline at end of file
+}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerTest3.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerTest3.java
new file mode 100644
index 0000000..2ec737c
--- /dev/null
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/async/QueueFullAsyncLoggerTest3.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.async;
+
+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.GarbageCollectionHelper;
+import org.apache.logging.log4j.core.config.ConfigurationFactory;
+import org.apache.logging.log4j.core.util.Constants;
+import org.apache.logging.log4j.junit.LoggerContextRule;
+import org.apache.logging.log4j.message.Message;
+import org.apache.logging.log4j.util.Strings;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.runner.RunWith;
+import org.junit.runners.BlockJUnit4ClassRunner;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Tests queue full scenarios with pure AsyncLoggers (all loggers async).
+ */
+@RunWith(BlockJUnit4ClassRunner.class)
+@Category(AsyncLoggers.class)
+public class QueueFullAsyncLoggerTest3 extends QueueFullAbstractTest {
+
+ @BeforeClass
+ public static void beforeClass() {
+ //FORMAT_MESSAGES_IN_BACKGROUND
+ System.setProperty("log4j.format.msg.async", "true");
+ System.setProperty("log4j2.asyncQueueFullPolicy", "discard");
+
+ System.setProperty("AsyncLogger.RingBufferSize", "128"); // minimum ringbuffer size
+ System.setProperty(ConfigurationFactory.CONFIGURATION_FILE_PROPERTY,
+ "log4j2-queueFull.xml");
+ }
+
+ @AfterClass
+ public static void afterClass() {
+ System.setProperty(Constants.LOG4J_CONTEXT_SELECTOR, Strings.EMPTY);
+ }
+
+ @Rule
+ public LoggerContextRule context = new LoggerContextRule(
+ "log4j2-queueFull.xml", AsyncLoggerContextSelector.class);
+
+ @Before
+ public void before() throws Exception {
+ blockingAppender = context.getRequiredAppender("Blocking", BlockingAppender.class);
+ }
+
+
+ @Test(timeout = 15000)
+ public void discardedMessagesShouldBeGarbageCollected() throws InterruptedException {
+ final Logger logger = LogManager.getLogger(this.getClass());
+
+ blockingAppender.logEvents = null;
+ blockingAppender.countDownLatch = new CountDownLatch(1);
+ int count = 200;
+ CountDownLatch garbageCollectionLatch = new CountDownLatch(count);
+ for (int i = 0; i < count; i++) {
+ logger.info(new CountdownOnGarbageCollectMessage(garbageCollectionLatch));
+ }
+ blockingAppender.countDownLatch.countDown();
+
+ final GarbageCollectionHelper gcHelper = new GarbageCollectionHelper();
+ gcHelper.run();
+ try {
+ assertTrue("Parameter should have been garbage collected", garbageCollectionLatch.await(30, TimeUnit.SECONDS));
+ } finally {
+ gcHelper.close();
+ }
+ }
+
+ private static final class CountdownOnGarbageCollectMessage implements Message {
+
+ private final CountDownLatch latch;
+
+ CountdownOnGarbageCollectMessage(CountDownLatch latch) {
+ this.latch = latch;
+ }
+
+ @Override
+ public String getFormattedMessage() {
+ return "formatted";
+ }
+
+ @Override
+ public String getFormat() {
+ return null;
+ }
+
+ @Override
+ public Object[] getParameters() {
+ return new Object[0];
+ }
+
+ @Override
+ public Throwable getThrowable() {
+ return null;
+ }
+
+ @Override
+ protected void finalize() throws Throwable {
+ latch.countDown();
+ super.finalize();
+ }
+ }
+}
\ No newline at end of file
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/RingBufferLogEventTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/async/RingBufferLogEventTest.java
index bb891cd..07a02c7 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/RingBufferLogEventTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/async/RingBufferLogEventTest.java
@@ -173,7 +173,6 @@
assertEquals(evt.getLevel(), actual.getLevel());
assertEquals(evt.getMessage(), actual.getMessage());
assertEquals(evt.getThrown(), actual.getThrown());
- assertEquals(evt.getContextMap(), actual.getContextMap());
assertEquals(evt.getContextData(), actual.getContextData());
assertEquals(evt.getContextStack(), actual.getContextStack());
assertEquals(evt.getThreadName(), actual.getThreadName());
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/perftest/MultiThreadPerfTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/async/perftest/MultiThreadPerfTest.java
index 7b10b34..77f3fc1 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/async/perftest/MultiThreadPerfTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/async/perftest/MultiThreadPerfTest.java
@@ -107,4 +107,4 @@
PerfTest.reportResult(resultFile, name, histogram);
}
}
-}
\ No newline at end of file
+}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/config/AppenderControlArraySetTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/config/AppenderControlArraySetTest.java
index 58ac9ea..ac5679a 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/config/AppenderControlArraySetTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/config/AppenderControlArraySetTest.java
@@ -188,4 +188,4 @@
assertEquals(3, set.get().length);
assertArrayEquals(controls, set.get());
}
-}
\ No newline at end of file
+}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/config/CompositeConfigurationTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/config/CompositeConfigurationTest.java
index cb7b497..845573c 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/config/CompositeConfigurationTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/config/CompositeConfigurationTest.java
@@ -115,6 +115,9 @@
appendersMap.size());
assertTrue(appendersMap.get("File") instanceof FileAppender);
assertTrue(appendersMap.get("STDOUT") instanceof ConsoleAppender);
+
+ assertEquals("Expected COMPOSITE_SOURCE for composite configuration but got " + config.getConfigurationSource(),
+ config.getConfigurationSource(), ConfigurationSource.COMPOSITE_SOURCE);
}
};
runTest(lcr, test);
@@ -157,7 +160,7 @@
//Regression
//Check level on cat3 (not present in root config)
assertEquals("Expected cat3 log level to be ERROR", Level.ERROR, config.getLogger("cat3").getLevel());
- //Check level on cat1 (not present in overriden config)
+ //Check level on cat1 (not present in overridden config)
assertEquals("Expected cat1 log level to be DEBUG", Level.DEBUG, config.getLogger("cat1").getLevel());
}
};
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/config/ConfigurationSourceTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/config/ConfigurationSourceTest.java
new file mode 100644
index 0000000..5ca42f2
--- /dev/null
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/config/ConfigurationSourceTest.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.config;
+
+import java.io.ByteArrayInputStream;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class ConfigurationSourceTest {
+
+ @Test
+ public void testJira_LOG4J2_2770_byteArray() throws Exception {
+ ConfigurationSource configurationSource = new ConfigurationSource(
+ new ByteArrayInputStream(new byte[] { 'a', 'b' }));
+ Assert.assertNotNull(configurationSource.resetInputStream());
+ }
+}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/config/ConfiguratorTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/config/ConfiguratorTest.java
index 8a40d22..485c8cd 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/config/ConfiguratorTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/config/ConfiguratorTest.java
@@ -17,11 +17,15 @@
package org.apache.logging.log4j.core.config;
import java.io.File;
+import java.net.URI;
import org.apache.logging.log4j.core.LoggerContext;
import org.junit.Assert;
import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
public class ConfiguratorTest {
@Test
@@ -36,6 +40,31 @@
testInitializeFromFilePath(path);
}
+ @Test
+ public void testReconfigure() {
+ final String path = new File("src/test/resources/log4j-list.xml").getAbsolutePath();
+ try (final LoggerContext loggerContext = Configurator.initialize(getClass().getName(), null, path)) {
+ assertNotNull(loggerContext.getConfiguration().getAppender("List"));
+ URI uri = loggerContext.getConfigLocation();
+ assertNotNull("No configuration location returned", uri);
+ Configurator.reconfigure();
+ assertEquals("Unexpected configuration location returned", uri, loggerContext.getConfigLocation());
+ }
+ }
+
+ @Test
+ public void testReconfigureFromPath() {
+ final String path = new File("src/test/resources/log4j-list.xml").getAbsolutePath();
+ try (final LoggerContext loggerContext = Configurator.initialize(getClass().getName(), null, path)) {
+ assertNotNull(loggerContext.getConfiguration().getAppender("List"));
+ URI uri = loggerContext.getConfigLocation();
+ assertNotNull("No configuration location returned", uri);
+ final URI location = new File("src/test/resources/log4j2-config.xml").toURI();
+ Configurator.reconfigure(location);
+ assertEquals("Unexpected configuration location returned", location, loggerContext.getConfigLocation());
+ }
+ }
+
private void testInitializeFromFilePath(final String path) {
try (final LoggerContext loggerContext = Configurator.initialize(getClass().getName(), null, path)) {
Assert.assertNotNull(loggerContext.getConfiguration().getAppender("List"));
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/config/CustomConfigurationTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/config/CustomConfigurationTest.java
index d725043..1eb193c 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/config/CustomConfigurationTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/config/CustomConfigurationTest.java
@@ -76,18 +76,18 @@
}
}
final Layout<? extends Serializable> layout = PatternLayout.newBuilder()
- .withPattern(PatternLayout.SIMPLE_CONVERSION_PATTERN)
- .withConfiguration(config)
+ .setPattern(PatternLayout.SIMPLE_CONVERSION_PATTERN)
+ .setConfiguration(config)
.build();
// @formatter:off
final FileAppender appender = FileAppender.newBuilder()
- .withFileName(LOG_FILE)
- .withAppend(false)
- .withName("File")
- .withIgnoreExceptions(false)
- .withBufferSize(4000)
- .withBufferedIo(false)
- .withLayout(layout)
+ .setFileName(LOG_FILE)
+ .setAppend(false)
+ .setName("File")
+ .setIgnoreExceptions(false)
+ .setBufferSize(4000)
+ .setBufferedIo(false)
+ .setLayout(layout)
.build();
// @formatter:on
appender.start();
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/config/InMemoryAdvertiser.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/config/InMemoryAdvertiser.java
index d0823d3..875ec5f 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/config/InMemoryAdvertiser.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/config/InMemoryAdvertiser.java
@@ -20,7 +20,7 @@
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.plugins.Plugin;
import org.apache.logging.log4j.core.net.Advertiser;
@Plugin(name = "memory", category = Core.CATEGORY_NAME, elementType = "advertiser", printObject = false)
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/config/JiraLog4j2_2134Test.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/config/JiraLog4j2_2134Test.java
new file mode 100644
index 0000000..9b2cef3
--- /dev/null
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/config/JiraLog4j2_2134Test.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.config;
+
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.core.Appender;
+import org.apache.logging.log4j.core.LoggerContext;
+import org.apache.logging.log4j.core.appender.FileAppender;
+import org.apache.logging.log4j.core.layout.PatternLayout;
+import org.apache.logging.log4j.junit.LoggerContextRule;
+import org.junit.Rule;
+import org.junit.Test;
+
+public class JiraLog4j2_2134Test {
+
+ @Rule
+ public final LoggerContextRule loggerContextRule = new LoggerContextRule("src/test/resources/log4j2-2134.yml");
+
+ @Test
+ public void testRefresh() {
+ Logger log = LogManager.getLogger(this.getClass());
+ final LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
+ final Configuration config = ctx.getConfiguration();
+ PatternLayout layout = PatternLayout.newBuilder()
+ // @formatter:off
+ .setPattern(PatternLayout.SIMPLE_CONVERSION_PATTERN)
+ .setConfiguration(config)
+ .build();
+ // @formatter:on
+ Appender appender = FileAppender.newBuilder().setFileName("target/test.log").setLayout(layout)
+ .setConfiguration(config).setBufferSize(4000).setName("File").build();
+ // appender.start();
+ config.addAppender(appender);
+ AppenderRef ref = AppenderRef.createAppenderRef("File", null, null);
+ AppenderRef[] refs = new AppenderRef[] { ref };
+ LoggerConfig loggerConfig = LoggerConfig.createLogger(false, Level.INFO, "testlog4j2refresh", "true", refs,
+ null, config, null);
+ loggerConfig.addAppender(appender, null, null);
+ config.addLogger("testlog4j2refresh", loggerConfig);
+ ctx.stop();
+ ctx.start(config);
+
+ log.error("Info message");
+ }
+
+ @Test
+ public void testRefreshMinimalCodeStart() {
+ Logger log = LogManager.getLogger(this.getClass());
+ final LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
+ final Configuration config = ctx.getConfiguration();
+ ctx.start(config);
+
+ log.error("Info message");
+ }
+
+ @Test
+ public void testRefreshMinimalCodeStopStart() {
+ Logger log = LogManager.getLogger(this.getClass());
+ final LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
+ ctx.stop();
+ ctx.start();
+
+ log.error("Info message");
+ }
+
+ @Test
+ public void testRefreshMinimalCodeStopStartConfig() {
+ Logger log = LogManager.getLogger(this.getClass());
+ final LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
+ final Configuration config = ctx.getConfiguration();
+ ctx.stop();
+ ctx.start(config);
+
+ log.error("Info message");
+ }
+
+ @SuppressWarnings("deprecation")
+ @Test
+ public void testRefreshDeprecatedApis() {
+ Logger log = LogManager.getLogger(this.getClass());
+ final LoggerContext ctx = (LoggerContext) LogManager.getContext(false);
+ final Configuration config = ctx.getConfiguration();
+ PatternLayout layout = PatternLayout.newBuilder()
+ .setPattern(PatternLayout.SIMPLE_CONVERSION_PATTERN)
+ .setPatternSelector(null)
+ .setConfiguration(config)
+ .setRegexReplacement(null)
+ .setCharset(null)
+ .setAlwaysWriteExceptions(false)
+ .setNoConsoleNoAnsi(false)
+ .setHeader(null)
+ .setFooter(null)
+ .build();
+ // @formatter:off
+ Appender appender = FileAppender.newBuilder()
+ .setFileName("target/test.log")
+ .setAppend(false)
+ .setLocking(false)
+ .setName("File")
+ .setImmediateFlush(true)
+ .setIgnoreExceptions(false)
+ .setBufferedIo(false)
+ .setBufferSize(4000)
+ .setLayout(layout)
+ .setAdvertise(false)
+ .setConfiguration(config)
+ .build();
+ // @formatter:on
+ appender.start();
+ config.addAppender(appender);
+ AppenderRef ref = AppenderRef.createAppenderRef("File", null, null);
+ AppenderRef[] refs = new AppenderRef[] { ref };
+ LoggerConfig loggerConfig = LoggerConfig.createLogger(false, Level.INFO, "testlog4j2refresh", "true", refs,
+ null, config, null);
+ loggerConfig.addAppender(appender, null, null);
+ config.addLogger("testlog4j2refresh", loggerConfig);
+ ctx.stop();
+ ctx.start(config);
+
+ log.error("Info message");
+ }
+}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/config/LoggerConfigTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/config/LoggerConfigTest.java
index a7cceff..30de999 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/config/LoggerConfigTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/config/LoggerConfigTest.java
@@ -46,7 +46,6 @@
@Test
public void testPropertiesWithoutSubstitution() {
assertNull("null propertiesList", createForProperties(null).getPropertyList());
- assertNull("null property Map", createForProperties(null).getProperties());
final Property[] all = new Property[] {
Property.createProperty("key1", "value1"),
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/config/LoggersPluginTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/config/LoggersPluginTest.java
index 27c903f..dd9e1f6 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/config/LoggersPluginTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/config/LoggersPluginTest.java
@@ -47,4 +47,4 @@
assertEquals(Level.ERROR, data.getLevel());
assertTrue(data.getMessage().getFormattedMessage().contains("multiple root loggers"));
}
-}
\ No newline at end of file
+}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/LegacyPluginTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/LegacyPluginTest.java
new file mode 100644
index 0000000..3aad3c6
--- /dev/null
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/LegacyPluginTest.java
@@ -0,0 +1,62 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.logging.log4j.core.config.plugins;
+
+import org.apache.logging.log4j.core.Appender;
+import org.apache.logging.log4j.core.Layout;
+import org.apache.logging.log4j.core.config.Configuration;
+import org.apache.logging.log4j.core.config.Configurator;
+import org.apache.logging.log4j.core.config.xml.XmlConfiguration;
+import org.apache.logging.log4j.spi.LoggerContext;
+import org.junit.Test;
+
+import java.util.Map;
+
+import static org.junit.Assert.*;
+
+/**
+ * Class Description goes here.
+ */
+public class LegacyPluginTest {
+
+ private static final String CONFIG_FILE = "legacy-plugins.xml";
+
+ @Test
+ public void testLegacy() throws Exception {
+ LoggerContext context = Configurator.initialize("LegacyTest", null, CONFIG_FILE);
+ assertNotNull("No Logger Context", context);
+ Configuration configuration = ((org.apache.logging.log4j.core.LoggerContext) context).getConfiguration();
+ assertNotNull("No Configuration", configuration);
+ assertTrue("Incorrect Configuration class " + configuration.getClass().getName(),
+ configuration instanceof XmlConfiguration);
+ for (Map.Entry<String, Appender> entry : configuration.getAppenders().entrySet()) {
+ if (entry.getKey().equalsIgnoreCase("console")) {
+ Layout layout = entry.getValue().getLayout();
+ assertNotNull("No layout for Console Appender");
+ String name = layout.getClass().getSimpleName();
+ assertTrue("Incorrect Layout class. Expected LogstashLayout, Actual " + name,
+ name.equals("LogstashLayout"));
+ } else if (entry.getKey().equalsIgnoreCase("customConsole")) {
+ Layout layout = entry.getValue().getLayout();
+ assertNotNull("No layout for CustomConsole Appender");
+ String name = layout.getClass().getSimpleName();
+ assertTrue("Incorrect Layout class. Expected CustomConsoleLayout, Actual " + name,
+ name.equals("CustomConsoleLayout"));
+ }
+ }
+ }
+}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/convert/TypeConverterRegistryTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/convert/TypeConverterRegistryTest.java
deleted file mode 100644
index f9e757d..0000000
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/convert/TypeConverterRegistryTest.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache license, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the license for the specific language governing permissions and
- * limitations under the license.
- */
-package org.apache.logging.log4j.core.config.plugins.convert;
-
-import org.junit.Test;
-
-import static org.hamcrest.Matchers.instanceOf;
-import static org.junit.Assert.*;
-
-public class TypeConverterRegistryTest {
-
- @Test(expected = NullPointerException.class)
- public void testFindNullConverter() throws Exception {
- TypeConverterRegistry.getInstance().findCompatibleConverter(null);
- }
-
- @Test
- public void testFindBooleanConverter() throws Exception {
- final TypeConverter<?> converter = TypeConverterRegistry.getInstance().findCompatibleConverter(Boolean.class);
- assertNotNull(converter);
- assertTrue((Boolean) converter.convert("TRUE"));
- }
-
- @Test
- public void testFindPrimitiveBooleanConverter() throws Exception {
- final TypeConverter<?> converter = TypeConverterRegistry.getInstance().findCompatibleConverter(Boolean.TYPE);
- assertNotNull(converter);
- assertTrue((Boolean) converter.convert("tRUe"));
- }
-
- @SuppressWarnings("unchecked")
- @Test
- public void testFindCharSequenceConverterUsingStringConverter() throws Exception {
- final TypeConverter<CharSequence> converter = (TypeConverter<CharSequence>)
- 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
- public void testFindNumberConverter() throws Exception {
- final TypeConverter<Number> numberTypeConverter = (TypeConverter<Number>)
- TypeConverterRegistry.getInstance().findCompatibleConverter(Number.class);
- assertNotNull(numberTypeConverter);
- // TODO: is there a specific converter this should return?
- }
-
- public static enum Foo {
- I, PITY, THE
- }
-
- @SuppressWarnings("unchecked")
- @Test
- public void testFindEnumConverter() throws Exception {
- final TypeConverter<Foo> fooTypeConverter = (TypeConverter<Foo>)
- 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/src/test/java/org/apache/logging/log4j/core/config/plugins/convert/TypeConvertersTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/convert/TypeConvertersTest.java
index 35a3855..37a66f5 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/convert/TypeConvertersTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/convert/TypeConvertersTest.java
@@ -38,6 +38,7 @@
import org.apache.logging.log4j.core.appender.rolling.action.Duration;
import org.apache.logging.log4j.core.layout.GelfLayout;
import org.apache.logging.log4j.core.net.Facility;
+import org.apache.logging.log4j.plugins.convert.TypeConverters;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -45,7 +46,7 @@
import static org.junit.Assert.*;
/**
- * Tests {@link TypeConverters}.
+ * Tests {@link CoreTypeConverters}.
*/
@RunWith(Parameterized.class)
public class TypeConvertersTest {
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/processor/FakePlugin.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/processor/FakePlugin.java
deleted file mode 100644
index f4ceb41..0000000
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/processor/FakePlugin.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache license, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the license for the specific language governing permissions and
- * limitations under the license.
- */
-
-package org.apache.logging.log4j.core.config.plugins.processor;
-
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginAliases;
-
-/**
- * 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 {
- }
-}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/processor/PluginProcessorTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/processor/PluginProcessorTest.java
deleted file mode 100644
index 9c37af4..0000000
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/processor/PluginProcessorTest.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache license, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the license for the specific language governing permissions and
- * limitations under the license.
- */
-
-package org.apache.logging.log4j.core.config.plugins.processor;
-
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginAliases;
-import org.junit.BeforeClass;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-import java.net.URL;
-import java.util.Enumeration;
-import java.util.Map;
-
-import static org.junit.Assert.*;
-
-@RunWith(JUnit4.class)
-public class PluginProcessorTest {
-
- private static final PluginCache pluginCache = new PluginCache();
-
- private final Plugin p = FakePlugin.class.getAnnotation(Plugin.class);
-
- @BeforeClass
- public static void setUpClass() throws Exception {
- final Enumeration<URL> resources =
- PluginProcessor.class.getClassLoader().getResources(PluginProcessor.PLUGIN_CACHE_FILE);
- pluginCache.loadCacheFiles(resources);
- }
-
- @Test
- public void testTestCategoryFound() throws Exception {
- assertNotNull("No plugin annotation on FakePlugin.", p);
- final Map<String, PluginEntry> testCategory = pluginCache.getCategory(p.category());
- assertNotEquals("No plugins were found.", 0, pluginCache.size());
- assertNotNull("The category '" + p.category() + "' was not found.", testCategory);
- assertFalse(testCategory.isEmpty());
- }
-
- @Test
- public void testFakePluginFoundWithCorrectInformation() throws Exception {
- final PluginEntry fake = pluginCache.getCategory(p.category()).get(p.name().toLowerCase());
- verifyFakePluginEntry(p.name(), fake);
- }
-
- @Test
- public void testFakePluginAliasesContainSameInformation() throws Exception {
- final PluginAliases aliases = FakePlugin.class.getAnnotation(PluginAliases.class);
- for (final String alias : aliases.value()) {
- final PluginEntry fake = pluginCache.getCategory(p.category()).get(alias.toLowerCase());
- verifyFakePluginEntry(alias, fake);
- }
- }
-
- private void verifyFakePluginEntry(final String name, final PluginEntry fake) {
- assertNotNull("The plugin '" + name.toLowerCase() + "' was not found.", fake);
- assertEquals(FakePlugin.class.getName(), fake.getClassName());
- assertEquals(name.toLowerCase(), fake.getKey());
- assertEquals(Plugin.EMPTY, p.elementType());
- assertEquals(name, fake.getName());
- assertEquals(p.printObject(), fake.isPrintable());
- assertEquals(p.deferChildren(), fake.isDefer());
- }
-
- @Test
- public void testNestedPlugin() throws Exception {
- final Plugin p = FakePlugin.Nested.class.getAnnotation(Plugin.class);
- final PluginEntry nested = pluginCache.getCategory(p.category()).get(p.name().toLowerCase());
- assertNotNull(nested);
- assertEquals(p.name().toLowerCase(), 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/src/test/java/org/apache/logging/log4j/core/config/plugins/util/PluginManagerPackagesTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/util/PluginManagerPackagesTest.java
index c97643f..c55b45e 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/util/PluginManagerPackagesTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/util/PluginManagerPackagesTest.java
@@ -64,9 +64,9 @@
// So we don't create the custom plugin class until this test is run.
final File orig = new File("target/test-classes/customplugin/FixedStringLayout.java.source");
final File f = new File(orig.getParentFile(), "FixedStringLayout.java");
- assertTrue("renamed source file OK", orig.renameTo(f));
+ assertTrue("renamed source file failed", orig.renameTo(f));
compile(f);
- assertTrue("reverted source file OK", f.renameTo(orig));
+ assertTrue("reverted source file failed", f.renameTo(orig));
// load the compiled class
Class.forName("customplugin.FixedStringLayout");
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/util/ResolverUtilCustomProtocolTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/util/ResolverUtilCustomProtocolTest.java
deleted file mode 100644
index 33e0ee1..0000000
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/util/ResolverUtilCustomProtocolTest.java
+++ /dev/null
@@ -1,210 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache license, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the license for the specific language governing permissions and
- * limitations under the license.
- */
-
-package org.apache.logging.log4j.core.config.plugins.util;
-
-import static org.junit.Assert.assertEquals;
-
-import java.io.IOException;
-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 org.apache.logging.log4j.core.config.plugins.util.PluginRegistry.PluginTest;
-import org.apache.logging.log4j.junit.CleanFolders;
-import org.apache.logging.log4j.junit.URLStreamHandlerFactoryRule;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.RuleChain;
-
-/**
- * Tests the ResolverUtil class for custom protocol like bundleresource, vfs, vfszip.
- */
-public class ResolverUtilCustomProtocolTest {
-
- @Rule
- public URLStreamHandlerFactoryRule rule = new URLStreamHandlerFactoryRule(new NoopURLStreamHandlerFactory());
-
- @Rule
- public RuleChain chain = RuleChain.outerRule(new CleanFolders(ResolverUtilTest.WORK_DIR));
-
- 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() throws IOException {
- // do nothing
- }
- };
- }
-
- @Override
- protected URLConnection openConnection(final URL url, final Proxy proxy) {
- return open(url, proxy);
- }
-
- @Override
- protected int getDefaultPort() {
- return 1;
- }
- };
- }
- }
-
- 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<URL> getResources(final String name) throws IOException {
- return findResources(name);
- }
-
- @Override
- protected Enumeration<URL> findResources(final String name) throws IOException {
- return Collections.enumeration(Arrays.asList(findResource(name)));
- }
- }
-
- @Test
- public void testExtractPathFromVfsEarJarWindowsUrl() throws Exception {
- final URL url = new URL(
- "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/");
- final String expected = "/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/";
- assertEquals(expected, new ResolverUtil().extractPath(url));
- }
-
- @Test
- public void testExtractPathFromVfsWarClassesWindowsUrl() throws Exception {
- final URL url = new URL(
- "vfs:/C:/jboss/jboss-eap-6.4/standalone/deployments/test-log4j2-web-standalone.war/WEB-INF/classes/org/hypik/test/jboss/eap7/logging/config/");
- final String expected = "/C:/jboss/jboss-eap-6.4/standalone/deployments/test-log4j2-web-standalone.war/WEB-INF/classes/org/hypik/test/jboss/eap7/logging/config/";
- assertEquals(expected, new ResolverUtil().extractPath(url));
- }
-
- @Test
- public void testExtractPathFromVfsWarClassesLinuxUrl() throws Exception {
- final URL url = new URL(
- "vfs:/content/mycustomweb.war/WEB-INF/classes/org/hypik/test/jboss/log4j2/logging/pluginweb/");
- final String expected = "/content/mycustomweb.war/WEB-INF/classes/org/hypik/test/jboss/log4j2/logging/pluginweb/";
- assertEquals(expected, new ResolverUtil().extractPath(url));
- }
-
- @Test
- public void testExtractPathFromVfszipUrl() throws Exception {
- final URL url = new URL(
- "vfszip:/home2/jboss-5.0.1.CR2/jboss-as/server/ais/ais-deploy/myear.ear/mywar.war/WEB-INF/some.xsd");
- final String expected = "/home2/jboss-5.0.1.CR2/jboss-as/server/ais/ais-deploy/myear.ear/mywar.war/WEB-INF/some.xsd";
- assertEquals(expected, new ResolverUtil().extractPath(url));
- }
-
- @Test
- public void testExtractPathFromVfsEarJarLinuxUrl() throws Exception {
- final URL url = new URL(
- "vfs:/content/test-log4k2-ear.ear/lib/test-log4j2-jar-plugins.jar/org/hypik/test/jboss/log4j2/pluginjar/");
- final String expected = "/content/test-log4k2-ear.ear/lib/test-log4j2-jar-plugins.jar/org/hypik/test/jboss/log4j2/pluginjar/";
- assertEquals(expected, new ResolverUtil().extractPath(url));
- }
-
- @Test
- public void testExtractPathFromVfszipUrlWithPlusCharacters() throws Exception {
- final URL url = new URL("vfszip:/path+with+plus/file+name+with+plus.xml");
- final String expected = "/path+with+plus/file+name+with+plus.xml";
- assertEquals(expected, new ResolverUtil().extractPath(url));
- }
-
- @Test
- public void testExtractPathFromVfsUrlWithPlusCharacters() throws Exception {
- final URL url = new URL("vfs:/path+with+plus/file+name+with+plus.xml");
- final String expected = "/path+with+plus/file+name+with+plus.xml";
- assertEquals(expected, new ResolverUtil().extractPath(url));
- }
-
- @Test
- public void testExtractPathFromResourceBundleUrl() throws Exception {
- final URL url = new URL("bundleresource:/some/path/some/file.properties");
- final String expected = "/some/path/some/file.properties";
- assertEquals(expected, new ResolverUtil().extractPath(url));
- }
-
- @Test
- public void testExtractPathFromResourceBundleUrlWithPlusCharacters() throws Exception {
- final URL url = new URL("bundleresource:/some+path/some+file.properties");
- final String expected = "/some+path/some+file.properties";
- assertEquals(expected, new ResolverUtil().extractPath(url));
- }
-
- @Test
- public void testFindInPackageFromVfsDirectoryURL() throws Exception {
- try (final URLClassLoader cl = ResolverUtilTest.compileAndCreateClassLoader("3")) {
- final ResolverUtil resolverUtil = new ResolverUtil();
- resolverUtil
- .setClassLoader(new SingleURLClassLoader(new URL("vfs:/" + ResolverUtilTest.WORK_DIR + "/resolverutil3/customplugin3/"), cl));
- resolverUtil.findInPackage(new PluginTest(), "customplugin3");
- assertEquals("Class not found in packages", 1, resolverUtil.getClasses().size());
- assertEquals("Unexpected class resolved", cl.loadClass("customplugin3.FixedString3Layout"),
- resolverUtil.getClasses().iterator().next());
- }
- }
-
- @Test
- public void testFindInPackageFromVfsJarURL() throws Exception {
- try (final URLClassLoader cl = ResolverUtilTest.compileJarAndCreateClassLoader("4")) {
- final ResolverUtil resolverUtil = new ResolverUtil();
- resolverUtil.setClassLoader(new SingleURLClassLoader(
- new URL("vfs:/" + ResolverUtilTest.WORK_DIR + "/resolverutil4/customplugin4.jar/customplugin4/"), cl));
- resolverUtil.findInPackage(new PluginTest(), "customplugin4");
- assertEquals("Class not found in packages", 1, resolverUtil.getClasses().size());
- assertEquals("Unexpected class resolved", cl.loadClass("customplugin4.FixedString4Layout"),
- resolverUtil.getClasses().iterator().next());
- }
- }
-
-}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/util/ResolverUtilTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/util/ResolverUtilTest.java
deleted file mode 100644
index 1c6371b..0000000
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/util/ResolverUtilTest.java
+++ /dev/null
@@ -1,221 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache license, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the license for the specific language governing permissions and
- * limitations under the license.
- */
-
-package org.apache.logging.log4j.core.config.plugins.util;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-import java.io.File;
-import java.io.IOException;
-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 org.apache.logging.log4j.core.config.plugins.util.PluginRegistry.PluginTest;
-import org.apache.logging.log4j.junit.CleanFolders;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.RuleChain;
-
-/**
- * Tests the ResolverUtil class.
- */
-public class ResolverUtilTest {
-
- static final String WORK_DIR = "target/testpluginsutil";
-
- @Rule
- public RuleChain chain = RuleChain.outerRule(new CleanFolders(WORK_DIR));
-
- @Test
- public void testExtractPathFromJarUrl() throws Exception {
- final URL url = new URL("jar:file:/C:/Users/me/.m2/repository/junit/junit/4.11/junit-4.11.jar!/org/junit/Test.class");
- final String expected = "/C:/Users/me/.m2/repository/junit/junit/4.11/junit-4.11.jar";
- assertEquals(expected, new ResolverUtil().extractPath(url));
- }
-
- @Test
- public 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 (!url.getProtocol().equals("jar")) {
- // create fake jar: URL that resolves to existing file
- url = new URL("jar:" + url.toExternalForm() + "!/some/entry");
- }
- final String actual = new ResolverUtil().extractPath(url);
- assertTrue("should not be decoded: " + actual, actual.endsWith(existingFile));
- }
-
- @Test
- public void testFileFromUriWithSpacesAndPlusCharactersInName() throws Exception {
- final String existingFile = "/s p a c e s/log4j+config+with+plus+characters.xml";
- testExtractPathFromJarUrlNotDecodedIfFileExists(existingFile);
- }
-
- @Test
- public void testExtractPathFromJarUrlDecodedIfFileDoesNotExist() throws Exception {
- final URL url = new URL("jar:file:/path+with+plus/file+does+not+exist.jar!/some/file");
- final String expected = "/path with plus/file does not exist.jar";
- assertEquals(expected, new ResolverUtil().extractPath(url));
- }
-
- @Test
- public void testExtractPathFromFileUrl() throws Exception {
- final URL url = new URL("file:/C:/Users/me/workspace/log4j2/log4j-core/target/test-classes/log4j2-config.xml");
- final String expected = "/C:/Users/me/workspace/log4j2/log4j-core/target/test-classes/log4j2-config.xml";
- assertEquals(expected, new ResolverUtil().extractPath(url));
- }
-
- @Test
- public void testExtractPathFromFileUrlNotDecodedIfFileExists() throws Exception {
- final String existingFile = "/log4j+config+with+plus+characters.xml";
- final URL url = ResolverUtilTest.class.getResource(existingFile);
- assertTrue("should be file url but was " + url, "file".equals(url.getProtocol()));
-
- final String actual = new ResolverUtil().extractPath(url);
- assertTrue("should not be decoded: " + actual, actual.endsWith(existingFile));
- }
-
- @Test
- public void testExtractPathFromFileUrlDecodedIfFileDoesNotExist() throws Exception {
- final URL url = new URL("file:///path+with+plus/file+does+not+exist.xml");
- final String expected = "/path with plus/file does not exist.xml";
- assertEquals(expected, new ResolverUtil().extractPath(url));
- }
-
- @Test
- public void testExtractPathFromHttpUrl() throws Exception {
- final URL url = new URL("http://java.sun.com/index.html#chapter1");
- final String expected = "/index.html";
- assertEquals(expected, new ResolverUtil().extractPath(url));
- }
-
- @Test
- public void testExtractPathFromHttpUrlWithPlusCharacters() throws Exception {
- final URL url = new URL("http://www.server.com/path+with+plus/file+name+with+plus.jar!/org/junit/Test.class");
- final String expected = "/path with plus/file name with plus.jar";
- assertEquals(expected, new ResolverUtil().extractPath(url));
- }
-
- @Test
- public void testExtractPathFromHttpsComplexUrl() throws Exception {
- final URL url = new URL("https://issues.apache.org/jira/browse/LOG4J2-445?focusedCommentId=13862479&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-13862479");
- final String expected = "/jira/browse/LOG4J2-445";
- assertEquals(expected, new ResolverUtil().extractPath(url));
- }
-
- @Test
- public void testExtractPathFromFtpUrl() throws Exception {
- final URL url = new URL("ftp://user001:secretpassword@private.ftp-servers.example.com/mydirectory/myfile.txt");
- final String expected = "/mydirectory/myfile.txt";
- assertEquals(expected, new ResolverUtil().extractPath(url));
- }
-
- @Test
- public void testExtractPathFromFtpUrlWithPlusCharacters() throws Exception {
- final URL url = new URL("ftp://user001:secretpassword@private.ftp-servers.example.com/my+directory/my+file.txt");
- final String expected = "/my directory/my file.txt";
- assertEquals(expected, new ResolverUtil().extractPath(url));
- }
-
- @Test
- public void testFindInPackageFromDirectoryPath() throws Exception {
- try (final URLClassLoader cl = compileAndCreateClassLoader("1")) {
- final ResolverUtil resolverUtil = new ResolverUtil();
- resolverUtil.setClassLoader(cl);
- resolverUtil.findInPackage(new PluginTest(), "customplugin1");
- assertEquals("Class not found in packages", 1, resolverUtil.getClasses().size());
- assertEquals("Unexpected class resolved", cl.loadClass("customplugin1.FixedString1Layout"),
- resolverUtil.getClasses().iterator().next());
- }
- }
-
- @Test
- public void testFindInPackageFromJarPath() throws Exception {
- try (final URLClassLoader cl = compileJarAndCreateClassLoader("2")) {
- final ResolverUtil resolverUtil = new ResolverUtil();
- resolverUtil.setClassLoader(cl);
- resolverUtil.findInPackage(new PluginTest(), "customplugin2");
- assertEquals("Class not found in packages", 1, resolverUtil.getClasses().size());
- assertEquals("Unexpected class resolved", cl.loadClass("customplugin2.FixedString2Layout"),
- resolverUtil.getClasses().iterator().next());
- }
- }
-
- static URLClassLoader compileJarAndCreateClassLoader(final String suffix) throws IOException, Exception {
- final File workDir = compile(suffix);
- final File jarFile = new File(workDir, "customplugin" + suffix + ".jar");
- final URI jarURI = jarFile.toURI();
- createJar(jarURI, workDir, new File(workDir,
- "customplugin" + suffix + "/FixedString" + suffix + "Layout.class"));
- return URLClassLoader.newInstance(new URL[] {jarURI.toURL()});
- }
-
- static URLClassLoader compileAndCreateClassLoader(final String suffix) throws IOException {
- final File workDir = compile(suffix);
- return URLClassLoader.newInstance(new URL[] {workDir.toURI().toURL()});
- }
-
- static File compile(final String suffix) throws IOException {
- final File orig = new File("target/test-classes/customplugin/FixedStringLayout.java.source");
- final File workDir = new File(WORK_DIR, "resolverutil" + suffix);
- final File f = new File(workDir, "customplugin" + suffix + "/FixedString" + suffix + "Layout.java");
- final File parent = f.getParentFile();
- if (!parent.exists()) {
- assertTrue("Create customplugin" + suffix + " folder KO", f.getParentFile().mkdirs());
- }
-
- final String content = new String(Files.readAllBytes(orig.toPath()))
- .replaceAll("FixedString", "FixedString" + suffix)
- .replaceAll("customplugin", "customplugin" + suffix);
- Files.write(f.toPath(), content.getBytes());
-
- PluginManagerPackagesTest.compile(f);
- return workDir;
- }
-
- static void createJar(final URI jarURI, final File workDir, final File f) throws Exception {
- final Map<String, String> env = new HashMap<>();
- env.put("create", "true");
- final URI uri = URI.create("jar:file://" + jarURI.getRawPath());
- try (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/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/AbstractPluginWithGenericBuilder.java
deleted file mode 100644
index 7b2a0f8..0000000
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/AbstractPluginWithGenericBuilder.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache license, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the license for the specific language governing permissions and
- * limitations under the license.
- */
-package org.apache.logging.log4j.core.config.plugins.validation;
-
-import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
-import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required;
-
-/**
- *
- */
-public class AbstractPluginWithGenericBuilder {
-
- public static abstract class Builder<B extends Builder<B>> {
-
- @PluginBuilderAttribute
- @Required(message = "The thing given by the builder is null")
- private String thing;
-
- @SuppressWarnings("unchecked")
- public B asBuilder() {
- return (B) this;
- }
-
- public String getThing() {
- return thing;
- }
-
- public B withThing(final String name) {
- this.thing = name;
- return asBuilder();
- }
-
- }
-
- private final String thing;
-
- public AbstractPluginWithGenericBuilder(final String thing) {
- super();
- this.thing = thing;
- }
-
- public String getThing() {
- return thing;
- }
-}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/HostAndPort.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/HostAndPort.java
deleted file mode 100644
index 34123c0..0000000
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/HostAndPort.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache license, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the license for the specific language governing permissions and
- * limitations under the license.
- */
-package org.apache.logging.log4j.core.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 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/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/PluginWithGenericSubclassFoo1Builder.java
deleted file mode 100644
index aed8eee..0000000
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/PluginWithGenericSubclassFoo1Builder.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.plugins.validation;
-
-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;
-
-@Plugin(name = "PluginWithGenericSubclassFoo1Builder", category = "Test")
-public class PluginWithGenericSubclassFoo1Builder extends AbstractPluginWithGenericBuilder {
-
- public static class Builder<B extends Builder<B>> extends AbstractPluginWithGenericBuilder.Builder<B>
- implements org.apache.logging.log4j.core.util.Builder<PluginWithGenericSubclassFoo1Builder> {
-
- @PluginBuilderFactory
- public static <B extends Builder<B>> B newBuilder() {
- return new Builder<B>().asBuilder();
- }
-
- @PluginBuilderAttribute
- @Required(message = "The foo1 given by the builder is null")
- private String foo1;
-
- @Override
- public PluginWithGenericSubclassFoo1Builder build() {
- return new PluginWithGenericSubclassFoo1Builder(getThing(), getFoo1());
- }
-
- public String getFoo1() {
- return foo1;
- }
-
- public B withFoo1(final String foo1) {
- this.foo1 = foo1;
- return asBuilder();
- }
-
- }
-
- @PluginBuilderFactory
- public static <B extends Builder<B>> B newBuilder() {
- return new Builder<B>().asBuilder();
- }
-
- private final String foo1;
-
- public PluginWithGenericSubclassFoo1Builder(final String thing, final String foo1) {
- super(thing);
- this.foo1 = foo1;
- }
-
- 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/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/ValidatingPlugin.java
deleted file mode 100644
index 6068f46..0000000
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/ValidatingPlugin.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache license, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the license for the specific language governing permissions and
- * limitations under the license.
- */
-package org.apache.logging.log4j.core.config.plugins.validation;
-
-import java.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;
-import org.apache.logging.log4j.core.config.plugins.PluginFactory;
-import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required;
-
-/**
- *
- */
-@Plugin(name = "Validator", category = "Test")
-public class ValidatingPlugin {
-
- private final String name;
-
- public ValidatingPlugin(final String name) {
- this.name = Objects.requireNonNull(name, "name");
- }
-
- public String getName() {
- return name;
- }
-
- @PluginFactory
- public static ValidatingPlugin newValidatingPlugin(
- @Required(message = "The name given by the factory is null") final String name) {
- return new ValidatingPlugin(name);
- }
-
- @PluginBuilderFactory
- public static Builder newBuilder() {
- return new Builder();
- }
-
- public static class Builder implements org.apache.logging.log4j.core.util.Builder<ValidatingPlugin> {
-
- @PluginBuilderAttribute
- @Required(message = "The name given by the builder is null")
- private String name;
-
- public Builder withName(final String name) {
- this.name = name;
- return this;
- }
-
- @Override
- public ValidatingPlugin build() {
- return new ValidatingPlugin(name);
- }
- }
-}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/ValidatingPluginWithGenericBuilder.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/ValidatingPluginWithGenericBuilder.java
deleted file mode 100644
index 5ca9b6e..0000000
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/ValidatingPluginWithGenericBuilder.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.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;
-import org.apache.logging.log4j.core.config.plugins.PluginFactory;
-import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required;
-
-/**
- *
- */
-@Plugin(name = "ValidatingPluginWithGenericBuilder", category = "Test")
-public class ValidatingPluginWithGenericBuilder {
-
- private final String name;
-
- public ValidatingPluginWithGenericBuilder(final String name) {
- this.name = Objects.requireNonNull(name, "name");
- }
-
- public String getName() {
- return name;
- }
-
- @PluginFactory
- public static ValidatingPluginWithGenericBuilder newValidatingPlugin(
- @Required(message = "The name given by the factory is null") final String name) {
- return new ValidatingPluginWithGenericBuilder(name);
- }
-
- @PluginBuilderFactory
- public static <B extends Builder<B>> B newBuilder() {
- return new Builder<B>().asBuilder();
- }
-
- public static class Builder<B extends Builder<B>> implements org.apache.logging.log4j.core.util.Builder<ValidatingPluginWithGenericBuilder> {
-
- @PluginBuilderAttribute
- @Required(message = "The name given by the builder is null")
- private String name;
-
- public B withName(final String name) {
- this.name = name;
- return asBuilder();
- }
-
- @SuppressWarnings("unchecked")
- public B asBuilder() {
- return (B) this;
- }
-
- @Override
- public ValidatingPluginWithGenericBuilder build() {
- return new ValidatingPluginWithGenericBuilder(name);
- }
- }
-}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/ValidatingPluginWithTypedBuilder.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/ValidatingPluginWithTypedBuilder.java
deleted file mode 100644
index 9ebb85e..0000000
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/ValidatingPluginWithTypedBuilder.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache license, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the license for the specific language governing permissions and
- * limitations under the license.
- */
-package org.apache.logging.log4j.core.config.plugins.validation;
-
-import java.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;
-import org.apache.logging.log4j.core.config.plugins.PluginFactory;
-import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required;
-
-/**
- *
- */
-@Plugin(name = "ValidatingPluginWithTypedBuilder", category = "Test")
-public class ValidatingPluginWithTypedBuilder {
-
- private final String name;
-
- public ValidatingPluginWithTypedBuilder(final String name) {
- this.name = Objects.requireNonNull(name, "name");
- }
-
- public String getName() {
- return name;
- }
-
- @PluginFactory
- public static ValidatingPluginWithTypedBuilder newValidatingPlugin(
- @Required(message = "The name given by the factory is null") final String name) {
- return new ValidatingPluginWithTypedBuilder(name);
- }
-
- @PluginBuilderFactory
- public static Builder<Integer> newBuilder() {
- return new Builder<>();
- }
-
- public static class Builder<T> implements org.apache.logging.log4j.core.util.Builder<ValidatingPluginWithTypedBuilder> {
-
- @PluginBuilderAttribute
- @Required(message = "The name given by the builder is null")
- private String name;
-
- public Builder<T> withName(final String name) {
- this.name = name;
- return this;
- }
-
- @Override
- public ValidatingPluginWithTypedBuilder build() {
- return new ValidatingPluginWithTypedBuilder(name);
- }
- }
-}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/validators/RequiredValidatorTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/validators/RequiredValidatorTest.java
index 15a51ae..c2dd9d4 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/validators/RequiredValidatorTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/validators/RequiredValidatorTest.java
@@ -16,12 +16,12 @@
*/
package org.apache.logging.log4j.core.config.plugins.validation.validators;
-import org.apache.logging.log4j.core.config.Node;
+import org.apache.logging.log4j.plugins.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.apache.logging.log4j.plugins.util.PluginManager;
+import org.apache.logging.log4j.plugins.util.PluginType;
+import org.apache.logging.log4j.plugins.validation.ValidatingPlugin;
import org.junit.Before;
import org.junit.Test;
@@ -38,15 +38,15 @@
final PluginManager manager = new PluginManager("Test");
manager.collectPlugins();
plugin = (PluginType<ValidatingPlugin>) manager.getPluginType("Validator");
- assertNotNull("Rebuild this module to make sure annotaion processing kicks in.", plugin);
+ assertNotNull("Rebuild this module to make sure annotation processing kicks in.", plugin);
node = new Node(null, "Validator", plugin);
}
@Test
public void testNullDefaultValue() throws Exception {
final ValidatingPlugin validatingPlugin = (ValidatingPlugin) new PluginBuilder(plugin)
- .withConfiguration(new NullConfiguration())
- .withConfigurationNode(node)
+ .setConfiguration(new NullConfiguration())
+ .setConfigurationNode(node)
.build();
assertNull(validatingPlugin);
}
@@ -55,8 +55,8 @@
public void testNonNullValue() throws Exception {
node.getAttributes().put("name", "foo");
final ValidatingPlugin validatingPlugin = (ValidatingPlugin) new PluginBuilder(plugin)
- .withConfiguration(new NullConfiguration())
- .withConfigurationNode(node)
+ .setConfiguration(new NullConfiguration())
+ .setConfigurationNode(node)
.build();
assertNotNull(validatingPlugin);
assertEquals("foo", validatingPlugin.getName());
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/validators/ValidHostValidatorTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/validators/ValidHostValidatorTest.java
index 3a5f534..049cc23 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/validators/ValidHostValidatorTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/validators/ValidHostValidatorTest.java
@@ -17,12 +17,12 @@
package org.apache.logging.log4j.core.config.plugins.validation.validators;
import org.apache.logging.log4j.Level;
-import org.apache.logging.log4j.core.config.Node;
+import org.apache.logging.log4j.plugins.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.plugins.util.PluginManager;
+import org.apache.logging.log4j.plugins.util.PluginType;
+import org.apache.logging.log4j.plugins.validation.HostAndPort;
import org.apache.logging.log4j.junit.StatusLoggerRule;
import org.junit.Before;
import org.junit.Rule;
@@ -72,8 +72,8 @@
private HostAndPort buildPlugin() {
return (HostAndPort) new PluginBuilder(plugin)
- .withConfiguration(new NullConfiguration())
- .withConfigurationNode(node)
+ .setConfiguration(new NullConfiguration())
+ .setConfigurationNode(node)
.build();
}
-}
\ No newline at end of file
+}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/validators/ValidPortValidatorTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/validators/ValidPortValidatorTest.java
index 3aab08d..208010a 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/validators/ValidPortValidatorTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/validators/ValidPortValidatorTest.java
@@ -16,12 +16,12 @@
*/
package org.apache.logging.log4j.core.config.plugins.validation.validators;
-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.plugins.Node;
+import org.apache.logging.log4j.plugins.util.PluginManager;
+import org.apache.logging.log4j.plugins.util.PluginType;
+import org.apache.logging.log4j.plugins.validation.HostAndPort;
import org.junit.Before;
import org.junit.Test;
@@ -62,9 +62,9 @@
private HostAndPort buildPlugin() {
return (HostAndPort) new PluginBuilder(plugin)
- .withConfiguration(new NullConfiguration())
- .withConfigurationNode(node)
+ .setConfiguration(new NullConfiguration())
+ .setConfigurationNode(node)
.build();
}
-}
\ No newline at end of file
+}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/validators/ValidatingPluginWithFailoverTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/validators/ValidatingPluginWithFailoverTest.java
new file mode 100644
index 0000000..744231f
--- /dev/null
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/validators/ValidatingPluginWithFailoverTest.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.config.plugins.validation.validators;
+
+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.NullConfiguration;
+import org.apache.logging.log4j.core.config.plugins.util.PluginBuilder;
+import org.apache.logging.log4j.plugins.Node;
+import org.apache.logging.log4j.plugins.util.PluginManager;
+import org.apache.logging.log4j.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.Before;
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.hamcrest.Matchers.emptyCollectionOf;
+import static org.junit.Assert.*;
+
+public class ValidatingPluginWithFailoverTest {
+
+ private PluginType<FailoverAppender> plugin;
+ private Node node;
+
+ @SuppressWarnings("unchecked")
+ @Before
+ public void setUp() throws Exception {
+ final PluginManager manager = new PluginManager(Core.CATEGORY_NAME);
+ manager.collectPlugins();
+ plugin = (PluginType<FailoverAppender>) manager.getPluginType("failover");
+ assertNotNull("Rebuild this module to make sure annotation processing kicks in.", plugin);
+
+ AppenderRef appenderRef = AppenderRef.createAppenderRef("List", Level.ALL, null);
+ node = new Node(null, "failover", plugin);
+ Node failoversNode = new Node(node, "Failovers", manager.getPluginType("Failovers"));
+ 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
+ public void testDoesNotLog_NoParameterThatMatchesElement_message() {
+ final StoringStatusListener listener = new StoringStatusListener();
+ // @formatter:off
+ final PluginBuilder builder = new PluginBuilder(plugin).
+ setConfiguration(new NullConfiguration()).
+ setConfigurationNode(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<StatusData> logs = new ArrayList<>();
+ @Override
+ public void log(StatusData data) {
+ logs.add(data);
+ }
+
+ @Override
+ public Level getStatusLevel() {
+ return Level.WARN;
+ }
+
+ @Override
+ public void close() {}
+ }
+}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/validators/ValidatingPluginWithGenericBuilderTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/validators/ValidatingPluginWithGenericBuilderTest.java
index 8e02f20..814ac68 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/validators/ValidatingPluginWithGenericBuilderTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/validators/ValidatingPluginWithGenericBuilderTest.java
@@ -20,12 +20,12 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
-import org.apache.logging.log4j.core.config.Node;
+import org.apache.logging.log4j.plugins.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.apache.logging.log4j.plugins.util.PluginManager;
+import org.apache.logging.log4j.plugins.util.PluginType;
+import org.apache.logging.log4j.plugins.validation.ValidatingPluginWithGenericBuilder;
import org.junit.Before;
import org.junit.Test;
@@ -40,15 +40,15 @@
final PluginManager manager = new PluginManager("Test");
manager.collectPlugins();
plugin = (PluginType<ValidatingPluginWithGenericBuilder>) manager.getPluginType("ValidatingPluginWithGenericBuilder");
- assertNotNull("Rebuild this module to make sure annotaion processing kicks in.", plugin);
+ assertNotNull("Rebuild this module to make sure annotation processing kicks in.", plugin);
node = new Node(null, "Validator", plugin);
}
@Test
public void testNullDefaultValue() throws Exception {
final ValidatingPluginWithGenericBuilder validatingPlugin = (ValidatingPluginWithGenericBuilder) new PluginBuilder(plugin)
- .withConfiguration(new NullConfiguration())
- .withConfigurationNode(node)
+ .setConfiguration(new NullConfiguration())
+ .setConfigurationNode(node)
.build();
assertNull(validatingPlugin);
}
@@ -57,8 +57,8 @@
public void testNonNullValue() throws Exception {
node.getAttributes().put("name", "foo");
final ValidatingPluginWithGenericBuilder validatingPlugin = (ValidatingPluginWithGenericBuilder) new PluginBuilder(plugin)
- .withConfiguration(new NullConfiguration())
- .withConfigurationNode(node)
+ .setConfiguration(new NullConfiguration())
+ .setConfigurationNode(node)
.build();
assertNotNull(validatingPlugin);
assertEquals("foo", validatingPlugin.getName());
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/validators/ValidatingPluginWithGenericSubclassFoo1BuilderTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/validators/ValidatingPluginWithGenericSubclassFoo1BuilderTest.java
index d6f3966..bea4e5a 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/validators/ValidatingPluginWithGenericSubclassFoo1BuilderTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/validators/ValidatingPluginWithGenericSubclassFoo1BuilderTest.java
@@ -20,12 +20,12 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
-import org.apache.logging.log4j.core.config.Node;
+import org.apache.logging.log4j.plugins.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.apache.logging.log4j.plugins.util.PluginManager;
+import org.apache.logging.log4j.plugins.util.PluginType;
+import org.apache.logging.log4j.plugins.validation.PluginWithGenericSubclassFoo1Builder;
import org.junit.Before;
import org.junit.Test;
@@ -40,15 +40,15 @@
final PluginManager manager = new PluginManager("Test");
manager.collectPlugins();
plugin = (PluginType<PluginWithGenericSubclassFoo1Builder>) manager.getPluginType("PluginWithGenericSubclassFoo1Builder");
- assertNotNull("Rebuild this module to make sure annotaion processing kicks in.", plugin);
+ assertNotNull("Rebuild this module to make sure annotation processing kicks in.", plugin);
node = new Node(null, "Validator", plugin);
}
@Test
public void testNullDefaultValue() throws Exception {
final PluginWithGenericSubclassFoo1Builder validatingPlugin = (PluginWithGenericSubclassFoo1Builder) new PluginBuilder(plugin)
- .withConfiguration(new NullConfiguration())
- .withConfigurationNode(node)
+ .setConfiguration(new NullConfiguration())
+ .setConfigurationNode(node)
.build();
assertNull(validatingPlugin);
}
@@ -58,8 +58,8 @@
node.getAttributes().put("thing", "thing1");
node.getAttributes().put("foo1", "foo1");
final PluginWithGenericSubclassFoo1Builder validatingPlugin = (PluginWithGenericSubclassFoo1Builder) new PluginBuilder(plugin)
- .withConfiguration(new NullConfiguration())
- .withConfigurationNode(node)
+ .setConfiguration(new NullConfiguration())
+ .setConfigurationNode(node)
.build();
assertNotNull(validatingPlugin);
assertEquals("thing1", validatingPlugin.getThing());
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/validators/ValidatingPluginWithTypedBuilderTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/validators/ValidatingPluginWithTypedBuilderTest.java
index ae236b4..0f00d46 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/validators/ValidatingPluginWithTypedBuilderTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/config/plugins/validation/validators/ValidatingPluginWithTypedBuilderTest.java
@@ -20,12 +20,12 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
-import org.apache.logging.log4j.core.config.Node;
+import org.apache.logging.log4j.plugins.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.apache.logging.log4j.plugins.util.PluginManager;
+import org.apache.logging.log4j.plugins.util.PluginType;
+import org.apache.logging.log4j.plugins.validation.ValidatingPluginWithTypedBuilder;
import org.junit.Before;
import org.junit.Test;
@@ -41,7 +41,7 @@
manager.collectPlugins();
plugin = (PluginType<ValidatingPluginWithTypedBuilder>) manager
.getPluginType("ValidatingPluginWithTypedBuilder");
- assertNotNull("Rebuild this module to make sure annotaion processing kicks in.", plugin);
+ assertNotNull("Rebuild this module to make sure annotation processing kicks in.", plugin);
node = new Node(null, "Validator", plugin);
}
@@ -50,8 +50,8 @@
// @formatter:off
final ValidatingPluginWithTypedBuilder validatingPlugin = (ValidatingPluginWithTypedBuilder)
new PluginBuilder(plugin).
- withConfiguration(new NullConfiguration()).
- withConfigurationNode(node).build();
+ setConfiguration(new NullConfiguration()).
+ setConfigurationNode(node).build();
// @formatter:on
assertNull(validatingPlugin);
}
@@ -62,8 +62,8 @@
// @formatter:off
final ValidatingPluginWithTypedBuilder validatingPlugin = (ValidatingPluginWithTypedBuilder)
new PluginBuilder(plugin).
- withConfiguration(new NullConfiguration()).
- withConfigurationNode(node).build();
+ setConfiguration(new NullConfiguration()).
+ setConfigurationNode(node).build();
// @formatter:on
assertNotNull(validatingPlugin);
assertEquals("foo", validatingPlugin.getName());
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/filter/AbstractFilterTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/filter/AbstractFilterTest.java
index 077e2b1..0285239 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/filter/AbstractFilterTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/filter/AbstractFilterTest.java
@@ -85,4 +85,4 @@
return testResult;
}
}
-}
\ No newline at end of file
+}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/filter/AbstractFilterableTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/filter/AbstractFilterableTest.java
index 4e0830d..6b9f534 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/filter/AbstractFilterableTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/filter/AbstractFilterableTest.java
@@ -51,7 +51,7 @@
// into a CompositeFilter.class
filterable.addFilter(filter);
assertTrue(filterable.getFilter() instanceof CompositeFilter);
- assertEquals(2, ((CompositeFilter) filterable.getFilter()).getFilters().size());
+ assertEquals(2, ((CompositeFilter) filterable.getFilter()).getFiltersArray().length);
}
@Test
@@ -64,7 +64,7 @@
// into a CompositeFilter.class
filterable.addFilter(filter);
assertTrue(filterable.getFilter() instanceof CompositeFilter);
- assertEquals(2, ((CompositeFilter) filterable.getFilter()).getFilters().size());
+ assertEquals(2, ((CompositeFilter) filterable.getFilter()).getFiltersArray().length);
}
@Test
@@ -90,7 +90,7 @@
// into a CompositeFilter.class
filterable.addFilter(compositeFilter);
assertTrue(filterable.getFilter() instanceof CompositeFilter);
- assertEquals(6, ((CompositeFilter) filterable.getFilter()).getFilters().size());
+ assertEquals(6, ((CompositeFilter) filterable.getFilter()).getFiltersArray().length);
}
@Test
@@ -106,7 +106,7 @@
// into a CompositeFilter.class
filterable.addFilter(compositeFilter);
assertTrue(filterable.getFilter() instanceof CompositeFilter);
- assertEquals(2, ((CompositeFilter) filterable.getFilter()).getFilters().size());
+ assertEquals(2, ((CompositeFilter) filterable.getFilter()).getFiltersArray().length);
}
@Test
@@ -122,7 +122,7 @@
// into a CompositeFilter.class
filterable.addFilter(notInCompositeFilterFilter);
assertTrue(filterable.getFilter() instanceof CompositeFilter);
- assertEquals(3, ((CompositeFilter) filterable.getFilter()).getFilters().size());
+ assertEquals(3, ((CompositeFilter) filterable.getFilter()).getFiltersArray().length);
}
@Test
@@ -167,7 +167,7 @@
filterable.addFilter(filterCopy);
filterable.removeFilter(filterCopy);
assertTrue(filterable.getFilter() instanceof CompositeFilter);
- assertEquals(2, ((CompositeFilter) filterable.getFilter()).getFilters().size());
+ assertEquals(2, ((CompositeFilter) filterable.getFilter()).getFiltersArray().length);
filterable.removeFilter(filterCopy);
assertEquals(filterOriginal, filterable.getFilter());
filterable.removeFilter(filterOriginal);
@@ -223,7 +223,7 @@
// should not remove internal filter of compositeFilter
filterable.removeFilter(anotherFilter);
assertTrue(filterable.getFilter() instanceof CompositeFilter);
- assertEquals(2, ((CompositeFilter) filterable.getFilter()).getFilters().size());
+ assertEquals(2, ((CompositeFilter) filterable.getFilter()).getFiltersArray().length);
}
@Test
@@ -246,9 +246,9 @@
filterable.addFilter(compositeFilter);
filterable.addFilter(anotherFilter);
- assertEquals(3, ((CompositeFilter) filterable.getFilter()).getFilters().size());
+ assertEquals(3, ((CompositeFilter) filterable.getFilter()).getFiltersArray().length);
filterable.removeFilter(filter1);
- assertEquals(2, ((CompositeFilter) filterable.getFilter()).getFilters().size());
+ assertEquals(2, ((CompositeFilter) filterable.getFilter()).getFiltersArray().length);
filterable.removeFilter(filter2);
assertSame(anotherFilter, filterable.getFilter());
}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/filter/MapFilterTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/filter/MapFilterTest.java
index 7310d38..97b2185 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/filter/MapFilterTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/filter/MapFilterTest.java
@@ -16,6 +16,13 @@
*/
package org.apache.logging.log4j.core.filter;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -29,11 +36,10 @@
import org.apache.logging.log4j.junit.LoggerContextRule;
import org.apache.logging.log4j.message.StringMapMessage;
import org.apache.logging.log4j.test.appender.ListAppender;
+import org.apache.logging.log4j.util.IndexedReadOnlyStringMap;
import org.junit.ClassRule;
import org.junit.Test;
-import static org.junit.Assert.*;
-
/**
*
*/
@@ -76,12 +82,12 @@
assertTrue("Not a MapFilter", filter instanceof MapFilter);
final MapFilter mapFilter = (MapFilter) filter;
assertFalse("Should not be And filter", mapFilter.isAnd());
- final Map<String, List<String>> map = mapFilter.getMap();
+ final IndexedReadOnlyStringMap map = mapFilter.getStringMap();
assertNotNull("No Map", map);
assertFalse("No elements in Map", map.isEmpty());
assertEquals("Incorrect number of elements in Map", 1, map.size());
assertTrue("Map does not contain key eventId", map.containsKey("eventId"));
- assertEquals("List does not contain 2 elements", 2, map.get("eventId").size());
+ assertEquals("List does not contain 2 elements", 2, map.<Collection<?>>getValue("eventId").size());
final Logger logger = LogManager.getLogger(MapFilterTest.class);
final Map<String, String> eventMap = new HashMap<>();
eventMap.put("eventId", "Login");
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/filter/NoMarkerFilterTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/filter/NoMarkerFilterTest.java
new file mode 100644
index 0000000..f833858
--- /dev/null
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/filter/NoMarkerFilterTest.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.filter;
+
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.Marker;
+import org.apache.logging.log4j.MarkerManager;
+import org.apache.logging.log4j.core.Filter;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.impl.Log4jLogEvent;
+import org.apache.logging.log4j.message.SimpleMessage;
+import org.junit.Test;
+
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+public class NoMarkerFilterTest {
+
+ @Test
+ public void testMarkers() {
+ final Marker sampleMarker = MarkerManager.getMarker("SampleMarker");
+ NoMarkerFilter filter = NoMarkerFilter.newBuilder().build();
+ filter.start();
+ assertTrue(filter.isStarted());
+ assertSame(Filter.Result.DENY, filter.filter(null, null, sampleMarker, (Object) null, (Throwable) null));
+ assertSame(Filter.Result.NEUTRAL, filter.filter(null, null, null, (Object) null, (Throwable) null));
+ filter.stop();
+ LogEvent event = Log4jLogEvent.newBuilder() //
+ .setLevel(Level.DEBUG) //
+ .setMessage(new SimpleMessage("Hello, world!")).build();
+ assertSame(Filter.Result.NEUTRAL, filter.filter(event));
+
+ filter.start();
+ assertSame(Filter.Result.NEUTRAL, filter.filter(event));
+ event = Log4jLogEvent.newBuilder() //
+ .setMarker(sampleMarker) //
+ .setLevel(Level.DEBUG) //
+ .setMessage(new SimpleMessage("Hello, world!")).build();
+ assertSame(Filter.Result.DENY, filter.filter(event));
+ filter.stop();
+ }
+
+}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/filter/StructuredDataFilterTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/filter/StructuredDataFilterTest.java
index d313e7c..7d49234 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/filter/StructuredDataFilterTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/filter/StructuredDataFilterTest.java
@@ -16,20 +16,24 @@
*/
package org.apache.logging.log4j.core.filter;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.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.util.KeyValuePair;
import org.apache.logging.log4j.junit.LoggerContextRule;
import org.apache.logging.log4j.message.StructuredDataMessage;
+import org.apache.logging.log4j.util.IndexedReadOnlyStringMap;
import org.junit.ClassRule;
import org.junit.Test;
-import java.util.List;
-import java.util.Map;
-
-import static org.junit.Assert.*;
-
/**
*
*/
@@ -72,11 +76,11 @@
assertTrue("Not a StructuredDataFilter", filter instanceof StructuredDataFilter);
final StructuredDataFilter sdFilter = (StructuredDataFilter) filter;
assertFalse("Should not be And filter", sdFilter.isAnd());
- final Map<String, List<String>> map = sdFilter.getMap();
+ final IndexedReadOnlyStringMap map = sdFilter.getStringMap();
assertNotNull("No Map", map);
assertFalse("No elements in Map", map.isEmpty());
assertEquals("Incorrect number of elements in Map", 1, map.size());
assertTrue("Map does not contain key eventId", map.containsKey("eventId"));
- assertEquals("List does not contain 2 elements", 2, map.get("eventId").size());
+ assertEquals("List does not contain 2 elements", 2, map.<Collection<?>>getValue("eventId").size());
}
}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/filter/TimeFilterTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/filter/TimeFilterTest.java
index 82135f5..4356ec7 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/filter/TimeFilterTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/filter/TimeFilterTest.java
@@ -16,13 +16,17 @@
*/
package org.apache.logging.log4j.core.filter;
+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.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.core.time.Clock;
import org.apache.logging.log4j.core.time.ClockFactory;
import org.apache.logging.log4j.core.time.ClockFactoryTest;
@@ -57,6 +61,125 @@
}
@Test
+ public void springForward() {
+ final TimeFilter filter = new TimeFilter(LocalTime.of(2,0), LocalTime.of(3,0),
+ ZoneId.of("America/Los_Angeles"), null, null, LocalDate.of(2020, 3, 8));
+ 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("Time " + CLOCKTIME + " is not within range: " + filter.toString(), Filter.Result.NEUTRAL, filter.filter(event));
+ date = date.plusDays(1).withHour(2);
+ CLOCKTIME = date.toInstant().toEpochMilli();
+ event = Log4jLogEvent.newBuilder().setTimeMillis(CLOCKTIME).build();
+ assertSame("Time " + CLOCKTIME + " is not within range: " + filter.toString(), Filter.Result.NEUTRAL, filter.filter(event));
+ date = date.withHour(4);
+ CLOCKTIME = date.toInstant().toEpochMilli();
+ event = Log4jLogEvent.newBuilder().setTimeMillis(CLOCKTIME).build();
+ assertSame("Time " + CLOCKTIME + " is within range: " + filter.toString(), Filter.Result.DENY, filter.filter(event));
+ }
+
+
+ @Test
+ public void fallBack() {
+ final TimeFilter filter = new TimeFilter(LocalTime.of(1,0), LocalTime.of(2,0),
+ ZoneId.of("America/Los_Angeles"), null, null, LocalDate.of(2020, 11, 1));
+ 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("Time " + CLOCKTIME + " is not within range: " + filter.toString(), Filter.Result.NEUTRAL, filter.filter(event));
+ 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("Time " + CLOCKTIME + " is within range: " + filter.toString(), Filter.Result.DENY, filter.filter(event));
+ date = date.plusDays(1).withHour(1).withMinute(30);
+ CLOCKTIME = date.toInstant().toEpochMilli();
+ event = Log4jLogEvent.newBuilder().setTimeMillis(CLOCKTIME).build();
+ assertSame("Time " + CLOCKTIME + " is not within range: " + filter.toString(), Filter.Result.NEUTRAL, filter.filter(event));
+ date = date.withHour(4);
+ CLOCKTIME = date.toInstant().toEpochMilli();
+ event = Log4jLogEvent.newBuilder().setTimeMillis(CLOCKTIME).build();
+ assertSame("Time " + CLOCKTIME + " is within range: " + filter.toString(), Filter.Result.DENY, filter.filter(event));
+ }
+
+
+ @Test
+ public void overnight() {
+ final TimeFilter filter = new TimeFilter(LocalTime.of(23,0), LocalTime.of(1,0),
+ ZoneId.of("America/Los_Angeles"), null, null, LocalDate.of(2020, 3, 10));
+ 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("Time " + CLOCKTIME + " is not within range: " + filter.toString(), Filter.Result.NEUTRAL, filter.filter(event));
+ date = date.plusHours(1);
+ CLOCKTIME = date.toInstant().toEpochMilli();
+ event = Log4jLogEvent.newBuilder().setTimeMillis(CLOCKTIME).build();
+ assertSame("Time " + CLOCKTIME + " is not within range: " + filter.toString(), Filter.Result.NEUTRAL, filter.filter(event));
+ date = date.plusHours(1);
+ CLOCKTIME = date.toInstant().toEpochMilli();
+ event = Log4jLogEvent.newBuilder().setTimeMillis(CLOCKTIME).build();
+ assertSame("Time " + CLOCKTIME + " is within range: " + filter.toString(), Filter.Result.DENY, filter.filter(event));
+ date = date.plusDays(1).withHour(0);
+ CLOCKTIME = date.toInstant().toEpochMilli();
+ event = Log4jLogEvent.newBuilder().setTimeMillis(CLOCKTIME).build();
+ assertSame("Time " + CLOCKTIME + " is not within range: " + filter.toString(), Filter.Result.NEUTRAL, filter.filter(event));
+ }
+
+ @Test
+ public void overnightForward() {
+ final TimeFilter filter = new TimeFilter(LocalTime.of(23,0), LocalTime.of(2,0),
+ ZoneId.of("America/Los_Angeles"), null, null, LocalDate.of(2020, 3, 7));
+ 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("Time " + CLOCKTIME + " is not within range: " + filter.toString(), Filter.Result.NEUTRAL, filter.filter(event));
+ date = date.plusHours(1);
+ CLOCKTIME = date.toInstant().toEpochMilli();
+ event = Log4jLogEvent.newBuilder().setTimeMillis(CLOCKTIME).build();
+ assertSame("Time " + CLOCKTIME + " is not within range: " + filter.toString(), Filter.Result.NEUTRAL, filter.filter(event));
+ date = date.plusHours(2);
+ CLOCKTIME = date.toInstant().toEpochMilli();
+ event = Log4jLogEvent.newBuilder().setTimeMillis(CLOCKTIME).build();
+ assertSame("Time " + CLOCKTIME + " is within range: " + filter.toString(), Filter.Result.DENY, filter.filter(event));
+ date = date.plusDays(1).withHour(0);
+ CLOCKTIME = date.toInstant().toEpochMilli();
+ event = Log4jLogEvent.newBuilder().setTimeMillis(CLOCKTIME).build();
+ assertSame("Time " + CLOCKTIME + " is not within range: " + filter.toString(), Filter.Result.NEUTRAL, filter.filter(event));
+ }
+
+
+ @Test
+ public void overnightFallback() {
+ final TimeFilter filter = new TimeFilter(LocalTime.of(23,0), LocalTime.of(2,0),
+ ZoneId.of("America/Los_Angeles"), null, null, LocalDate.of(2020, 10, 31));
+ 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("Time " + CLOCKTIME + " is not within range: " + filter.toString(), Filter.Result.NEUTRAL, filter.filter(event));
+ date = date.plusHours(1);
+ CLOCKTIME = date.toInstant().toEpochMilli();
+ event = Log4jLogEvent.newBuilder().setTimeMillis(CLOCKTIME).build();
+ assertSame("Time " + CLOCKTIME + " is not within range: " + filter.toString(), Filter.Result.NEUTRAL, filter.filter(event));
+ date = date.plusHours(2);
+ CLOCKTIME = date.toInstant().toEpochMilli();
+ event = Log4jLogEvent.newBuilder().setTimeMillis(CLOCKTIME).build();
+ assertSame("Time " + CLOCKTIME + " is within range: " + filter.toString(), Filter.Result.DENY, filter.filter(event));
+ date = date.plusDays(1).withHour(0);
+ CLOCKTIME = date.toInstant().toEpochMilli();
+ event = Log4jLogEvent.newBuilder().setTimeMillis(CLOCKTIME).build();
+ assertSame("Time " + CLOCKTIME + " is not within range: " + filter.toString(), Filter.Result.NEUTRAL, filter.filter(event));
+ }
+
+ @Test
public 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);
@@ -66,19 +189,20 @@
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));
+ //assertSame(Filter.Result.NEUTRAL, filter.filter(null, Level.ERROR, null, (Object) null, (Throwable) null));
+ assertSame("Time " + CLOCKTIME + " is not within range: " + filter.toString(), Filter.Result.NEUTRAL, filter.filter(event));
cal.roll(Calendar.DAY_OF_MONTH, true);
+ cal.set(Calendar.HOUR_OF_DAY, 2);
CLOCKTIME = cal.getTimeInMillis();
event = Log4jLogEvent.newBuilder().setTimeMillis(CLOCKTIME).build();
- assertSame(Filter.Result.NEUTRAL, filter.filter(event));
- assertSame(Filter.Result.NEUTRAL, filter.filter(null, Level.ERROR, null, (Object) null, (Throwable) null));
+ assertSame("Time " + CLOCKTIME + " is not within range: " + filter.toString(), Filter.Result.NEUTRAL, filter.filter(event));
+ //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));
+ //assertSame(Filter.Result.DENY, filter.filter(null, Level.ERROR, null, (Object) null, (Throwable) null));
+ assertSame("Time " + CLOCKTIME + " is within range: " + filter.toString(), Filter.Result.DENY, filter.filter(event));
}
}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/impl/ContextDataFactoryPropertySetMissingConstructorTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/impl/ContextDataFactoryPropertySetMissingConstructorTest.java
index 47c9c14..b86588f 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/impl/ContextDataFactoryPropertySetMissingConstructorTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/impl/ContextDataFactoryPropertySetMissingConstructorTest.java
@@ -38,4 +38,4 @@
assertEquals(2, thresholdField.getInt(actual));
System.clearProperty("log4j2.ContextData");
}
-}
\ No newline at end of file
+}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/impl/ContextDataFactoryPropertySetTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/impl/ContextDataFactoryPropertySetTest.java
index dc308dc..484923e 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/impl/ContextDataFactoryPropertySetTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/impl/ContextDataFactoryPropertySetTest.java
@@ -46,4 +46,4 @@
assertEquals(2, actual.initialCapacity);
System.clearProperty("log4j2.ContextData");
}
-}
\ No newline at end of file
+}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/impl/ContextDataFactoryTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/impl/ContextDataFactoryTest.java
index 5da1267..10a2e96 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/impl/ContextDataFactoryTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/impl/ContextDataFactoryTest.java
@@ -44,4 +44,4 @@
thresholdField.setAccessible(true);
assertEquals(2, thresholdField.getInt(actual));
}
-}
\ No newline at end of file
+}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/impl/JdkMapAdapterStringMapTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/impl/JdkMapAdapterStringMapTest.java
index c8c8db1..c3cc13f 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/impl/JdkMapAdapterStringMapTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/impl/JdkMapAdapterStringMapTest.java
@@ -880,4 +880,4 @@
assertEquals(state.count, original.size());
}
-}
\ No newline at end of file
+}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/impl/Log4jLogEventTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/impl/Log4jLogEventTest.java
index 4489564..0f406cd 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/impl/Log4jLogEventTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/impl/Log4jLogEventTest.java
@@ -30,6 +30,7 @@
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
+import java.util.Base64;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.Marker;
@@ -37,7 +38,6 @@
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.time.Clock;
import org.apache.logging.log4j.core.time.ClockFactory;
import org.apache.logging.log4j.core.time.ClockFactoryTest;
@@ -58,6 +58,8 @@
public class Log4jLogEventTest {
+ private static final Base64.Decoder decoder = Base64.getDecoder();
+
/** Helper class */
public static class FixedTimeClock implements Clock {
public static final long FIXED_TIME = 1234567890L;
@@ -114,7 +116,6 @@
assertEquals(evt.getLevel(), evt2.getLevel());
assertEquals(evt.getLoggerName(), evt2.getLoggerName());
assertEquals(evt.getMarker(), evt2.getMarker());
- assertEquals(evt.getContextMap(), evt2.getContextMap());
assertEquals(evt.getContextData(), evt2.getContextData());
assertEquals(evt.getContextStack(), evt2.getContextStack());
assertEquals(evt.getMessage(), evt2.getMessage());
@@ -144,7 +145,6 @@
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());
@@ -201,7 +201,7 @@
final String base64 = "rO0ABXNyAD5vcmcuYXBhY2hlLmxvZ2dpbmcubG9nNGouY29yZS5pbXBsLkxvZzRqTG9nRXZlbnQkTG9nRXZlbnRQcm94eYgtmn+yXsP9AwAQWgAMaXNFbmRPZkJhdGNoWgASaXNMb2NhdGlvblJlcXVpcmVkSgAIdGhyZWFkSWRJAA50aHJlYWRQcmlvcml0eUoACnRpbWVNaWxsaXNMAAtjb250ZXh0RGF0YXQAKUxvcmcvYXBhY2hlL2xvZ2dpbmcvbG9nNGovdXRpbC9TdHJpbmdNYXA7TAAMY29udGV4dFN0YWNrdAA1TG9yZy9hcGFjaGUvbG9nZ2luZy9sb2c0ai9UaHJlYWRDb250ZXh0JENvbnRleHRTdGFjaztMAAVsZXZlbHQAIExvcmcvYXBhY2hlL2xvZ2dpbmcvbG9nNGovTGV2ZWw7TAAKbG9nZ2VyRlFDTnQAEkxqYXZhL2xhbmcvU3RyaW5nO0wACmxvZ2dlck5hbWVxAH4ABEwABm1hcmtlcnQAIUxvcmcvYXBhY2hlL2xvZ2dpbmcvbG9nNGovTWFya2VyO0wAEW1hcnNoYWxsZWRNZXNzYWdldAAbTGphdmEvcm1pL01hcnNoYWxsZWRPYmplY3Q7TAANbWVzc2FnZVN0cmluZ3EAfgAETAAGc291cmNldAAdTGphdmEvbGFuZy9TdGFja1RyYWNlRWxlbWVudDtMAAp0aHJlYWROYW1lcQB+AARMAAt0aHJvd25Qcm94eXQAM0xvcmcvYXBhY2hlL2xvZ2dpbmcvbG9nNGovY29yZS9pbXBsL1Rocm93YWJsZVByb3h5O3hwAAAAAAAAAAAAAQAAAAUAAAAASZYC0nNyADJvcmcuYXBhY2hlLmxvZ2dpbmcubG9nNGoudXRpbC5Tb3J0ZWRBcnJheVN0cmluZ01hcLA3yJFz7CvcAwACWgAJaW1tdXRhYmxlSQAJdGhyZXNob2xkeHABAAAAAXcIAAAAAQAAAAB4c3IAPm9yZy5hcGFjaGUubG9nZ2luZy5sb2c0ai5UaHJlYWRDb250ZXh0JEVtcHR5VGhyZWFkQ29udGV4dFN0YWNrAAAAAAAAAAECAAB4cHNyAB5vcmcuYXBhY2hlLmxvZ2dpbmcubG9nNGouTGV2ZWwAAAAAABggGgIAA0kACGludExldmVsTAAEbmFtZXEAfgAETAANc3RhbmRhcmRMZXZlbHQALExvcmcvYXBhY2hlL2xvZ2dpbmcvbG9nNGovc3BpL1N0YW5kYXJkTGV2ZWw7eHAAAAGQdAAESU5GT35yACpvcmcuYXBhY2hlLmxvZ2dpbmcubG9nNGouc3BpLlN0YW5kYXJkTGV2ZWwAAAAAAAAAABIAAHhyAA5qYXZhLmxhbmcuRW51bQAAAAAAAAAAEgAAeHB0AARJTkZPdAAAdAAJc29tZS50ZXN0cHNyABlqYXZhLnJtaS5NYXJzaGFsbGVkT2JqZWN0fL0el+1j/D4CAANJAARoYXNoWwAIbG9jQnl0ZXN0AAJbQlsACG9iakJ5dGVzcQB+ABl4cJNvO+xwdXIAAltCrPMX+AYIVOACAAB4cAAAAGms7QAFc3IALm9yZy5hcGFjaGUubG9nZ2luZy5sb2c0ai5tZXNzYWdlLlNpbXBsZU1lc3NhZ2WLdE0wYLeiqAMAAUwAB21lc3NhZ2V0ABJMamF2YS9sYW5nL1N0cmluZzt4cHQAA2FiY3h0AANhYmNwdAAEbWFpbnNyADFvcmcuYXBhY2hlLmxvZ2dpbmcubG9nNGouY29yZS5pbXBsLlRocm93YWJsZVByb3h52cww1Zp7rPoCAAdJABJjb21tb25FbGVtZW50Q291bnRMAApjYXVzZVByb3h5cQB+AAhbABJleHRlbmRlZFN0YWNrVHJhY2V0AD9bTG9yZy9hcGFjaGUvbG9nZ2luZy9sb2c0ai9jb3JlL2ltcGwvRXh0ZW5kZWRTdGFja1RyYWNlRWxlbWVudDtMABBsb2NhbGl6ZWRNZXNzYWdlcQB+AARMAAdtZXNzYWdlcQB+AARMAARuYW1lcQB+AARbABFzdXBwcmVzc2VkUHJveGllc3QANFtMb3JnL2FwYWNoZS9sb2dnaW5nL2xvZzRqL2NvcmUvaW1wbC9UaHJvd2FibGVQcm94eTt4cAAAAABwdXIAP1tMb3JnLmFwYWNoZS5sb2dnaW5nLmxvZzRqLmNvcmUuaW1wbC5FeHRlbmRlZFN0YWNrVHJhY2VFbGVtZW50O8rPiCOlx8+8AgAAeHAAAAAec3IAPG9yZy5hcGFjaGUubG9nZ2luZy5sb2c0ai5jb3JlLmltcGwuRXh0ZW5kZWRTdGFja1RyYWNlRWxlbWVudOHez7rGtpAHAgACTAAOZXh0cmFDbGFzc0luZm90ADZMb3JnL2FwYWNoZS9sb2dnaW5nL2xvZzRqL2NvcmUvaW1wbC9FeHRlbmRlZENsYXNzSW5mbztMABFzdGFja1RyYWNlRWxlbWVudHEAfgAHeHBzcgA0b3JnLmFwYWNoZS5sb2dnaW5nLmxvZzRqLmNvcmUuaW1wbC5FeHRlbmRlZENsYXNzSW5mbwAAAAAAAAABAgADWgAFZXhhY3RMAAhsb2NhdGlvbnEAfgAETAAHdmVyc2lvbnEAfgAEeHABdAANdGVzdC1jbGFzc2VzL3QAAT9zcgAbamF2YS5sYW5nLlN0YWNrVHJhY2VFbGVtZW50YQnFmiY23YUCAARJAApsaW5lTnVtYmVyTAAOZGVjbGFyaW5nQ2xhc3NxAH4ABEwACGZpbGVOYW1lcQB+AARMAAptZXRob2ROYW1lcQB+AAR4cAAAAKx0ADRvcmcuYXBhY2hlLmxvZ2dpbmcubG9nNGouY29yZS5pbXBsLkxvZzRqTG9nRXZlbnRUZXN0dAAWTG9nNGpMb2dFdmVudFRlc3QuamF2YXQAKnRlc3RKYXZhSW9TZXJpYWxpemFibGVXaXRoVW5rbm93blRocm93YWJsZXNxAH4AJXNxAH4AKABxAH4AK3QACDEuNy4wXzU1c3EAfgAs/////nQAJHN1bi5yZWZsZWN0Lk5hdGl2ZU1ldGhvZEFjY2Vzc29ySW1wbHQAHU5hdGl2ZU1ldGhvZEFjY2Vzc29ySW1wbC5qYXZhdAAHaW52b2tlMHNxAH4AJXNxAH4AKABxAH4AK3EAfgAzc3EAfgAsAAAAOXEAfgA1cQB+ADZ0AAZpbnZva2VzcQB+ACVzcQB+ACgAcQB+ACtxAH4AM3NxAH4ALAAAACt0AChzdW4ucmVmbGVjdC5EZWxlZ2F0aW5nTWV0aG9kQWNjZXNzb3JJbXBsdAAhRGVsZWdhdGluZ01ldGhvZEFjY2Vzc29ySW1wbC5qYXZhcQB+ADtzcQB+ACVzcQB+ACgAcQB+ACtxAH4AM3NxAH4ALAAAAl50ABhqYXZhLmxhbmcucmVmbGVjdC5NZXRob2R0AAtNZXRob2QuamF2YXEAfgA7c3EAfgAlc3EAfgAoAXQADmp1bml0LTQuMTIuamFydAAENC4xMnNxAH4ALAAAADJ0AClvcmcuanVuaXQucnVubmVycy5tb2RlbC5GcmFtZXdvcmtNZXRob2QkMXQAFEZyYW1ld29ya01ldGhvZC5qYXZhdAARcnVuUmVmbGVjdGl2ZUNhbGxzcQB+ACVzcQB+ACgBdAAOanVuaXQtNC4xMi5qYXJxAH4ASXNxAH4ALAAAAAx0ADNvcmcuanVuaXQuaW50ZXJuYWwucnVubmVycy5tb2RlbC5SZWZsZWN0aXZlQ2FsbGFibGV0ABdSZWZsZWN0aXZlQ2FsbGFibGUuamF2YXQAA3J1bnNxAH4AJXNxAH4AKAF0AA5qdW5pdC00LjEyLmphcnEAfgBJc3EAfgAsAAAAL3QAJ29yZy5qdW5pdC5ydW5uZXJzLm1vZGVsLkZyYW1ld29ya01ldGhvZHEAfgBMdAARaW52b2tlRXhwbG9zaXZlbHlzcQB+ACVzcQB+ACgBdAAOanVuaXQtNC4xMi5qYXJxAH4ASXNxAH4ALAAAABF0ADJvcmcuanVuaXQuaW50ZXJuYWwucnVubmVycy5zdGF0ZW1lbnRzLkludm9rZU1ldGhvZHQAEUludm9rZU1ldGhvZC5qYXZhdAAIZXZhbHVhdGVzcQB+ACVzcQB+ACgBdAAOanVuaXQtNC4xMi5qYXJxAH4ASXNxAH4ALAAAAUV0AB5vcmcuanVuaXQucnVubmVycy5QYXJlbnRSdW5uZXJ0ABFQYXJlbnRSdW5uZXIuamF2YXQAB3J1bkxlYWZzcQB+ACVzcQB+ACgBdAAOanVuaXQtNC4xMi5qYXJxAH4ASXNxAH4ALAAAAE50AChvcmcuanVuaXQucnVubmVycy5CbG9ja0pVbml0NENsYXNzUnVubmVydAAbQmxvY2tKVW5pdDRDbGFzc1J1bm5lci5qYXZhdAAIcnVuQ2hpbGRzcQB+ACVzcQB+ACgBdAAOanVuaXQtNC4xMi5qYXJxAH4ASXNxAH4ALAAAADlxAH4AbXEAfgBucQB+AG9zcQB+ACVzcQB+ACgBdAAOanVuaXQtNC4xMi5qYXJxAH4ASXNxAH4ALAAAASJ0ACBvcmcuanVuaXQucnVubmVycy5QYXJlbnRSdW5uZXIkM3EAfgBncQB+AFRzcQB+ACVzcQB+ACgBdAAOanVuaXQtNC4xMi5qYXJxAH4ASXNxAH4ALAAAAEd0ACBvcmcuanVuaXQucnVubmVycy5QYXJlbnRSdW5uZXIkMXEAfgBndAAIc2NoZWR1bGVzcQB+ACVzcQB+ACgBdAAOanVuaXQtNC4xMi5qYXJxAH4ASXNxAH4ALAAAASBxAH4AZnEAfgBndAALcnVuQ2hpbGRyZW5zcQB+ACVzcQB+ACgBdAAOanVuaXQtNC4xMi5qYXJxAH4ASXNxAH4ALAAAADpxAH4AZnEAfgBndAAKYWNjZXNzJDAwMHNxAH4AJXNxAH4AKAF0AA5qdW5pdC00LjEyLmphcnEAfgBJc3EAfgAsAAABDHQAIG9yZy5qdW5pdC5ydW5uZXJzLlBhcmVudFJ1bm5lciQycQB+AGdxAH4AYXNxAH4AJXNxAH4AKAF0AA5qdW5pdC00LjEyLmphcnEAfgBJc3EAfgAsAAAAGnQAMG9yZy5qdW5pdC5pbnRlcm5hbC5ydW5uZXJzLnN0YXRlbWVudHMuUnVuQmVmb3Jlc3QAD1J1bkJlZm9yZXMuamF2YXEAfgBhc3EAfgAlc3EAfgAoAXQADmp1bml0LTQuMTIuamFycQB+AElzcQB+ACwAAAAbdAAvb3JnLmp1bml0LmludGVybmFsLnJ1bm5lcnMuc3RhdGVtZW50cy5SdW5BZnRlcnN0AA5SdW5BZnRlcnMuamF2YXEAfgBhc3EAfgAlc3EAfgAoAXQADmp1bml0LTQuMTIuamFycQB+AElzcQB+ACwAAAFrcQB+AGZxAH4AZ3EAfgBUc3EAfgAlc3EAfgAoAXQADmp1bml0LTQuMTIuamFycQB+AElzcQB+ACwAAACJdAAab3JnLmp1bml0LnJ1bm5lci5KVW5pdENvcmV0AA5KVW5pdENvcmUuamF2YXEAfgBUc3EAfgAlc3EAfgAoAXQADGp1bml0LXJ0LmphcnEAfgArc3EAfgAsAAAAdXQAKGNvbS5pbnRlbGxpai5qdW5pdDQuSlVuaXQ0SWRlYVRlc3RSdW5uZXJ0ABlKVW5pdDRJZGVhVGVzdFJ1bm5lci5qYXZhdAATc3RhcnRSdW5uZXJXaXRoQXJnc3NxAH4AJXNxAH4AKAF0AAxqdW5pdC1ydC5qYXJxAH4AK3NxAH4ALAAAACpxAH4AqHEAfgCpcQB+AKpzcQB+ACVzcQB+ACgBdAAManVuaXQtcnQuamFycQB+ACtzcQB+ACwAAAEGdAAsY29tLmludGVsbGlqLnJ0LmV4ZWN1dGlvbi5qdW5pdC5KVW5pdFN0YXJ0ZXJ0ABFKVW5pdFN0YXJ0ZXIuamF2YXQAFnByZXBhcmVTdHJlYW1zQW5kU3RhcnRzcQB+ACVzcQB+ACgBdAAManVuaXQtcnQuamFycQB+ACtzcQB+ACwAAABUcQB+ALNxAH4AtHQABG1haW5zcQB+ACVzcQB+ACgAcQB+ACtxAH4AM3NxAH4ALP////5xAH4ANXEAfgA2cQB+ADdzcQB+ACVzcQB+ACgAcQB+ACtxAH4AM3NxAH4ALAAAADlxAH4ANXEAfgA2cQB+ADtzcQB+ACVzcQB+ACgAcQB+ACtxAH4AM3NxAH4ALAAAACtxAH4AP3EAfgBAcQB+ADtzcQB+ACVzcQB+ACgAcQB+ACtxAH4AM3NxAH4ALAAAAl5xAH4ARHEAfgBFcQB+ADtzcQB+ACVzcQB+ACgBdAALaWRlYV9ydC5qYXJxAH4AK3NxAH4ALAAAAJN0AC1jb20uaW50ZWxsaWoucnQuZXhlY3V0aW9uLmFwcGxpY2F0aW9uLkFwcE1haW50AAxBcHBNYWluLmphdmFxAH4AunQAFk9NRyBJJ3ZlIGJlZW4gZGVsZXRlZCFxAH4AzXQARW9yZy5hcGFjaGUubG9nZ2luZy5sb2c0ai5jb3JlLmltcGwuTG9nNGpMb2dFdmVudFRlc3QkRGVsZXRlZEV4Y2VwdGlvbnVyADRbTG9yZy5hcGFjaGUubG9nZ2luZy5sb2c0ai5jb3JlLmltcGwuVGhyb3dhYmxlUHJveHk7+u0B4IWi6zkCAAB4cAAAAAB4";
- final byte[] binaryDecoded = Base64Converter.parseBase64Binary(base64);
+ final byte[] binaryDecoded = decoder.decode(base64);
final Log4jLogEvent evt2 = deserialize(binaryDecoded);
assertEquals(loggerFQN, evt2.getLoggerFqcn());
@@ -242,22 +242,14 @@
verifyNanoTimeWithAllConstructors(87654);
}
- @SuppressWarnings("deprecation")
private void verifyNanoTimeWithAllConstructors(final long expected) {
assertEquals(expected, Log4jLogEvent.getNanoClock().nanoTime());
assertEquals("No-arg constructor", expected, new Log4jLogEvent().getNanoTime());
- assertEquals("1-arg constructor", expected, new Log4jLogEvent(98).getNanoTime());
- assertEquals("6-arg constructor", expected, new Log4jLogEvent("l", null, "a", null, null, null).getNanoTime());
assertEquals("7-arg constructor", expected, new Log4jLogEvent("l", null, "a", null, null, null, null)
.getNanoTime());
- assertEquals("11-arg constructor", expected, new Log4jLogEvent("l", null, "a", null, null, null, null, null,
- null, null, 0).getNanoTime());
- assertEquals("12-arg factory method", expected, Log4jLogEvent.createEvent("l", null, "a", null, null, null,
- null, null, null, null, null, 0).getNanoTime());
}
- @SuppressWarnings("deprecation")
@Test
public void testBuilderCorrectlyCopiesAllEventAttributes() {
final StringMap contextData = ContextDataFactory.createContextData();
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/impl/MutableLogEventTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/impl/MutableLogEventTest.java
index 273502a..7e94ca6 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/impl/MutableLogEventTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/impl/MutableLogEventTest.java
@@ -31,6 +31,7 @@
import org.apache.logging.log4j.message.Message;
import org.apache.logging.log4j.message.ParameterizedMessage;
import org.apache.logging.log4j.message.ReusableMessageFactory;
+import org.apache.logging.log4j.message.ReusableSimpleMessage;
import org.apache.logging.log4j.message.SimpleMessage;
import org.apache.logging.log4j.util.FilteredObjectInputStream;
import org.apache.logging.log4j.util.SortedArrayStringMap;
@@ -189,6 +190,10 @@
@Test
public void testClear() {
final MutableLogEvent mutable = new MutableLogEvent();
+ // initialize the event with an empty message
+ ReusableSimpleMessage simpleMessage = new ReusableSimpleMessage();
+ simpleMessage.set("");
+ mutable.setMessage(simpleMessage);
assertEquals("context data", 0, mutable.getContextData().size());
assertNull("context stack", mutable.getContextStack());
assertFalse("end of batch", mutable.isEndOfBatch());
@@ -224,7 +229,6 @@
mutable.setThrown(new Exception());
mutable.setTimeMillis(56789);
- assertNotNull("context map", mutable.getContextMap());
assertNotNull("context stack", mutable.getContextStack());
assertTrue("end of batch", mutable.isEndOfBatch());
assertTrue("incl loc", mutable.isIncludeLocation());
@@ -296,7 +300,6 @@
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());
@@ -341,7 +344,6 @@
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());
@@ -373,4 +375,4 @@
}
-}
\ No newline at end of file
+}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/impl/NestedLoggingFromToStringTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/impl/NestedLoggingFromToStringTest.java
index d0c35cb..30dbef9 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/impl/NestedLoggingFromToStringTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/impl/NestedLoggingFromToStringTest.java
@@ -135,4 +135,4 @@
assertEquals(expect4, list.get(3));
}
-}
\ No newline at end of file
+}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/impl/ReusableLogEventFactoryTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/impl/ReusableLogEventFactoryTest.java
index cb9eea3..75b9287 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/impl/ReusableLogEventFactoryTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/impl/ReusableLogEventFactoryTest.java
@@ -123,7 +123,6 @@
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 {
@@ -131,4 +130,4 @@
}
}
-}
\ No newline at end of file
+}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/impl/ThrowableProxyTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/impl/ThrowableProxyTest.java
index ba09c99..ea22e02 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/impl/ThrowableProxyTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/impl/ThrowableProxyTest.java
@@ -32,6 +32,7 @@
import java.nio.channels.ServerSocketChannel;
import java.security.Permission;
import java.security.SecureRandom;
+import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.Stack;
@@ -43,7 +44,6 @@
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
-import org.apache.logging.log4j.core.config.plugins.convert.Base64Converter;
import org.apache.logging.log4j.core.pattern.PlainTextRenderer;
import org.apache.logging.log4j.util.Strings;
import org.junit.Test;
@@ -56,6 +56,8 @@
*/
public class ThrowableProxyTest {
+ private static final Base64.Decoder decoder = Base64.getDecoder();
+
public static class AlwaysThrowsError {
static {
if (true) {
@@ -283,7 +285,7 @@
final String base64 = "rO0ABXNyADFvcmcuYXBhY2hlLmxvZ2dpbmcubG9nNGouY29yZS5pbXBsLlRocm93YWJsZVByb3h52cww1Zp7rPoCAAdJABJjb21tb25FbGVtZW50Q291bnRMAApjYXVzZVByb3h5dAAzTG9yZy9hcGFjaGUvbG9nZ2luZy9sb2c0ai9jb3JlL2ltcGwvVGhyb3dhYmxlUHJveHk7WwASZXh0ZW5kZWRTdGFja1RyYWNldAA/W0xvcmcvYXBhY2hlL2xvZ2dpbmcvbG9nNGovY29yZS9pbXBsL0V4dGVuZGVkU3RhY2tUcmFjZUVsZW1lbnQ7TAAQbG9jYWxpemVkTWVzc2FnZXQAEkxqYXZhL2xhbmcvU3RyaW5nO0wAB21lc3NhZ2VxAH4AA0wABG5hbWVxAH4AA1sAEXN1cHByZXNzZWRQcm94aWVzdAA0W0xvcmcvYXBhY2hlL2xvZ2dpbmcvbG9nNGovY29yZS9pbXBsL1Rocm93YWJsZVByb3h5O3hwAAAAAHB1cgA/W0xvcmcuYXBhY2hlLmxvZ2dpbmcubG9nNGouY29yZS5pbXBsLkV4dGVuZGVkU3RhY2tUcmFjZUVsZW1lbnQ7ys+II6XHz7wCAAB4cAAAABhzcgA8b3JnLmFwYWNoZS5sb2dnaW5nLmxvZzRqLmNvcmUuaW1wbC5FeHRlbmRlZFN0YWNrVHJhY2VFbGVtZW504d7Pusa2kAcCAAJMAA5leHRyYUNsYXNzSW5mb3QANkxvcmcvYXBhY2hlL2xvZ2dpbmcvbG9nNGovY29yZS9pbXBsL0V4dGVuZGVkQ2xhc3NJbmZvO0wAEXN0YWNrVHJhY2VFbGVtZW50dAAdTGphdmEvbGFuZy9TdGFja1RyYWNlRWxlbWVudDt4cHNyADRvcmcuYXBhY2hlLmxvZ2dpbmcubG9nNGouY29yZS5pbXBsLkV4dGVuZGVkQ2xhc3NJbmZvAAAAAAAAAAECAANaAAVleGFjdEwACGxvY2F0aW9ucQB+AANMAAd2ZXJzaW9ucQB+AAN4cAF0AA10ZXN0LWNsYXNzZXMvdAABP3NyABtqYXZhLmxhbmcuU3RhY2tUcmFjZUVsZW1lbnRhCcWaJjbdhQIABEkACmxpbmVOdW1iZXJMAA5kZWNsYXJpbmdDbGFzc3EAfgADTAAIZmlsZU5hbWVxAH4AA0wACm1ldGhvZE5hbWVxAH4AA3hwAAAAaHQANW9yZy5hcGFjaGUubG9nZ2luZy5sb2c0ai5jb3JlLmltcGwuVGhyb3dhYmxlUHJveHlUZXN0dAAXVGhyb3dhYmxlUHJveHlUZXN0LmphdmF0ACV0ZXN0U2VyaWFsaXphdGlvbldpdGhVbmtub3duVGhyb3dhYmxlc3EAfgAIc3EAfgAMAHEAfgAPdAAIMS43LjBfNTVzcQB+ABD////+dAAkc3VuLnJlZmxlY3QuTmF0aXZlTWV0aG9kQWNjZXNzb3JJbXBscHQAB2ludm9rZTBzcQB+AAhzcQB+AAwAcQB+AA9xAH4AF3NxAH4AEP////9xAH4AGXB0AAZpbnZva2VzcQB+AAhzcQB+AAwAcQB+AA9xAH4AF3NxAH4AEP////90AChzdW4ucmVmbGVjdC5EZWxlZ2F0aW5nTWV0aG9kQWNjZXNzb3JJbXBscHEAfgAec3EAfgAIc3EAfgAMAHEAfgAPcQB+ABdzcQB+ABD/////dAAYamF2YS5sYW5nLnJlZmxlY3QuTWV0aG9kcHEAfgAec3EAfgAIc3EAfgAMAXQADmp1bml0LTQuMTEuamFycQB+AA9zcQB+ABAAAAAvdAApb3JnLmp1bml0LnJ1bm5lcnMubW9kZWwuRnJhbWV3b3JrTWV0aG9kJDF0ABRGcmFtZXdvcmtNZXRob2QuamF2YXQAEXJ1blJlZmxlY3RpdmVDYWxsc3EAfgAIc3EAfgAMAXQADmp1bml0LTQuMTEuamFycQB+AA9zcQB+ABAAAAAMdAAzb3JnLmp1bml0LmludGVybmFsLnJ1bm5lcnMubW9kZWwuUmVmbGVjdGl2ZUNhbGxhYmxldAAXUmVmbGVjdGl2ZUNhbGxhYmxlLmphdmF0AANydW5zcQB+AAhzcQB+AAwBdAAOanVuaXQtNC4xMS5qYXJxAH4AD3NxAH4AEAAAACx0ACdvcmcuanVuaXQucnVubmVycy5tb2RlbC5GcmFtZXdvcmtNZXRob2RxAH4ALHQAEWludm9rZUV4cGxvc2l2ZWx5c3EAfgAIc3EAfgAMAXQADmp1bml0LTQuMTEuamFycQB+AA9zcQB+ABAAAAARdAAyb3JnLmp1bml0LmludGVybmFsLnJ1bm5lcnMuc3RhdGVtZW50cy5JbnZva2VNZXRob2R0ABFJbnZva2VNZXRob2QuamF2YXQACGV2YWx1YXRlc3EAfgAIc3EAfgAMAXQADmp1bml0LTQuMTEuamFycQB+AA9zcQB+ABAAAAEPdAAeb3JnLmp1bml0LnJ1bm5lcnMuUGFyZW50UnVubmVydAARUGFyZW50UnVubmVyLmphdmF0AAdydW5MZWFmc3EAfgAIc3EAfgAMAXQADmp1bml0LTQuMTEuamFycQB+AA9zcQB+ABAAAABGdAAob3JnLmp1bml0LnJ1bm5lcnMuQmxvY2tKVW5pdDRDbGFzc1J1bm5lcnQAG0Jsb2NrSlVuaXQ0Q2xhc3NSdW5uZXIuamF2YXQACHJ1bkNoaWxkc3EAfgAIc3EAfgAMAXQADmp1bml0LTQuMTEuamFycQB+AA9zcQB+ABAAAAAycQB+AE1xAH4ATnEAfgBPc3EAfgAIc3EAfgAMAXQADmp1bml0LTQuMTEuamFycQB+AA9zcQB+ABAAAADudAAgb3JnLmp1bml0LnJ1bm5lcnMuUGFyZW50UnVubmVyJDNxAH4AR3EAfgA0c3EAfgAIc3EAfgAMAXQADmp1bml0LTQuMTEuamFycQB+AA9zcQB+ABAAAAA/dAAgb3JnLmp1bml0LnJ1bm5lcnMuUGFyZW50UnVubmVyJDFxAH4AR3QACHNjaGVkdWxlc3EAfgAIc3EAfgAMAXQADmp1bml0LTQuMTEuamFycQB+AA9zcQB+ABAAAADscQB+AEZxAH4AR3QAC3J1bkNoaWxkcmVuc3EAfgAIc3EAfgAMAXQADmp1bml0LTQuMTEuamFycQB+AA9zcQB+ABAAAAA1cQB+AEZxAH4AR3QACmFjY2VzcyQwMDBzcQB+AAhzcQB+AAwBdAAOanVuaXQtNC4xMS5qYXJxAH4AD3NxAH4AEAAAAOV0ACBvcmcuanVuaXQucnVubmVycy5QYXJlbnRSdW5uZXIkMnEAfgBHcQB+AEFzcQB+AAhzcQB+AAwBdAAOanVuaXQtNC4xMS5qYXJxAH4AD3NxAH4AEAAAATVxAH4ARnEAfgBHcQB+ADRzcQB+AAhzcQB+AAwBdAAELmNwL3EAfgAPc3EAfgAQAAAAMnQAOm9yZy5lY2xpcHNlLmpkdC5pbnRlcm5hbC5qdW5pdDQucnVubmVyLkpVbml0NFRlc3RSZWZlcmVuY2V0ABhKVW5pdDRUZXN0UmVmZXJlbmNlLmphdmFxAH4ANHNxAH4ACHNxAH4ADAF0AAQuY3AvcQB+AA9zcQB+ABAAAAAmdAAzb3JnLmVjbGlwc2UuamR0LmludGVybmFsLmp1bml0LnJ1bm5lci5UZXN0RXhlY3V0aW9udAASVGVzdEV4ZWN1dGlvbi5qYXZhcQB+ADRzcQB+AAhzcQB+AAwBdAAELmNwL3EAfgAPc3EAfgAQAAAB03QANm9yZy5lY2xpcHNlLmpkdC5pbnRlcm5hbC5qdW5pdC5ydW5uZXIuUmVtb3RlVGVzdFJ1bm5lcnQAFVJlbW90ZVRlc3RSdW5uZXIuamF2YXQACHJ1blRlc3Rzc3EAfgAIc3EAfgAMAXQABC5jcC9xAH4AD3NxAH4AEAAAAqtxAH4AgnEAfgCDcQB+AIRzcQB+AAhzcQB+AAwBdAAELmNwL3EAfgAPc3EAfgAQAAABhnEAfgCCcQB+AINxAH4ANHNxAH4ACHNxAH4ADAF0AAQuY3AvcQB+AA9zcQB+ABAAAADFcQB+AIJxAH4Ag3QABG1haW50ABZPTUcgSSd2ZSBiZWVuIGRlbGV0ZWQhcQB+AJJ0AEZvcmcuYXBhY2hlLmxvZ2dpbmcubG9nNGouY29yZS5pbXBsLlRocm93YWJsZVByb3h5VGVzdCREZWxldGVkRXhjZXB0aW9udXIANFtMb3JnLmFwYWNoZS5sb2dnaW5nLmxvZzRqLmNvcmUuaW1wbC5UaHJvd2FibGVQcm94eTv67QHghaLrOQIAAHhwAAAAAA==";
- final byte[] binaryDecoded = Base64Converter.parseBase64Binary(base64);
+ final byte[] binaryDecoded = decoder.decode(base64);
final ThrowableProxy proxy2 = deserialize(binaryDecoded);
assertEquals(this.getClass().getName() + "$DeletedException", proxy2.getName());
@@ -362,11 +364,11 @@
@Test
public void testStack() {
- final Map<String, ThrowableProxy.CacheEntry> map = new HashMap<>();
+ final Map<String, ThrowableProxyHelper.CacheEntry> map = new HashMap<>();
final Stack<Class<?>> stack = new Stack<>();
final Throwable throwable = new IllegalStateException("This is a test");
final ThrowableProxy proxy = new ThrowableProxy(throwable);
- final ExtendedStackTraceElement[] callerPackageData = proxy.toExtendedStackTrace(stack, map, null,
+ final ExtendedStackTraceElement[] callerPackageData = ThrowableProxyHelper.toExtendedStackTrace(proxy, stack, map, null,
throwable.getStackTrace());
assertNotNull("No package data returned", callerPackageData);
}
@@ -379,17 +381,16 @@
@Test
public void testStackWithUnloadableClass() throws Exception {
final Stack<Class<?>> stack = new Stack<>();
- final Map<String, ThrowableProxy.CacheEntry> map = new HashMap<>();
+ final Map<String, ThrowableProxyHelper.CacheEntry> map = new HashMap<>();
final String runtimeExceptionThrownAtUnloadableClass_base64 = "rO0ABXNyABpqYXZhLmxhbmcuUnVudGltZUV4Y2VwdGlvbp5fBkcKNIPlAgAAeHIAE2phdmEubGFuZy5FeGNlcHRpb27Q/R8+GjscxAIAAHhyABNqYXZhLmxhbmcuVGhyb3dhYmxl1cY1Jzl3uMsDAANMAAVjYXVzZXQAFUxqYXZhL2xhbmcvVGhyb3dhYmxlO0wADWRldGFpbE1lc3NhZ2V0ABJMamF2YS9sYW5nL1N0cmluZztbAApzdGFja1RyYWNldAAeW0xqYXZhL2xhbmcvU3RhY2tUcmFjZUVsZW1lbnQ7eHBxAH4ABnB1cgAeW0xqYXZhLmxhbmcuU3RhY2tUcmFjZUVsZW1lbnQ7AkYqPDz9IjkCAAB4cAAAAAFzcgAbamF2YS5sYW5nLlN0YWNrVHJhY2VFbGVtZW50YQnFmiY23YUCAARJAApsaW5lTnVtYmVyTAAOZGVjbGFyaW5nQ2xhc3NxAH4ABEwACGZpbGVOYW1lcQB+AARMAAptZXRob2ROYW1lcQB+AAR4cAAAAAZ0ADxvcmcuYXBhY2hlLmxvZ2dpbmcubG9nNGouY29yZS5pbXBsLkZvcmNlTm9EZWZDbGFzc0ZvdW5kRXJyb3J0AB5Gb3JjZU5vRGVmQ2xhc3NGb3VuZEVycm9yLmphdmF0AARtYWlueA==";
- final byte[] binaryDecoded = Base64Converter
- .parseBase64Binary(runtimeExceptionThrownAtUnloadableClass_base64);
+ final byte[] binaryDecoded = decoder.decode(runtimeExceptionThrownAtUnloadableClass_base64);
final ByteArrayInputStream inArr = new ByteArrayInputStream(binaryDecoded);
final ObjectInputStream in = new ObjectInputStream(inArr);
final Throwable throwable = (Throwable) in.readObject();
final ThrowableProxy subject = new ThrowableProxy(throwable);
- subject.toExtendedStackTrace(stack, map, null, throwable.getStackTrace());
+ ThrowableProxyHelper.toExtendedStackTrace(subject, stack, map, null, throwable.getStackTrace());
}
/**
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/AbstractStringLayoutTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/AbstractStringLayoutTest.java
index 04505d4..7b14556 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/AbstractStringLayoutTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/AbstractStringLayoutTest.java
@@ -74,4 +74,5 @@
assertEquals("capacity, trimmed to MAX_STRING_BUILDER_SIZE", ConcreteStringLayout.MAX_STRING_BUILDER_SIZE, sb3.capacity());
assertEquals("empty, ready for use", 0, sb3.length());
}
-}
\ No newline at end of file
+
+}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/GelfLayoutTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/GelfLayoutTest.java
index 095bec6..8b8736f 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/GelfLayoutTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/GelfLayoutTest.java
@@ -45,9 +45,9 @@
import static org.junit.Assert.assertEquals;
public 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";
@@ -61,7 +61,7 @@
private static final String VALUE1 = "Value1";
@Rule
- public final ThreadContextRule threadContextRule = new ThreadContextRule();
+ public final ThreadContextRule threadContextRule = new ThreadContextRule();
@AfterClass
public static void cleanupClass() {
@@ -265,4 +265,27 @@
assertEquals("1458741206.653", GelfLayout.formatTimestamp(1458741206653L).toString());
assertEquals("9223372036854775.807", GelfLayout.formatTimestamp(Long.MAX_VALUE).toString());
}
+
+ private void testRequiresLocation(String messagePattern, Boolean requiresLocation) {
+ GelfLayout layout = GelfLayout.newBuilder()
+ .setMessagePattern(messagePattern)
+ .build();
+
+ assertEquals(layout.requiresLocation(), requiresLocation);
+ }
+
+ @Test
+ public void testRequiresLocationPatternNotSet() {
+ testRequiresLocation(null, false);
+ }
+
+ @Test
+ public void testRequiresLocationPatternNotContainsLocation() {
+ testRequiresLocation("%m %n", false);
+ }
+
+ @Test
+ public void testRequiresLocationPatternContainsLocation() {
+ testRequiresLocation("%C %m %t", true);
+ }
}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/GelfLayoutTest3.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/GelfLayoutTest3.java
new file mode 100644
index 0000000..9e02b65
--- /dev/null
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/GelfLayoutTest3.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 com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.ThreadContext;
+import org.apache.logging.log4j.core.lookup.JavaLookup;
+import org.apache.logging.log4j.junit.LoggerContextRule;
+import org.junit.After;
+import org.junit.ClassRule;
+import org.junit.Test;
+
+import java.io.IOException;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+public class GelfLayoutTest3 {
+
+ @ClassRule
+ public static LoggerContextRule context = new LoggerContextRule("GelfLayoutTest3.xml");
+
+ @After
+ public void teardown() throws Exception {
+ ThreadContext.clearMap();
+ }
+
+ @Test
+ public void gelfLayout() throws IOException {
+ final Logger logger = context.getLogger();
+ ThreadContext.put("loginId", "rgoers");
+ ThreadContext.put("internalId", "12345");
+ logger.info("My Test Message");
+ final String gelf = context.getListAppender("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("_loginId"));
+ assertEquals("rgoers", json.get("_loginId").asText());
+ assertNull(json.get("_internalId"));
+ assertNull(json.get("_requestId"));
+ String message = json.get("full_message").asText();
+ assertTrue(message.contains("loginId=rgoers"));
+ assertTrue(message.contains("GelfLayoutTest3"));
+ }
+
+}
+
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/HtmlLayoutTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/HtmlLayoutTest.java
index 80acb1f..d0ac614 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/HtmlLayoutTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/HtmlLayoutTest.java
@@ -73,7 +73,7 @@
@Test
public void testContentType() {
final HtmlLayout layout = HtmlLayout.newBuilder()
- .withContentType("text/html; charset=UTF-16")
+ .setContentType("text/html; charset=UTF-16")
.build();
assertEquals("text/html; charset=UTF-16", layout.getContentType());
// TODO: make sure this following bit works as well
@@ -106,7 +106,7 @@
}
// set up appender
final HtmlLayout layout = HtmlLayout.newBuilder()
- .withLocationInfo(includeLocation)
+ .setLocationInfo(includeLocation)
.build();
final ListAppender appender = new ListAppender("List", null, layout, true, false);
appender.start();
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/Log4j2_1482_Test.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/Log4j2_1482_Test.java
index abd1852..9ef0487 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/Log4j2_1482_Test.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/Log4j2_1482_Test.java
@@ -89,4 +89,4 @@
loopingRun(1);
}
-}
\ No newline at end of file
+}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/LogEventFixtures.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/LogEventFixtures.java
index 4e85eb7..2fffad4 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/LogEventFixtures.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/LogEventFixtures.java
@@ -88,7 +88,6 @@
final boolean includeContext, final boolean includeStacktrace) {
assertEquals(expected.getClass(), actual.getClass());
assertEquals(includeContext ? expected.getContextData() : ContextDataFactory.createContextData(), actual.getContextData());
- assertEquals(includeContext ? expected.getContextMap() : Collections.EMPTY_MAP, actual.getContextMap());
assertEquals(expected.getContextStack(), actual.getContextStack());
assertEquals(expected.getLevel(), actual.getLevel());
assertEquals(expected.getLoggerName(), actual.getLoggerName());
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/PatternLayoutMainMapLookupTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/PatternLayoutMainMapLookupTest.java
index a2881df..120af89 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/PatternLayoutMainMapLookupTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/PatternLayoutMainMapLookupTest.java
@@ -53,12 +53,14 @@
final ListAppender listApp = context.getListAppender("List");
final Logger logger = context.getLogger(this.getClass().getName());
logger.info("Hello World");
- final List<String> messages = listApp.getMessages();
- Assert.assertFalse(messages.isEmpty());
- final String messagesStr = messages.toString();
- Assert.assertEquals(messagesStr, "Header: value0", messages.get(0));
+ final List<String> initialMessages = listApp.getMessages();
+ Assert.assertFalse(initialMessages.isEmpty());
+ final String messagesStr = initialMessages.toString();
+ Assert.assertEquals(messagesStr, "Header: value0", initialMessages.get(0));
listApp.stop();
- Assert.assertEquals(messagesStr, "Footer: value1", messages.get(2));
+ final List<String> finalMessages = listApp.getMessages();
+ Assert.assertEquals(3, finalMessages.size());
+ Assert.assertEquals("Footer: value1", finalMessages.get(2));
}
}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/PatternLayoutTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/PatternLayoutTest.java
index 4749546..d6f3a80 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/PatternLayoutTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/PatternLayoutTest.java
@@ -117,8 +117,8 @@
@Test
public void testEqualsEmptyMarker() throws Exception {
// replace "[]" with the empty string
- final PatternLayout layout = PatternLayout.newBuilder().withPattern("[%logger]%equals{[%marker]}{[]}{} %msg")
- .withConfiguration(ctx.getConfiguration()).build();
+ final PatternLayout layout = PatternLayout.newBuilder().setPattern("[%logger]%equals{[%marker]}{[]}{} %msg")
+ .setConfiguration(ctx.getConfiguration()).build();
// Not empty marker
final LogEvent event1 = Log4jLogEvent.newBuilder() //
.setLoggerName(this.getClass().getName()).setLoggerFqcn("org.apache.logging.log4j.core.Logger") //
@@ -142,8 +142,8 @@
public void testHeaderFooterJavaLookup() throws Exception {
// % does not work here.
final String pattern = "%d{UNIX} MyApp%n${java:version}%n${java:runtime}%n${java:vm}%n${java:os}%n${java:hw}";
- final PatternLayout layout = PatternLayout.newBuilder().withConfiguration(ctx.getConfiguration())
- .withHeader("Header: " + pattern).withFooter("Footer: " + pattern).build();
+ final PatternLayout layout = PatternLayout.newBuilder().setConfiguration(ctx.getConfiguration())
+ .setHeader("Header: " + pattern).setFooter("Footer: " + pattern).build();
final byte[] header = layout.getHeader();
assertNotNull("No header", header);
final String headerStr = new String(header);
@@ -171,8 +171,8 @@
@Test
public void testHeaderFooterMainLookup() {
MainMapLookup.setMainArguments("value0", "value1", "value2");
- final PatternLayout layout = PatternLayout.newBuilder().withConfiguration(ctx.getConfiguration())
- .withHeader("${main:0}").withFooter("${main:2}").build();
+ final PatternLayout layout = PatternLayout.newBuilder().setConfiguration(ctx.getConfiguration())
+ .setHeader("${main:0}").setFooter("${main:2}").build();
final byte[] header = layout.getHeader();
assertNotNull("No header", header);
final String headerStr = new String(header);
@@ -186,8 +186,8 @@
@Test
public void testHeaderFooterThreadContext() throws Exception {
- final PatternLayout layout = PatternLayout.newBuilder().withPattern("%d{UNIX} %m")
- .withConfiguration(ctx.getConfiguration()).withHeader("${ctx:header}").withFooter("${ctx:footer}")
+ final PatternLayout layout = PatternLayout.newBuilder().setPattern("%d{UNIX} %m")
+ .setConfiguration(ctx.getConfiguration()).setHeader("${ctx:header}").setFooter("${ctx:footer}")
.build();
ThreadContext.put("header", "Hello world Header");
ThreadContext.put("footer", "Hello world Footer");
@@ -199,8 +199,8 @@
private void testMdcPattern(final String patternStr, final String expectedStr, final boolean useThreadContext)
throws Exception {
- final PatternLayout layout = PatternLayout.newBuilder().withPattern(patternStr)
- .withConfiguration(ctx.getConfiguration()).build();
+ final PatternLayout layout = PatternLayout.newBuilder().setPattern(patternStr)
+ .setConfiguration(ctx.getConfiguration()).build();
if (useThreadContext) {
ThreadContext.put("key1", "value1");
ThreadContext.put("key2", "value2");
@@ -247,9 +247,17 @@
public void testPatternSelector() throws Exception {
final PatternMatch[] patterns = new PatternMatch[1];
patterns[0] = new PatternMatch("FLOW", "%d %-5p [%t]: ====== %C{1}.%M:%L %m ======%n");
- final PatternSelector selector = MarkerPatternSelector.createSelector(patterns, "%d %-5p [%t]: %m%n", true, true, ctx.getConfiguration());
- final PatternLayout layout = PatternLayout.newBuilder().withPatternSelector(selector)
- .withConfiguration(ctx.getConfiguration()).build();
+ // @formatter:off
+ final PatternSelector selector = MarkerPatternSelector.newBuilder()
+ .setProperties(patterns)
+ .setDefaultPattern("%d %-5p [%t]: %m%n")
+ .setAlwaysWriteExceptions(true)
+ .setNoConsoleNoAnsi(true)
+ .setConfiguration(ctx.getConfiguration())
+ .build();
+ // @formatter:on
+ final PatternLayout layout = PatternLayout.newBuilder().setPatternSelector(selector)
+ .setConfiguration(ctx.getConfiguration()).build();
final LogEvent event1 = Log4jLogEvent.newBuilder() //
.setLoggerName(this.getClass().getName()).setLoggerFqcn("org.apache.logging.log4j.core.layout.PatternLayoutTest$FauxLogger")
.setMarker(MarkerManager.getMarker("FLOW"))
@@ -270,8 +278,8 @@
@Test
public void testRegex() throws Exception {
- final PatternLayout layout = PatternLayout.newBuilder().withPattern(regexPattern)
- .withConfiguration(ctx.getConfiguration()).build();
+ final PatternLayout layout = PatternLayout.newBuilder().setPattern(regexPattern)
+ .setConfiguration(ctx.getConfiguration()).build();
final LogEvent event = Log4jLogEvent.newBuilder() //
.setLoggerName(this.getClass().getName()).setLoggerFqcn("org.apache.logging.log4j.core.Logger") //
.setLevel(Level.INFO) //
@@ -283,8 +291,8 @@
@Test
public void testRegexEmptyMarker() throws Exception {
// replace "[]" with the empty string
- final PatternLayout layout = PatternLayout.newBuilder().withPattern("[%logger]%replace{[%marker]}{\\[\\]}{} %msg")
- .withConfiguration(ctx.getConfiguration()).build();
+ final PatternLayout layout = PatternLayout.newBuilder().setPattern("[%logger]%replace{[%marker]}{\\[\\]}{} %msg")
+ .setConfiguration(ctx.getConfiguration()).build();
// Not empty marker
final LogEvent event1 = Log4jLogEvent.newBuilder() //
.setLoggerName(this.getClass().getName()).setLoggerFqcn("org.apache.logging.log4j.core.Logger") //
@@ -308,8 +316,8 @@
@Test
public void testEqualsMarkerWithMessageSubstitution() throws Exception {
// replace "[]" with the empty string
- final PatternLayout layout = PatternLayout.newBuilder().withPattern("[%logger]%equals{[%marker]}{[]}{[%msg]}")
- .withConfiguration(ctx.getConfiguration()).build();
+ final PatternLayout layout = PatternLayout.newBuilder().setPattern("[%logger]%equals{[%marker]}{[]}{[%msg]}")
+ .setConfiguration(ctx.getConfiguration()).build();
// Not empty marker
final LogEvent event1 = Log4jLogEvent.newBuilder() //
.setLoggerName(this.getClass().getName()).setLoggerFqcn("org.apache.logging.log4j.core.Logger") //
@@ -329,8 +337,8 @@
@Test
public void testSpecialChars() throws Exception {
- final PatternLayout layout = PatternLayout.newBuilder().withPattern("\\\\%level\\t%msg\\n\\t%logger\\r\\n\\f")
- .withConfiguration(ctx.getConfiguration()).build();
+ final PatternLayout layout = PatternLayout.newBuilder().setPattern("\\\\%level\\t%msg\\n\\t%logger\\r\\n\\f")
+ .setConfiguration(ctx.getConfiguration()).build();
final LogEvent event = Log4jLogEvent.newBuilder() //
.setLoggerName(this.getClass().getName()).setLoggerFqcn("org.apache.logging.log4j.core.Logger") //
.setLevel(Level.INFO) //
@@ -345,8 +353,8 @@
@Test
public void testUnixTime() throws Exception {
- final PatternLayout layout = PatternLayout.newBuilder().withPattern("%d{UNIX} %m")
- .withConfiguration(ctx.getConfiguration()).build();
+ final PatternLayout layout = PatternLayout.newBuilder().setPattern("%d{UNIX} %m")
+ .setConfiguration(ctx.getConfiguration()).build();
final LogEvent event1 = Log4jLogEvent.newBuilder() //
.setLoggerName(this.getClass().getName()).setLoggerFqcn("org.apache.logging.log4j.core.Logger") //
.setLevel(Level.INFO) //
@@ -365,8 +373,8 @@
@SuppressWarnings("unused")
private void testUnixTime(final String pattern) throws Exception {
- final PatternLayout layout = PatternLayout.newBuilder().withPattern(pattern + " %m")
- .withConfiguration(ctx.getConfiguration()).build();
+ final PatternLayout layout = PatternLayout.newBuilder().setPattern(pattern + " %m")
+ .setConfiguration(ctx.getConfiguration()).build();
final LogEvent event1 = Log4jLogEvent.newBuilder() //
.setLoggerName(this.getClass().getName()).setLoggerFqcn("org.apache.logging.log4j.core.Logger") //
.setLevel(Level.INFO) //
@@ -385,8 +393,8 @@
@Test
public void testUnixTimeMillis() throws Exception {
- final PatternLayout layout = PatternLayout.newBuilder().withPattern("%d{UNIX_MILLIS} %m")
- .withConfiguration(ctx.getConfiguration()).build();
+ final PatternLayout layout = PatternLayout.newBuilder().setPattern("%d{UNIX_MILLIS} %m")
+ .setConfiguration(ctx.getConfiguration()).build();
final LogEvent event1 = Log4jLogEvent.newBuilder() //
.setLoggerName(this.getClass().getName()).setLoggerFqcn("org.apache.logging.log4j.core.Logger") //
.setLevel(Level.INFO) //
@@ -405,23 +413,23 @@
@Test
public void testUsePlatformDefaultIfNoCharset() throws Exception {
- final PatternLayout layout = PatternLayout.newBuilder().withPattern("%m")
- .withConfiguration(ctx.getConfiguration()).build();
+ final PatternLayout layout = PatternLayout.newBuilder().setPattern("%m")
+ .setConfiguration(ctx.getConfiguration()).build();
assertEquals(Charset.defaultCharset(), layout.getCharset());
}
@Test
public void testUseSpecifiedCharsetIfExists() throws Exception {
- final PatternLayout layout = PatternLayout.newBuilder().withPattern("%m")
- .withConfiguration(ctx.getConfiguration()).withCharset(StandardCharsets.UTF_8).build();
+ final PatternLayout layout = PatternLayout.newBuilder().setPattern("%m")
+ .setConfiguration(ctx.getConfiguration()).setCharset(StandardCharsets.UTF_8).build();
assertEquals(StandardCharsets.UTF_8, layout.getCharset());
}
@Test
public void testLoggerNameTruncationByRetainingPartsFromEnd() throws Exception {
{
- final PatternLayout layout = PatternLayout.newBuilder().withPattern("%c{1} %m")
- .withConfiguration(ctx.getConfiguration()).build();
+ final PatternLayout layout = PatternLayout.newBuilder().setPattern("%c{1} %m")
+ .setConfiguration(ctx.getConfiguration()).build();
final LogEvent event1 = Log4jLogEvent.newBuilder()
.setLoggerName(this.getClass().getName()).setLoggerFqcn("org.apache.logging.log4j.core.Logger")
.setLevel(Level.INFO)
@@ -430,8 +438,8 @@
assertEquals(this.getClass().getName().substring(this.getClass().getName().lastIndexOf(".") + 1) + " Hello, world 1!", new String(result1));
}
{
- final PatternLayout layout = PatternLayout.newBuilder().withPattern("%c{2} %m")
- .withConfiguration(ctx.getConfiguration()).build();
+ final PatternLayout layout = PatternLayout.newBuilder().setPattern("%c{2} %m")
+ .setConfiguration(ctx.getConfiguration()).build();
final LogEvent event1 = Log4jLogEvent.newBuilder()
.setLoggerName(this.getClass().getName()).setLoggerFqcn("org.apache.logging.log4j.core.Logger")
.setLevel(Level.INFO)
@@ -442,8 +450,8 @@
assertEquals(this.getClass().getName().substring(name.length() + 1) + " Hello, world 1!", new String(result1));
}
{
- final PatternLayout layout = PatternLayout.newBuilder().withPattern("%c{20} %m")
- .withConfiguration(ctx.getConfiguration()).build();
+ final PatternLayout layout = PatternLayout.newBuilder().setPattern("%c{20} %m")
+ .setConfiguration(ctx.getConfiguration()).build();
final LogEvent event1 = Log4jLogEvent.newBuilder()
.setLoggerName(this.getClass().getName()).setLoggerFqcn("org.apache.logging.log4j.core.Logger")
.setLevel(Level.INFO)
@@ -456,8 +464,8 @@
@Test
public void testCallersFqcnTruncationByRetainingPartsFromEnd() throws Exception {
{
- final PatternLayout layout = PatternLayout.newBuilder().withPattern("%C{1} %m")
- .withConfiguration(ctx.getConfiguration()).build();
+ final PatternLayout layout = PatternLayout.newBuilder().setPattern("%C{1} %m")
+ .setConfiguration(ctx.getConfiguration()).build();
final LogEvent event1 = Log4jLogEvent.newBuilder()
.setLoggerName(this.getClass().getName()).setLoggerFqcn("org.apache.logging.log4j.core.Logger")
.setLevel(Level.INFO)
@@ -468,8 +476,8 @@
assertEquals(this.getClass().getName().substring(this.getClass().getName().lastIndexOf(".") + 1) + " Hello, world 1!", new String(result1));
}
{
- final PatternLayout layout = PatternLayout.newBuilder().withPattern("%C{2} %m")
- .withConfiguration(ctx.getConfiguration()).build();
+ final PatternLayout layout = PatternLayout.newBuilder().setPattern("%C{2} %m")
+ .setConfiguration(ctx.getConfiguration()).build();
final LogEvent event1 = Log4jLogEvent.newBuilder()
.setLoggerName(this.getClass().getName()).setLoggerFqcn("org.apache.logging.log4j.core.Logger")
.setLevel(Level.INFO)
@@ -482,8 +490,8 @@
assertEquals(this.getClass().getName().substring(name.length() + 1) + " Hello, world 1!", new String(result1));
}
{
- final PatternLayout layout = PatternLayout.newBuilder().withPattern("%C{20} %m")
- .withConfiguration(ctx.getConfiguration()).build();
+ final PatternLayout layout = PatternLayout.newBuilder().setPattern("%C{20} %m")
+ .setConfiguration(ctx.getConfiguration()).build();
final LogEvent event1 = Log4jLogEvent.newBuilder()
.setLoggerName(this.getClass().getName()).setLoggerFqcn("org.apache.logging.log4j.core.Logger")
.setLevel(Level.INFO)
@@ -494,8 +502,8 @@
assertEquals(this.getClass().getName() + " Hello, world 1!", new String(result1));
}
{
- final PatternLayout layout = PatternLayout.newBuilder().withPattern("%class{1} %m")
- .withConfiguration(ctx.getConfiguration()).build();
+ final PatternLayout layout = PatternLayout.newBuilder().setPattern("%class{1} %m")
+ .setConfiguration(ctx.getConfiguration()).build();
final LogEvent event1 = Log4jLogEvent.newBuilder()
.setLoggerName(this.getClass().getName()).setLoggerFqcn("org.apache.logging.log4j.core.Logger")
.setLevel(Level.INFO)
@@ -510,8 +518,8 @@
@Test
public void testLoggerNameTruncationByDroppingPartsFromFront() throws Exception {
{
- final PatternLayout layout = PatternLayout.newBuilder().withPattern("%c{-1} %m")
- .withConfiguration(ctx.getConfiguration()).build();
+ final PatternLayout layout = PatternLayout.newBuilder().setPattern("%c{-1} %m")
+ .setConfiguration(ctx.getConfiguration()).build();
final LogEvent event1 = Log4jLogEvent.newBuilder()
.setLoggerName(this.getClass().getName()).setLoggerFqcn("org.apache.logging.log4j.core.Logger")
.setLevel(Level.INFO)
@@ -521,8 +529,8 @@
assertEquals(name + " Hello, world 1!", new String(result1));
}
{
- final PatternLayout layout = PatternLayout.newBuilder().withPattern("%c{-3} %m")
- .withConfiguration(ctx.getConfiguration()).build();
+ final PatternLayout layout = PatternLayout.newBuilder().setPattern("%c{-3} %m")
+ .setConfiguration(ctx.getConfiguration()).build();
final LogEvent event1 = Log4jLogEvent.newBuilder()
.setLoggerName(this.getClass().getName()).setLoggerFqcn("org.apache.logging.log4j.core.Logger")
.setLevel(Level.INFO)
@@ -534,8 +542,8 @@
assertEquals(name + " Hello, world 1!", new String(result1));
}
{
- final PatternLayout layout = PatternLayout.newBuilder().withPattern("%logger{-3} %m")
- .withConfiguration(ctx.getConfiguration()).build();
+ final PatternLayout layout = PatternLayout.newBuilder().setPattern("%logger{-3} %m")
+ .setConfiguration(ctx.getConfiguration()).build();
final LogEvent event1 = Log4jLogEvent.newBuilder()
.setLoggerName(this.getClass().getName()).setLoggerFqcn("org.apache.logging.log4j.core.Logger")
.setLevel(Level.INFO)
@@ -547,8 +555,8 @@
assertEquals(name + " Hello, world 1!", new String(result1));
}
{
- final PatternLayout layout = PatternLayout.newBuilder().withPattern("%c{-20} %m")
- .withConfiguration(ctx.getConfiguration()).build();
+ final PatternLayout layout = PatternLayout.newBuilder().setPattern("%c{-20} %m")
+ .setConfiguration(ctx.getConfiguration()).build();
final LogEvent event1 = Log4jLogEvent.newBuilder()
.setLoggerName(this.getClass().getName()).setLoggerFqcn("org.apache.logging.log4j.core.Logger")
.setLevel(Level.INFO)
@@ -562,8 +570,8 @@
@Test
public void testCallersFqcnTruncationByDroppingPartsFromFront() throws Exception {
{
- final PatternLayout layout = PatternLayout.newBuilder().withPattern("%C{-1} %m")
- .withConfiguration(ctx.getConfiguration()).build();
+ final PatternLayout layout = PatternLayout.newBuilder().setPattern("%C{-1} %m")
+ .setConfiguration(ctx.getConfiguration()).build();
final LogEvent event1 = Log4jLogEvent.newBuilder()
.setLoggerName(this.getClass().getName()).setLoggerFqcn("org.apache.logging.log4j.core.Logger")
.setLevel(Level.INFO)
@@ -575,8 +583,8 @@
assertEquals(name + " Hello, world 1!", new String(result1));
}
{
- final PatternLayout layout = PatternLayout.newBuilder().withPattern("%C{-3} %m")
- .withConfiguration(ctx.getConfiguration()).build();
+ final PatternLayout layout = PatternLayout.newBuilder().setPattern("%C{-3} %m")
+ .setConfiguration(ctx.getConfiguration()).build();
final LogEvent event1 = Log4jLogEvent.newBuilder()
.setLoggerName(this.getClass().getName()).setLoggerFqcn("org.apache.logging.log4j.core.Logger")
.setLevel(Level.INFO)
@@ -590,8 +598,8 @@
assertEquals(name + " Hello, world 1!", new String(result1));
}
{
- final PatternLayout layout = PatternLayout.newBuilder().withPattern("%class{-3} %m")
- .withConfiguration(ctx.getConfiguration()).build();
+ final PatternLayout layout = PatternLayout.newBuilder().setPattern("%class{-3} %m")
+ .setConfiguration(ctx.getConfiguration()).build();
final LogEvent event1 = Log4jLogEvent.newBuilder()
.setLoggerName(this.getClass().getName()).setLoggerFqcn("org.apache.logging.log4j.core.Logger")
.setLevel(Level.INFO)
@@ -605,8 +613,8 @@
assertEquals(name + " Hello, world 1!", new String(result1));
}
{
- final PatternLayout layout = PatternLayout.newBuilder().withPattern("%C{-20} %m")
- .withConfiguration(ctx.getConfiguration()).build();
+ final PatternLayout layout = PatternLayout.newBuilder().setPattern("%C{-20} %m")
+ .setConfiguration(ctx.getConfiguration()).build();
final LogEvent event1 = Log4jLogEvent.newBuilder()
.setLoggerName(this.getClass().getName()).setLoggerFqcn("org.apache.logging.log4j.core.Logger")
.setLevel(Level.INFO)
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/PatternSelectorTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/PatternSelectorTest.java
index 152c605..a332f1e 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/PatternSelectorTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/PatternSelectorTest.java
@@ -38,12 +38,20 @@
LoggerContext ctx = LoggerContext.getContext();
@Test
- public void testPatternSelector() throws Exception {
+ public void testMarkerPatternSelector() throws Exception {
final PatternMatch[] patterns = new PatternMatch[1];
patterns[0] = new PatternMatch("FLOW", "%d %-5p [%t]: ====== %C{1}.%M:%L %m ======%n");
- final PatternSelector selector = MarkerPatternSelector.createSelector(patterns, "%d %-5p [%t]: %m%n", true, true, ctx.getConfiguration());
- final PatternLayout layout = PatternLayout.newBuilder().withPatternSelector(selector)
- .withConfiguration(ctx.getConfiguration()).build();
+ // @formatter:off
+ final PatternSelector selector = MarkerPatternSelector.newBuilder()
+ .setProperties(patterns)
+ .setDefaultPattern("%d %-5p [%t]: %m%n")
+ .setAlwaysWriteExceptions(true)
+ .setNoConsoleNoAnsi(true)
+ .setConfiguration(ctx.getConfiguration())
+ .build();
+ // @formatter:on
+ final PatternLayout layout = PatternLayout.newBuilder().setPatternSelector(selector)
+ .setConfiguration(ctx.getConfiguration()).build();
final LogEvent event1 = Log4jLogEvent.newBuilder() //
.setLoggerName(this.getClass().getName()).setLoggerFqcn("org.apache.logging.log4j.core.layout.PatternSelectorTest$FauxLogger")
.setMarker(MarkerManager.getMarker("FLOW"))
@@ -51,7 +59,7 @@
.setIncludeLocation(true)
.setMessage(new SimpleMessage("entry")).build();
final String result1 = new FauxLogger().formatEvent(event1, layout);
- final String expectSuffix1 = String.format("====== PatternSelectorTest.testPatternSelector:53 entry ======%n");
+ final String expectSuffix1 = String.format("====== PatternSelectorTest.testMarkerPatternSelector:61 entry ======%n");
assertTrue("Unexpected result: " + result1, result1.endsWith(expectSuffix1));
final LogEvent event2 = Log4jLogEvent.newBuilder() //
.setLoggerName(this.getClass().getName()).setLoggerFqcn("org.apache.logging.log4j.core.Logger") //
@@ -62,4 +70,27 @@
assertTrue("Unexpected result: " + result2, result2.endsWith(expectSuffix2));
}
+ @Test
+ public void testLevelPatternSelector() throws Exception {
+ final PatternMatch[] patterns = new PatternMatch[1];
+ patterns[0] = new PatternMatch("TRACE", "%d %-5p [%t]: ====== %C{1}.%M:%L %m ======%n");
+ final PatternSelector selector = LevelPatternSelector.createSelector(patterns, "%d %-5p [%t]: %m%n", true, true, ctx.getConfiguration());
+ final PatternLayout layout = PatternLayout.newBuilder().setPatternSelector(selector)
+ .setConfiguration(ctx.getConfiguration()).build();
+ final LogEvent event1 = Log4jLogEvent.newBuilder() //
+ .setLoggerName(this.getClass().getName()).setLoggerFqcn("org.apache.logging.log4j.core.layout.PatternSelectorTest$FauxLogger")
+ .setLevel(Level.TRACE) //
+ .setIncludeLocation(true)
+ .setMessage(new SimpleMessage("entry")).build();
+ final String result1 = new FauxLogger().formatEvent(event1, layout);
+ final String expectSuffix1 = String.format("====== PatternSelectorTest.testLevelPatternSelector:85 entry ======%n");
+ assertTrue("Unexpected result: " + result1, result1.endsWith(expectSuffix1));
+ final LogEvent event2 = Log4jLogEvent.newBuilder() //
+ .setLoggerName(this.getClass().getName()).setLoggerFqcn("org.apache.logging.log4j.core.Logger") //
+ .setLevel(Level.INFO) //
+ .setMessage(new SimpleMessage("Hello, world 1!")).build();
+ final String result2 = new String(layout.toByteArray(event2));
+ final String expectSuffix2 = String.format("Hello, world 1!%n");
+ assertTrue("Unexpected result: " + result2, result2.endsWith(expectSuffix2));
+ }
}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/Rfc5424LayoutTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/Rfc5424LayoutTest.java
index 1987c44..4d900a1 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/Rfc5424LayoutTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/Rfc5424LayoutTest.java
@@ -36,7 +36,7 @@
import org.apache.logging.log4j.message.StructuredDataMessage;
import org.apache.logging.log4j.status.StatusLogger;
import org.apache.logging.log4j.test.appender.ListAppender;
-import org.apache.logging.log4j.util.ProcessIdUtil;
+import org.apache.logging.log4j.core.util.ProcessIdUtil;
import org.apache.logging.log4j.util.Strings;
import org.junit.AfterClass;
import org.junit.Assert;
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/SerializedLayoutTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/SerializedLayoutTest.java
deleted file mode 100644
index c01c82e..0000000
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/SerializedLayoutTest.java
+++ /dev/null
@@ -1,189 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache license, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the license for the specific language governing permissions and
- * limitations under the license.
- */
-package org.apache.logging.log4j.core.layout;
-
-import java.io.ByteArrayInputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.ObjectInputStream;
-import java.util.List;
-import java.util.Map;
-
-import org.apache.logging.log4j.Level;
-import org.apache.logging.log4j.LoggingException;
-import org.apache.logging.log4j.ThreadContext;
-import org.apache.logging.log4j.core.Appender;
-import org.apache.logging.log4j.core.BasicConfigurationFactory;
-import org.apache.logging.log4j.core.LogEvent;
-import org.apache.logging.log4j.core.Logger;
-import org.apache.logging.log4j.core.LoggerContext;
-import org.apache.logging.log4j.core.config.ConfigurationFactory;
-import org.apache.logging.log4j.core.impl.Log4jLogEvent;
-import org.apache.logging.log4j.junit.ThreadContextRule;
-import org.apache.logging.log4j.message.SimpleMessage;
-import org.apache.logging.log4j.test.appender.ListAppender;
-import org.apache.logging.log4j.util.FilteredObjectInputStream;
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-import org.junit.Rule;
-import org.junit.Test;
-
-import static org.junit.Assert.*;
-
-/**
- *
- */
-public class SerializedLayoutTest {
- private static final String DAT_PATH = "target/test-classes/serializedEvent.dat";
- LoggerContext ctx = LoggerContext.getContext();
- Logger root = ctx.getRootLogger();
-
- static ConfigurationFactory cf = new BasicConfigurationFactory();
-
- static boolean useObjectInputStream = false;
-
- @Rule
- public final ThreadContextRule threadContextRule = new ThreadContextRule();
-
- @BeforeClass
- public static void setupClass() {
- try {
- Class.forName("java.io.ObjectInputFilter");
- useObjectInputStream = true;
- } catch (ClassNotFoundException ex) {
- // Ignore the exception
- }
- ConfigurationFactory.setConfigurationFactory(cf);
- final LoggerContext ctx = LoggerContext.getContext();
- ctx.reconfigure();
- }
-
- @AfterClass
- public static void cleanupClass() {
- ConfigurationFactory.removeConfigurationFactory(cf);
- }
-
- private static final String body =
- "<log4j:message><![CDATA[empty mdc]]></log4j:message>\r";
-
- private static final String[] expected = {
- "Logger=root Level=DEBUG Message=starting mdc pattern test",
- "Logger=root Level=DEBUG Message=empty mdc",
- "Logger=root Level=DEBUG Message=filled mdc",
- "Logger=root Level=ERROR Message=finished mdc pattern test",
- "Logger=root Level=ERROR Message=Throwing an exception"
- };
-
-
- /**
- * Test case for MDC conversion pattern.
- */
- @Test
- public void testLayout() throws Exception {
- final Map<String, Appender> 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<byte[]> data = appender.getData();
- assertTrue(data.size() > 0);
- int i = 0;
- for (final byte[] item : data) {
- final ByteArrayInputStream bais = new ByteArrayInputStream(item);
- final ObjectInputStream ois = useObjectInputStream ? new ObjectInputStream(bais) :
- new FilteredObjectInputStream(bais);
- LogEvent event;
- try {
- event = (LogEvent) ois.readObject();
- } catch (final IOException ioe) {
- System.err.println("Exception processing item " + i);
- throw ioe;
- }
- assertTrue("Incorrect event", event.toString().equals(expected[i]));
- ++i;
- }
- for (final Appender app : appenders.values()) {
- root.addAppender(app);
- }
- }
-
- @Test
- public void testSerialization() throws Exception {
- final SerializedLayout layout = SerializedLayout.createLayout();
- final Throwable throwable = new LoggingException("Test");
- final LogEvent event = Log4jLogEvent.newBuilder() //
- .setLoggerName(this.getClass().getName()) //
- .setLoggerFqcn("org.apache.logging.log4j.core.Logger") //
- .setLevel(Level.INFO) //
- .setMessage(new SimpleMessage("Hello, world!")) //
- .setThrown(throwable) //
- .build();
- final byte[] result = layout.toByteArray(event);
- assertNotNull(result);
- final FileOutputStream fos = new FileOutputStream(DAT_PATH);
- fos.write(layout.getHeader());
- fos.write(result);
- fos.close();
- }
-
- @Test
- public void testDeserialization() throws Exception {
- testSerialization();
- final File file = new File(DAT_PATH);
- final FileInputStream fis = new FileInputStream(file);
- final ObjectInputStream ois = useObjectInputStream ? new ObjectInputStream(fis) :
- new FilteredObjectInputStream(fis);
- try {
- final LogEvent event = (LogEvent) ois.readObject();
- assertNotNull(event);
- } finally {
- ois.close();
- }
- }
-}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/StringBuilderEncoderTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/StringBuilderEncoderTest.java
index 51faed9..5958c22 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/StringBuilderEncoderTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/layout/StringBuilderEncoderTest.java
@@ -307,4 +307,4 @@
}
return result;
}
-}
\ No newline at end of file
+}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/CaseLookupTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/CaseLookupTest.java
new file mode 100644
index 0000000..ae7d5ac
--- /dev/null
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/CaseLookupTest.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.lookup;
+
+import java.util.Calendar;
+
+import org.apache.logging.log4j.core.AbstractLogEvent;
+import org.apache.logging.log4j.core.LogEvent;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+/**
+ *
+ */
+public class CaseLookupTest {
+
+
+ @Test
+ public void testLookup() {
+ final String testStr = "JabberWocky";
+ final String lower = "jabberwocky";
+ final String upper = "JABBERWOCKY";
+ StrLookup lookup = new LowerLookup();
+ String value = lookup.lookup(null, testStr);
+ assertNotNull(value);
+ assertEquals(lower, value);
+ lookup = new UpperLookup();
+ value = lookup.lookup(null, testStr);
+ assertNotNull(value);
+ assertEquals(upper, value);
+ }
+}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/EventLookupTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/EventLookupTest.java
new file mode 100644
index 0000000..96f6027
--- /dev/null
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/EventLookupTest.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.lookup;
+
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.Marker;
+import org.apache.logging.log4j.MarkerManager;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.impl.Log4jLogEvent;
+import org.apache.logging.log4j.message.SimpleMessage;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+/**
+ * Tests {@link MarkerLookup}.
+ *
+ * @since 2.4
+ */
+public class EventLookupTest {
+
+ private static final String ABSENT_MARKER_NAME = "NONE";
+ private final String markerName = "EventLookupTest";
+ private final StrLookup strLookup = new EventLookup();
+
+ @Test
+ public void testLookupEventMarker() {
+ final Marker marker = MarkerManager.getMarker(markerName);
+ final LogEvent event = Log4jLogEvent.newBuilder() //
+ .setLoggerName(this.getClass().getName()) //
+ .setMarker(marker) //
+ .setLoggerFqcn("org.apache.logging.log4j.core.Logger") //
+ .setLevel(Level.INFO) //
+ .setMessage(new SimpleMessage("Hello, world!")).build();
+ final String value = strLookup.lookup(event, "Marker");
+ assertEquals(markerName, value);
+ }
+
+ @Test
+ public void testLookupEventMessage() {
+ String msg = "Hello, world!";
+ final LogEvent event = Log4jLogEvent.newBuilder() //
+ .setLoggerName(this.getClass().getName()) //
+ .setLoggerFqcn("org.apache.logging.log4j.core.Logger") //
+ .setLevel(Level.INFO) //
+ .setMessage(new SimpleMessage(msg)).build();
+ final String value = strLookup.lookup(event, "Message");
+ assertEquals(msg, value);
+ }
+
+ @Test
+ public void testLookupEventLevel() {
+ String msg = "Hello, world!";
+ final LogEvent event = Log4jLogEvent.newBuilder() //
+ .setLoggerName(this.getClass().getName()) //
+ .setLoggerFqcn("org.apache.logging.log4j.core.Logger") //
+ .setLevel(Level.INFO) //
+ .setMessage(new SimpleMessage(msg)).build();
+ final String value = strLookup.lookup(event, "Level");
+ assertEquals(Level.INFO.toString(), value);
+ }
+
+ @Test
+ public void testLookupEventTimestamp() {
+ String msg = "Hello, world!";
+ long now = System.currentTimeMillis();
+ final LogEvent event = Log4jLogEvent.newBuilder() //
+ .setLoggerName(this.getClass().getName()) //
+ .setTimeMillis(now)
+ .setLoggerFqcn("org.apache.logging.log4j.core.Logger") //
+ .setLevel(Level.INFO) //
+ .setMessage(new SimpleMessage(msg)).build();
+ final String value = strLookup.lookup(event, "Timestamp");
+ assertEquals(Long.toString(now), value);
+ }
+
+ @Test
+ public void testLookupEventLogger() {
+ String msg = "Hello, world!";
+ final LogEvent event = Log4jLogEvent.newBuilder() //
+ .setLoggerName(this.getClass().getName()) //
+ .setLoggerFqcn("org.apache.logging.log4j.core.Logger") //
+ .setLevel(Level.INFO) //
+ .setMessage(new SimpleMessage(msg)).build();
+ final String value = strLookup.lookup(event, "Logger");
+ assertEquals(this.getClass().getName(), value);
+ }
+
+ @Test
+ public void testLookupEventThreadName() {
+ String msg = "Hello, world!";
+ final LogEvent event = Log4jLogEvent.newBuilder() //
+ .setLoggerName(this.getClass().getName()) //
+ .setThreadName("Main")
+ .setLoggerFqcn("org.apache.logging.log4j.core.Logger") //
+ .setLevel(Level.INFO) //
+ .setMessage(new SimpleMessage(msg)).build();
+ final String value = strLookup.lookup(event, "ThreadName");
+ assertEquals("Main", value);
+ }
+
+}
\ No newline at end of file
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/MainInputArgumentsLookupApp.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/MainInputArgumentsLookupApp.java
new file mode 100644
index 0000000..ddb8b8f
--- /dev/null
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/MainInputArgumentsLookupApp.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.logging.log4j.core.lookup;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.core.LoggerContext;
+import org.apache.logging.log4j.core.config.Configurator;
+
+/**
+ * Tests {@link org.apache.logging.log4j.core.lookup.MainMapLookup#MAIN_SINGLETON} from the command line, not a real
+ * JUnit test.
+ *
+ * From an IDE or CLI: --file foo.txt
+ *
+ * @since 2.4
+ */
+public class MainInputArgumentsLookupApp {
+
+ public static void main(final String[] args) {
+ MainMapLookup.setMainArguments(args);
+ try (final LoggerContext ctx = Configurator.initialize(MainInputArgumentsLookupApp.class.getName(),
+ "target/test-classes/log4j-lookup-main.xml")) {
+ LogManager.getLogger().error("this is an error message");
+ }
+ }
+
+}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/MainInputArgumentsLookupTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/MainInputArgumentsLookupTest.java
deleted file mode 100644
index 6150afd..0000000
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/MainInputArgumentsLookupTest.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache license, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the license for the specific language governing permissions and
- * limitations under the license.
- */
-package org.apache.logging.log4j.core.lookup;
-
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.core.LoggerContext;
-import org.apache.logging.log4j.core.config.Configurator;
-
-/**
- * Tests {@link org.apache.logging.log4j.core.lookup.MainMapLookup#MAIN_SINGLETON} from the command line, not a real
- * JUnit test.
- *
- * From an IDE or CLI: --file foo.txt
- *
- * @since 2.4
- */
-public class MainInputArgumentsLookupTest {
-
- public static void main(final String[] args) {
- MainMapLookup.setMainArguments(args);
- try (final LoggerContext ctx = Configurator.initialize(MainInputArgumentsLookupTest.class.getName(),
- "target/test-classes/log4j-lookup-main.xml")) {
- LogManager.getLogger().error("this is an error message");
- }
- }
-
-}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/MainInputArgumentsMapLookup.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/MainInputArgumentsMapLookup.java
index f6bad22..cce9d1e 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/MainInputArgumentsMapLookup.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/MainInputArgumentsMapLookup.java
@@ -34,7 +34,7 @@
final StackTraceElement[] stackTraceElements = entry.getValue();
entry.getKey();
// Can't use the thread name to look for "main" since anyone can set it.
- // Can't use thread ID since it can be any positive value, and is likely vender dependent. Oracle seems to
+ // Can't use thread ID since it can be any positive value, and is likely vendor dependent. Oracle seems to
// use 1.
// We are left to look for "main" at the top of the stack
if (stackTraceElements != null) {
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/MainLookupTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/MainLookupTest.java
new file mode 100644
index 0000000..b019c2a
--- /dev/null
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/MainLookupTest.java
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.logging.log4j.core.lookup;
+
+import org.junit.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Tests MainLookup.
+ */
+public class MainLookupTest {
+
+ @Test
+ public void testMainArgs(){
+ MainMapLookup.setMainArguments("--file", "foo.txt", "--verbose", "-x", "bar");
+ String str ="${key} ${main:-1} ${main:0} ${main:1} ${main:2} ${main:3} ${main:4} ${main:\\--file} ${main:foo.txt} ${main:\\--verbose} ${main:\\-x} ${main:bar} ${main:\\--quiet:-true}";
+ Map<String, String> properties = new HashMap<String, String>();
+ properties.put("key", "value");
+ properties.put("bar", "default_bar_value");
+ Interpolator lookup = new Interpolator(properties);
+ StrSubstitutor substitutor = new StrSubstitutor(lookup);
+ String replacedValue = substitutor.replace(null, str);
+ String[] values = replacedValue.split(" ");
+ assertEquals("Item 0 is incorrect ", "value", values[0]);
+ assertEquals("Item 1 is incorrect ", "1", values[1]);
+ assertEquals("Item 2 is incorrect", "--file", values[2]);
+ assertEquals("Item 3 is incorrect", "foo.txt", values[3]);
+ assertEquals("Item 4 is incorrect", "--verbose", values[4]);
+ assertEquals("Item 5 is incorrect", "-x", values[5]);
+ assertEquals("Iten 6 is incorrect", "bar", values[6]);
+ assertEquals("Item 7 is incorrect", "foo.txt", values[7]);
+ assertEquals("Item 8 is incorrect", "--verbose", values[8]);
+ assertEquals("Item 9 is incorrect", "-x", values[9]);
+ assertEquals("Item 10 is incorrect", "bar", values[10]);
+ assertEquals("Item 11 is incorrect", "default_bar_value", values[11]);
+ assertEquals("Item 12 is incorrect", "true", values[12]);
+ }
+}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/MapLookupTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/MapLookupTest.java
index 61d81ec..7c09fbf 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/MapLookupTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/MapLookupTest.java
@@ -22,6 +22,7 @@
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.Test;
@@ -55,7 +56,7 @@
@Test
public void testMainMap() {
- MapLookup.setMainArguments(new String[] {
+ MainMapLookup.setMainArguments(new String[] {
"--file",
"foo.txt" });
final MapLookup lookup = MainMapLookup.MAIN_SINGLETON;
@@ -68,7 +69,7 @@
}
@Test
- public void testEventMapMessage() {
+ public void testEventStringMapMessage() {
final HashMap<String, String> map = new HashMap<>();
map.put("A", "B");
final HashMap<String, String> eventMap = new HashMap<>();
@@ -83,6 +84,35 @@
}
@Test
+ public void testEventMapMessage() {
+ final HashMap<String, String> map = new HashMap<>();
+ map.put("A", "B");
+ final HashMap<String, Object> eventMap = new HashMap<>();
+ eventMap.put("A1", 11);
+ final MapMessage message = new MapMessage<>(eventMap);
+ final LogEvent event = Log4jLogEvent.newBuilder()
+ .setMessage(message)
+ .build();
+ final MapLookup lookup = new MapLookup(map);
+ assertEquals("B", lookup.lookup(event, "A"));
+ assertEquals("11", lookup.lookup(event, "A1"));
+ }
+
+ @Test
+ public void testLookupDefaultMapIsCheckedBeforeMapMessage() {
+ final HashMap<String, String> map = new HashMap<>();
+ map.put("A", "ADefault");
+ final HashMap<String, Object> 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("ADefault", lookup.lookup(event, "A"));
+ }
+
+ @Test
public void testNullEvent() {
final HashMap<String, String> map = new HashMap<>();
map.put("A", "B");
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/MarkerLookupTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/MarkerLookupTest.java
index d26f895..4bad20e 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/MarkerLookupTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/lookup/MarkerLookupTest.java
@@ -93,4 +93,4 @@
assertNull(value);
}
-}
\ No newline at end of file
+}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/message/ExtendedThreadInformationTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/message/ExtendedThreadInformationTest.java
index 8333f19..34efb4b 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/message/ExtendedThreadInformationTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/message/ExtendedThreadInformationTest.java
@@ -33,4 +33,4 @@
//System.out.print(message);
assertTrue("No header", message.contains(" Id="));
}
-}
\ No newline at end of file
+}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/net/SocketReconnectTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/net/SocketReconnectTest.java
index 151d444..72b7c25 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/net/SocketReconnectTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/net/SocketReconnectTest.java
@@ -16,71 +16,123 @@
*/
package org.apache.logging.log4j.core.net;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.core.LoggerContext;
+import org.apache.logging.log4j.core.appender.AppenderLoggingException;
+import org.apache.logging.log4j.core.config.Configurator;
+import org.apache.logging.log4j.test.AvailablePortFinder;
+import org.apache.logging.log4j.util.Strings;
+import org.junit.After;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
import java.io.BufferedReader;
+import java.io.IOException;
import java.io.InputStreamReader;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
+import java.net.SocketException;
+import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
-import org.apache.logging.log4j.Logger;
-import org.apache.logging.log4j.core.appender.AppenderLoggingException;
-import org.apache.logging.log4j.junit.LoggerContextRule;
-import org.apache.logging.log4j.test.AvailablePortFinder;
-import org.apache.logging.log4j.util.Strings;
-import org.junit.ClassRule;
-import org.junit.Ignore;
-import org.junit.Test;
-
import static org.junit.Assert.*;
-@Ignore("Currently needs better port choosing support")
+//@Ignore("Currently needs better port choosing support")
public class SocketReconnectTest {
- private static final int SOCKET_PORT = AvailablePortFinder.getNextAvailable();
+ private static final int SOCKET_PORT1 = AvailablePortFinder.getNextAvailable();
+ private static final int SOCKET_PORT2 = AvailablePortFinder.getNextAvailable();
private static final String CONFIG = "log4j-socket.xml";
private static final String SHUTDOWN = "Shutdown" + Strings.LINE_SEPARATOR +
- "................................................................" + Strings.LINE_SEPARATOR +
- "................................................................" + Strings.LINE_SEPARATOR +
- "................................................................" + Strings.LINE_SEPARATOR +
- "................................................................" + Strings.LINE_SEPARATOR;
+ "................................................................" + Strings.LINE_SEPARATOR +
+ "................................................................" + Strings.LINE_SEPARATOR +
+ "................................................................" + Strings.LINE_SEPARATOR +
+ "................................................................" + Strings.LINE_SEPARATOR;
- @ClassRule
- public static LoggerContextRule context = new LoggerContextRule(CONFIG);
+ public static LocalHostResolver resolver = new LocalHostResolver();
+
+ private static LoggerContext loggerContext;
+
+ private static final List<String> list = new ArrayList<>();
+ private static int[] ports;
+ private static TestSocketServer server1;
+ private static TestSocketServer server2;
+ private static Logger logger;
+
+
+ @BeforeClass
+ public static void beforeClass() throws IOException, InterruptedException {
+ server1 = new TestSocketServer(0);
+ server2 = new TestSocketServer(0);
+ server1.start();
+ server2.start();
+ Thread.sleep(100);
+ ports = new int[] { server1.getPort(), server2.getPort()};
+ resolver.ports = ports;
+ TcpSocketManager.setHostResolver(resolver);
+ loggerContext = Configurator.initialize("SocketReconnectTest", SocketReconnectTest.class.getClassLoader(),
+ CONFIG);
+ logger = LogManager.getLogger(SocketReconnectTest.class);
+ server1.shutdown();
+ server1.join();
+ server2.shutdown();
+ server2.join();
+ server1 = null;
+ server2 = null;
+ Thread.sleep(100);
+ list.clear();
+ }
+
+ @AfterClass
+ public static void afterClass() {
+ Configurator.shutdown(loggerContext);
+ }
+
+ @After
+ public void after() throws InterruptedException {
+ if (server1 != null) {
+ server1.shutdown();
+ server1.join();
+ }
+ if (server2 != null) {
+ server2.shutdown();
+ server2.join();
+ }
+ server1 = null;
+ server2 = null;
+ Thread.sleep(300);
+ }
@Test
public void testReconnect() throws Exception {
-
- final List<String> list = new ArrayList<>();
- TestSocketServer server = new TestSocketServer(list);
- server.start();
- Thread.sleep(300);
-
- //System.err.println("Initializing logger");
- final Logger logger = context.getLogger();
-
+ list.clear();
+ resolver.ports = new int[] {ports[0]};
+ server1 = new TestSocketServer(ports[0]);
+ server1.start();
+ Thread.sleep(200);
String message = "Log #1";
- logger.error(message);
- final String expectedHeader = "Header";
-
String msg = null;
- String header = null;
for (int i = 0; i < 5; ++i) {
+ logger.error(message);
Thread.sleep(100);
- if (list.size() > 1) {
- header = list.get(0);
- msg = list.get(1);
- break;
+ if (list.size() > 0) {
+ msg = list.get(0);
+ if (msg != null) {
+ break;
+ }
}
}
- assertNotNull("No header", header);
- assertEquals(expectedHeader, header);
assertNotNull("No message", msg);
assertEquals(message, msg);
logger.error(SHUTDOWN);
- server.join();
+ server1.join();
list.clear();
@@ -100,73 +152,172 @@
message = "Log #3";
- server = new TestSocketServer(list);
- server.start();
+ server1 = new TestSocketServer(ports[0]);
+ server1.start();
Thread.sleep(300);
msg = null;
- header = null;
- logger.error(message);
for (int i = 0; i < 5; ++i) {
+ logger.error(message);
Thread.sleep(100);
- if (list.size() > 1) {
- header = list.get(0);
- msg = list.get(1);
- break;
+ if (list.size() > 0) {
+ msg = list.get(0);
+ if (msg != null) {
+ break;
+ }
}
}
- assertNotNull("No header", header);
- assertEquals(expectedHeader, header);
assertNotNull("No message", msg);
assertEquals(message, msg);
logger.error(SHUTDOWN);
- server.join();
+ server1.join();
+ }
+
+ @Test
+ public void testFailover() throws Exception {
+ list.clear();
+ server1 = new TestSocketServer(ports[0]);
+ server2 = new TestSocketServer(ports[1]);
+ resolver.ports = ports;
+ server1.start();
+ server2.start();
+ Thread.sleep(100);
+
+ String message = "Log #1";
+
+ String msg = null;
+ for (int i = 0; i < 5; ++i) {
+ logger.error(message);
+ Thread.sleep(100);
+ if (list.size() > 0) {
+ msg = list.get(0);
+ if (msg != null) {
+ break;
+ }
+ }
+ }
+ assertNotNull("No message", msg);
+ assertEquals(message, msg);
+
+ server1.shutdown();
+ server1.join();
+
+ list.clear();
+
+ message = "Log #2";
+ for (int i = 0; i < 5; ++i) {
+ logger.error(message);
+ Thread.sleep(100);
+ if (list.size() > 0) {
+ msg = list.get(0);
+ if (msg != null) {
+ break;
+ }
+ }
+ }
+ assertNotNull("No message", msg);
+ assertEquals(message, msg);
+
+ server2.shutdown();
+ server2.join();
}
private static class TestSocketServer extends Thread {
private volatile boolean shutdown = false;
- private final List<String> list;
- private Socket client;
+ private volatile boolean started = false;
+ private volatile Socket client;
+ private final int port;
+ private ServerSocket server;
- public TestSocketServer(final List<String> list) {
- this.list = list;
+ public TestSocketServer(int port) throws IOException {
+ this.port = port;
+ server = new ServerSocket(port);
+ }
+
+ public int getPort() {
+ return port == 0 ? server.getLocalPort() : port;
+ }
+
+ public void shutdown() {
+ if (!shutdown) {
+ shutdown = true;
+ if (server != null && server.isBound()) {
+ try {
+ if (client != null) {
+ Socket serverClient = client;
+ client = null;
+ serverClient.shutdownInput();
+ serverClient.shutdownOutput();
+ serverClient.setSoLinger(true, 0);
+ serverClient.close();
+ }
+ ServerSocket serverSocket = server;
+ server = null;
+ serverSocket.close();
+ } catch (Exception ex) {
+ System.out.println("Unable to send shutdown message");
+ ex.printStackTrace();
+ }
+ }
+ return;
+ }
}
@Override
public void run() {
- ServerSocket server = null;
client = null;
try {
- server = new ServerSocket(SOCKET_PORT);
client = server.accept();
+ started = true;
while (!shutdown) {
final BufferedReader reader = new BufferedReader(new InputStreamReader(client.getInputStream()));
final String line = reader.readLine();
- if (line.equals("Shutdown")) {
+ if (line == null || line.equals("Shutdown")) {
shutdown = true;
} else {
list.add(line);
}
}
+ } catch (final SocketException ex) {
+ if (!shutdown) {
+ ex.printStackTrace();
+ }
} catch (final Exception ex) {
ex.printStackTrace();
} finally {
- if (client != null) {
+ if (client != null && !client.isClosed()) {
try {
+ client.setSoLinger(true, 0);
+ client.shutdownOutput();
client.close();
} catch (final Exception ex) {
- System.out.println("Unable to close socket " + ex.getMessage());
+ System.out.println("Unable to close socket: " + ex.getMessage());
}
}
- if (server != null) {
+ if (server != null && !server.isClosed()) {
try {
server.close();
} catch (final Exception ex) {
- System.out.println("Unable to close server socket " + ex.getMessage());
+ System.out.println("Unable to close server socket: " + ex.getMessage());
}
}
}
}
}
+
+ private static class LocalHostResolver extends TcpSocketManager.HostResolver {
+ public volatile int[] ports;
+
+ @Override
+ public List<InetSocketAddress> resolveHost(String host, int port) throws UnknownHostException {
+ int[] socketPorts = ports;
+ List<InetSocketAddress> socketAddresses = new ArrayList<>(ports.length);
+ InetAddress addr = InetAddress.getLocalHost();
+ for (int i = 0; i < socketPorts.length; ++i){
+ socketAddresses.add(new InetSocketAddress(addr, socketPorts[i]));
+ }
+ return socketAddresses;
+ }
+ }
}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/net/mock/MockTlsSyslogServer.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/net/mock/MockTlsSyslogServer.java
index a445464..6c028db 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/net/mock/MockTlsSyslogServer.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/net/mock/MockTlsSyslogServer.java
@@ -151,4 +151,4 @@
public List<String> getMessageList() {
return messageList;
}
-}
\ No newline at end of file
+}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/net/mock/MockUdpSyslogServer.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/net/mock/MockUdpSyslogServer.java
index a74940b..b9ef199 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/net/mock/MockUdpSyslogServer.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/net/mock/MockUdpSyslogServer.java
@@ -67,4 +67,4 @@
}
System.out.println("Log4j UDP server stopped.");
}
-}
\ No newline at end of file
+}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/net/ssl/EnvironmentPasswordProviderTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/net/ssl/EnvironmentPasswordProviderTest.java
index a9b266f..56676ec 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/net/ssl/EnvironmentPasswordProviderTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/net/ssl/EnvironmentPasswordProviderTest.java
@@ -35,4 +35,4 @@
final char[] actual = new EnvironmentPasswordProvider("PATH").getPassword();
assertArrayEquals(value.toCharArray(), actual);
}
-}
\ No newline at end of file
+}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/net/ssl/TlsSyslogInputStreamReaderBase.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/net/ssl/TlsSyslogInputStreamReaderBase.java
index 5db7224..28699b1 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/net/ssl/TlsSyslogInputStreamReaderBase.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/net/ssl/TlsSyslogInputStreamReaderBase.java
@@ -32,4 +32,4 @@
public String read() throws IOException {
throw new UnsupportedOperationException();
}
-}
\ No newline at end of file
+}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/DatePatternConverterTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/DatePatternConverterTest.java
index ae53cc2..360db1b 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/DatePatternConverterTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/DatePatternConverterTest.java
@@ -16,6 +16,9 @@
*/
package org.apache.logging.log4j.core.pattern;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.text.SimpleDateFormat;
@@ -27,19 +30,38 @@
import org.apache.logging.log4j.core.AbstractLogEvent;
import org.apache.logging.log4j.core.LogEvent;
-import org.apache.logging.log4j.core.util.Constants;
import org.apache.logging.log4j.core.time.Instant;
import org.apache.logging.log4j.core.time.MutableInstant;
import org.apache.logging.log4j.core.time.internal.format.FixedDateFormat;
+import org.apache.logging.log4j.core.time.internal.format.FixedDateFormat.FixedTimeZoneFormat;
+import org.apache.logging.log4j.core.util.Constants;
+import org.apache.logging.log4j.util.Strings;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
-import static org.junit.Assert.*;
-
@RunWith(Parameterized.class)
public class DatePatternConverterTest {
+ private class MyLogEvent extends AbstractLogEvent {
+ private static final long serialVersionUID = 0;
+
+ @Override
+ public Instant getInstant() {
+ MutableInstant result = new MutableInstant();
+ result.initFromEpochMilli(getTimeMillis(), 123456);
+ return result;
+ }
+
+ @Override
+ public long getTimeMillis() {
+ final Calendar cal = Calendar.getInstance();
+ cal.set(2011, Calendar.DECEMBER, 30, 10, 56, 35);
+ cal.set(Calendar.MILLISECOND, 987);
+ return cal.getTimeInMillis();
+ }
+ }
+
/**
* SimpleTimePattern for DEFAULT.
*/
@@ -48,11 +70,23 @@
/**
* ISO8601 string literal.
*/
- private static final String ISO8601_FORMAT = FixedDateFormat.FixedFormat.ISO8601.name();
+ private static final String ISO8601 = FixedDateFormat.FixedFormat.ISO8601.name();
- private static final String[] ISO8601_FORMAT_OPTIONS = {ISO8601_FORMAT};
+ /**
+ * ISO8601_OFFSE_DATE_TIME_XX string literal.
+ */
+ private static final String ISO8601_OFFSE_DATE_TIME_HHMM = FixedDateFormat.FixedFormat.ISO8601_OFFSET_DATE_TIME_HHMM
+ .name();
- @Parameterized.Parameters
+ /**
+ * ISO8601_OFFSET_DATE_TIME_XXX string literal.
+ */
+ private static final String ISO8601_OFFSET_DATE_TIME_HHCMM = FixedDateFormat.FixedFormat.ISO8601_OFFSET_DATE_TIME_HHCMM
+ .name();
+
+ private static final String[] ISO8601_FORMAT_OPTIONS = { ISO8601 };
+
+ @Parameterized.Parameters(name = "threadLocalEnabled={0}")
public static Collection<Object[]> data() {
return Arrays.asList(new Object[][]{{Boolean.TRUE}, {Boolean.FALSE}});
}
@@ -71,177 +105,19 @@
field.setBoolean(null, threadLocalEnabled.booleanValue());
}
- @Test
- public void testNewInstanceAllowsNullParameter() {
- DatePatternConverter.newInstance(null); // no errors
- }
-
- @Test
- public void testFormatLogEventStringBuilderDefaultPattern() {
- final LogEvent event = new MyLogEvent();
- final DatePatternConverter converter = DatePatternConverter.newInstance(null);
- final StringBuilder sb = new StringBuilder();
- converter.format(event, sb);
-
- final String expected = "2011-12-30 10:56:35,987";
- assertEquals(expected, sb.toString());
- }
-
- @Test
- public void testFormatLogEventStringBuilderIso8601() {
- final LogEvent event = new MyLogEvent();
- final DatePatternConverter converter = DatePatternConverter.newInstance(ISO8601_FORMAT_OPTIONS);
- final StringBuilder sb = new StringBuilder();
- converter.format(event, sb);
-
- final String expected = "2011-12-30T10:56:35,987";
- assertEquals(expected, sb.toString());
- }
-
- @Test
- public void testFormatLogEventStringBuilderIso8601TimezoneUTC() {
- final LogEvent event = new MyLogEvent();
- final DatePatternConverter converter = DatePatternConverter.newInstance(new String[] {"ISO8601", "UTC"});
- final StringBuilder sb = new StringBuilder();
- converter.format(event, sb);
-
- final TimeZone tz = TimeZone.getTimeZone("UTC");
- final SimpleDateFormat sdf = new SimpleDateFormat(converter.getPattern());
- sdf.setTimeZone(tz);
- final long adjusted = event.getTimeMillis() + tz.getDSTSavings();
- final String expected = sdf.format(new Date(adjusted));
- // final String expected = "2011-12-30T09:56:35,987";
- assertEquals(expected, sb.toString());
- }
-
- @Test
- public void testFormatLogEventStringBuilderIso8601TimezoneJST() {
- final LogEvent event = new MyLogEvent();
- final String[] optionsWithTimezone = {ISO8601_FORMAT, "JST"};
- final DatePatternConverter converter = DatePatternConverter.newInstance(optionsWithTimezone);
- final StringBuilder sb = new StringBuilder();
- converter.format(event, sb);
-
- // JST=Japan Standard Time: UTC+9:00
- final TimeZone tz = TimeZone.getTimeZone("JST");
- final SimpleDateFormat sdf = new SimpleDateFormat(converter.getPattern());
- sdf.setTimeZone(tz);
- final long adjusted = event.getTimeMillis() + tz.getDSTSavings();
- final String expected = sdf.format(new Date(adjusted));
- // final String expected = "2011-12-30T18:56:35,987"; // in CET (Central Eastern Time: Amsterdam)
- assertEquals(expected, sb.toString());
- }
-
- @Test
- public void testPredefinedFormatWithTimezone() {
- for (final FixedDateFormat.FixedFormat format : FixedDateFormat.FixedFormat.values()) {
- final String[] options = {format.name(), "PDT"}; // Pacific Daylight Time=UTC-8:00
- final DatePatternConverter converter = DatePatternConverter.newInstance(options);
- assertEquals(format.getPattern(), converter.getPattern());
- }
- }
-
- @Test
- public void testPredefinedFormatWithoutTimezone() {
- for (final FixedDateFormat.FixedFormat format : FixedDateFormat.FixedFormat.values()) {
- final String[] options = {format.name()};
- final DatePatternConverter converter = DatePatternConverter.newInstance(options);
- assertEquals(format.getPattern(), converter.getPattern());
- }
+ private Date date(final int year, final int month, final int date) {
+ final Calendar cal = Calendar.getInstance();
+ cal.set(year, month, date, 14, 15, 16);
+ cal.set(Calendar.MILLISECOND, 123);
+ return cal.getTime();
}
private String precisePattern(final String pattern, int precision) {
- String seconds = pattern.substring(0, pattern.indexOf("SSS"));
- return seconds + "nnnnnnnnn".substring(0, precision);
- }
-
- // test with all formats from one 'n' (100s of millis) to 'nnnnnnnnn' (nanosecond precision)
- @Test
- public void testPredefinedFormatWithAnyValidNanoPrecision() {
- final StringBuilder precise = new StringBuilder();
- final StringBuilder milli = new StringBuilder();
- final LogEvent event = new MyLogEvent();
-
- for (String timeZone : new String[]{"PDT", null}) { // Pacific Daylight Time=UTC-8:00
- for (final FixedDateFormat.FixedFormat format : FixedDateFormat.FixedFormat.values()) {
- for (int i = 1; i <= 9; i++) {
- if (format.getPattern().endsWith("n")) {
- continue; // ignore patterns that already have precise time formats
- }
- precise.setLength(0);
- milli.setLength(0);
-
- final String[] preciseOptions = {precisePattern(format.getPattern(), i), timeZone};
- final DatePatternConverter preciseConverter = DatePatternConverter.newInstance(preciseOptions);
- preciseConverter.format(event, precise);
-
- final String[] milliOptions = {format.getPattern(), timeZone};
- DatePatternConverter.newInstance(milliOptions).format(event, milli);
- milli.setLength(milli.length() - 3); // truncate millis
- String expected = milli.append("987123456".substring(0, i)).toString();
-
- assertEquals(expected, precise.toString());
- //System.out.println(preciseOptions[0] + ": " + precise);
- }
- }
- }
- }
-
- @Test
- public void testInvalidLongPatternIgnoresExcessiveDigits() {
- final StringBuilder precise = new StringBuilder();
- final StringBuilder milli = new StringBuilder();
- final LogEvent event = new MyLogEvent();
-
- for (final FixedDateFormat.FixedFormat format : FixedDateFormat.FixedFormat.values()) {
- if (format.getPattern().endsWith("n")) {
- continue; // ignore patterns that already have precise time formats
- }
- precise.setLength(0);
- milli.setLength(0);
-
- final String pattern = format.getPattern().substring(0, format.getPattern().indexOf("SSS"));
- final String[] preciseOptions = {pattern + "nnnnnnnnn" + "n"}; // too long
- final DatePatternConverter preciseConverter = DatePatternConverter.newInstance(preciseOptions);
- preciseConverter.format(event, precise);
-
- final String[] milliOptions = {format.getPattern()};
- DatePatternConverter.newInstance(milliOptions).format(event, milli);
- milli.setLength(milli.length() - 3); // truncate millis
- String expected = milli.append("987123456").toString();
-
- assertEquals(expected, precise.toString());
- //System.out.println(preciseOptions[0] + ": " + precise);
- }
- }
-
- private class MyLogEvent extends AbstractLogEvent {
- private static final long serialVersionUID = 0;
-
- @Override
- public long getTimeMillis() {
- final Calendar cal = Calendar.getInstance();
- cal.set(2011, Calendar.DECEMBER, 30, 10, 56, 35);
- cal.set(Calendar.MILLISECOND, 987);
- return cal.getTimeInMillis();
- }
-
- @Override
- public Instant getInstant() {
- MutableInstant result = new MutableInstant();
- result.initFromEpochMilli(getTimeMillis(), 123456);
- return result;
- }
- }
-
- @Test
- public void testFormatObjectStringBuilderDefaultPattern() {
- final DatePatternConverter converter = DatePatternConverter.newInstance(null);
- final StringBuilder sb = new StringBuilder();
- converter.format("nondate", sb);
-
- final String expected = ""; // only process dates
- assertEquals(expected, sb.toString());
+ String search = "SSS";
+ int foundIndex = pattern.indexOf(search);
+ final String seconds = pattern.substring(0, foundIndex);
+ final String remainder = pattern.substring(foundIndex + search.length());
+ return seconds + "nnnnnnnnn".substring(0, precision) + remainder;
}
@Test
@@ -265,17 +141,6 @@
}
@Test
- public void testFormatDateStringBuilderIso8601WithPeriod() {
- final String[] pattern = {FixedDateFormat.FixedFormat.ISO8601_PERIOD.name()};
- final DatePatternConverter converter = DatePatternConverter.newInstance(pattern);
- final StringBuilder sb = new StringBuilder();
- converter.format(date(2001, 1, 1), sb);
-
- final String expected = "2001-02-01T14:15:16.123";
- assertEquals(expected, sb.toString());
- }
-
- @Test
public void testFormatDateStringBuilderIso8601BasicWithPeriod() {
final String[] pattern = {FixedDateFormat.FixedFormat.ISO8601_BASIC_PERIOD.name()};
final DatePatternConverter converter = DatePatternConverter.newInstance(pattern);
@@ -287,6 +152,33 @@
}
@Test
+ public void testFormatDateStringBuilderIso8601WithPeriod() {
+ final String[] pattern = {FixedDateFormat.FixedFormat.ISO8601_PERIOD.name()};
+ final DatePatternConverter converter = DatePatternConverter.newInstance(pattern);
+ final StringBuilder sb = new StringBuilder();
+ converter.format(date(2001, 1, 1), sb);
+
+ final String expected = "2001-02-01T14:15:16.123";
+ assertEquals(expected, sb.toString());
+ }
+
+ @Test
+ public void testFormatDateStringBuilderIso8601WithPeriodMicroseconds() {
+ final String[] pattern = {FixedDateFormat.FixedFormat.ISO8601_PERIOD_MICROS.name(), "Z"};
+ final DatePatternConverter converter = DatePatternConverter.newInstance(pattern);
+ final StringBuilder sb = new StringBuilder();
+ MutableInstant instant = new MutableInstant();
+ instant.initFromEpochMilli(
+ 1577225134559L,
+ // One microsecond
+ 1000);
+ converter.format(instant, sb);
+
+ final String expected = "2019-12-24T22:05:34.559001";
+ assertEquals(expected, sb.toString());
+ }
+
+ @Test
public void testFormatDateStringBuilderOriginalPattern() {
final String[] pattern = {"yyyy/MM/dd HH-mm-ss.SSS"};
final DatePatternConverter converter = DatePatternConverter.newInstance(pattern);
@@ -298,6 +190,117 @@
}
@Test
+ public void testFormatLogEventStringBuilderDefaultPattern() {
+ final LogEvent event = new MyLogEvent();
+ final DatePatternConverter converter = DatePatternConverter.newInstance(null);
+ final StringBuilder sb = new StringBuilder();
+ converter.format(event, sb);
+
+ final String expected = "2011-12-30 10:56:35,987";
+ assertEquals(expected, sb.toString());
+ }
+
+ @Test
+ public void testFormatLogEventStringBuilderIso8601() {
+ final LogEvent event = new MyLogEvent();
+ final DatePatternConverter converter = DatePatternConverter.newInstance(ISO8601_FORMAT_OPTIONS);
+ final StringBuilder sb = new StringBuilder();
+ converter.format(event, sb);
+
+ final String expected = "2011-12-30T10:56:35,987";
+ assertEquals(expected, sb.toString());
+ }
+
+ @Test
+ public void testFormatLogEventStringBuilderIso8601TimezoneJST() {
+ final LogEvent event = new MyLogEvent();
+ final String[] optionsWithTimezone = {ISO8601, "JST"};
+ final DatePatternConverter converter = DatePatternConverter.newInstance(optionsWithTimezone);
+ final StringBuilder sb = new StringBuilder();
+ converter.format(event, sb);
+
+ // JST=Japan Standard Time: UTC+9:00
+ final TimeZone tz = TimeZone.getTimeZone("JST");
+ final SimpleDateFormat sdf = new SimpleDateFormat(converter.getPattern());
+ sdf.setTimeZone(tz);
+ final long adjusted = event.getTimeMillis() + tz.getDSTSavings();
+ final String expected = sdf.format(new Date(adjusted));
+ // final String expected = "2011-12-30T18:56:35,987"; // in CET (Central Eastern Time: Amsterdam)
+ assertEquals(expected, sb.toString());
+ }
+
+ @Test
+ public void testFormatLogEventStringBuilderIso8601TimezoneOffsetHHCMM() {
+ final LogEvent event = new MyLogEvent();
+ final String[] optionsWithTimezone = { ISO8601_OFFSET_DATE_TIME_HHCMM };
+ final DatePatternConverter converter = DatePatternConverter.newInstance(optionsWithTimezone);
+ final StringBuilder sb = new StringBuilder();
+ converter.format(event, sb);
+
+ final SimpleDateFormat sdf = new SimpleDateFormat(converter.getPattern());
+ final String format = sdf.format(new Date(event.getTimeMillis()));
+ final String expected = format.endsWith("Z") ? format.substring(0, format.length() - 1) + "+00:00" : format;
+ assertEquals(expected, sb.toString());
+ }
+
+ @Test
+ public void testFormatLogEventStringBuilderIso8601TimezoneOffsetHHMM() {
+ final LogEvent event = new MyLogEvent();
+ final String[] optionsWithTimezone = { ISO8601_OFFSE_DATE_TIME_HHMM };
+ final DatePatternConverter converter = DatePatternConverter.newInstance(optionsWithTimezone);
+ final StringBuilder sb = new StringBuilder();
+ converter.format(event, sb);
+
+ final SimpleDateFormat sdf = new SimpleDateFormat(converter.getPattern());
+ final String format = sdf.format(new Date(event.getTimeMillis()));
+ final String expected = format.endsWith("Z") ? format.substring(0, format.length() - 1) + "+0000" : format;
+ assertEquals(expected, sb.toString());
+ }
+
+ @Test
+ public void testFormatLogEventStringBuilderIso8601TimezoneUTC() {
+ final LogEvent event = new MyLogEvent();
+ final DatePatternConverter converter = DatePatternConverter.newInstance(new String[] {"ISO8601", "UTC"});
+ final StringBuilder sb = new StringBuilder();
+ converter.format(event, sb);
+
+ final TimeZone tz = TimeZone.getTimeZone("UTC");
+ final SimpleDateFormat sdf = new SimpleDateFormat(converter.getPattern());
+ sdf.setTimeZone(tz);
+ final long adjusted = event.getTimeMillis() + tz.getDSTSavings();
+ final String expected = sdf.format(new Date(adjusted));
+ // final String expected = "2011-12-30T09:56:35,987";
+ assertEquals(expected, sb.toString());
+ }
+
+ @Test
+ public void testFormatLogEventStringBuilderIso8601TimezoneZ() {
+ final LogEvent event = new MyLogEvent();
+ final String[] optionsWithTimezone = { ISO8601, "Z" };
+ final DatePatternConverter converter = DatePatternConverter.newInstance(optionsWithTimezone);
+ final StringBuilder sb = new StringBuilder();
+ converter.format(event, sb);
+
+ final TimeZone tz = TimeZone.getTimeZone("UTC");
+ final SimpleDateFormat sdf = new SimpleDateFormat(converter.getPattern());
+ sdf.setTimeZone(tz);
+ final long adjusted = event.getTimeMillis() + tz.getDSTSavings();
+ final String expected = sdf.format(new Date(adjusted));
+ // final String expected = "2011-12-30T17:56:35,987"; // in UTC
+ assertEquals(expected, sb.toString());
+ }
+
+ @Test
+ public void testFormatObjectStringBuilderDefaultPattern() {
+ final DatePatternConverter converter = DatePatternConverter.newInstance(null);
+ final StringBuilder sb = new StringBuilder();
+ converter.format("nondate", sb);
+
+ final String expected = ""; // only process dates
+ assertEquals(expected, sb.toString());
+ }
+
+ @Test
public void testFormatStringBuilderObjectArrayDefaultPattern() {
final DatePatternConverter converter = DatePatternConverter.newInstance(null);
final StringBuilder sb = new StringBuilder();
@@ -317,11 +320,15 @@
assertEquals(expected, sb.toString());
}
- private Date date(final int year, final int month, final int date) {
- final Calendar cal = Calendar.getInstance();
- cal.set(year, month, date, 14, 15, 16);
- cal.set(Calendar.MILLISECOND, 123);
- return cal.getTime();
+ @Test
+ public void testGetPatternReturnsDefaultForEmptyOptionsArray() {
+ assertEquals(DEFAULT_PATTERN, DatePatternConverter.newInstance(new String[0]).getPattern());
+ }
+
+ @Test
+ public void testGetPatternReturnsDefaultForInvalidPattern() {
+ final String[] invalid = {"ABC I am not a valid date pattern"};
+ assertEquals(DEFAULT_PATTERN, DatePatternConverter.newInstance(invalid).getPattern());
}
@Test
@@ -330,11 +337,6 @@
}
@Test
- public void testGetPatternReturnsDefaultForEmptyOptionsArray() {
- assertEquals(DEFAULT_PATTERN, DatePatternConverter.newInstance(new String[0]).getPattern());
- }
-
- @Test
public void testGetPatternReturnsDefaultForSingleNullElementOptionsArray() {
assertEquals(DEFAULT_PATTERN, DatePatternConverter.newInstance(new String[1]).getPattern());
}
@@ -345,12 +347,6 @@
}
@Test
- public void testGetPatternReturnsDefaultForInvalidPattern() {
- final String[] invalid = {"ABC I am not a valid date pattern"};
- assertEquals(DEFAULT_PATTERN, DatePatternConverter.newInstance(invalid).getPattern());
- }
-
- @Test
public void testGetPatternReturnsNullForUnix() {
final String[] options = {"UNIX"};
assertNull(DatePatternConverter.newInstance(options).getPattern());
@@ -362,4 +358,122 @@
assertNull(DatePatternConverter.newInstance(options).getPattern());
}
+ @Test
+ public void testInvalidLongPatternIgnoresExcessiveDigits() {
+ final StringBuilder preciseBuilder = new StringBuilder();
+ final StringBuilder milliBuilder = new StringBuilder();
+ final LogEvent event = new MyLogEvent();
+
+ for (final FixedDateFormat.FixedFormat format : FixedDateFormat.FixedFormat.values()) {
+ String pattern = format.getPattern();
+ final String search = "SSS";
+ final int foundIndex = pattern.indexOf(search);
+ if (pattern.endsWith("n") || pattern.matches(".+n+X*") || pattern.matches(".+n+Z*")) {
+ // ignore patterns that already have precise time formats
+ // ignore patterns that do not use seconds.
+ continue;
+ }
+ preciseBuilder.setLength(0);
+ milliBuilder.setLength(0);
+
+ final DatePatternConverter preciseConverter;
+ final String precisePattern;
+ if (foundIndex < 0) {
+ precisePattern = pattern;
+ preciseConverter = DatePatternConverter.newInstance(new String[] { precisePattern });
+ } else {
+ final String subPattern = pattern.substring(0, foundIndex);
+ final String remainder = pattern.substring(foundIndex + search.length());
+ precisePattern = subPattern + "nnnnnnnnn" + "n" + remainder; // nanos too long
+ preciseConverter = DatePatternConverter.newInstance(new String[] { precisePattern });
+ }
+ preciseConverter.format(event, preciseBuilder);
+
+ final String[] milliOptions = { pattern };
+ DatePatternConverter.newInstance(milliOptions).format(event, milliBuilder);
+ FixedTimeZoneFormat timeZoneFormat = format.getTimeZoneFormat();
+ final int truncateLen = 3 + (timeZoneFormat != null ? timeZoneFormat.getLength() : 0);
+ final String tz = timeZoneFormat != null
+ ? milliBuilder.substring(milliBuilder.length() - timeZoneFormat.getLength(), milliBuilder.length())
+ : Strings.EMPTY;
+ milliBuilder.setLength(milliBuilder.length() - truncateLen); // truncate millis
+ if (foundIndex >= 0) {
+ milliBuilder.append("987123456");
+ }
+ final String expected = milliBuilder.append(tz).toString();
+
+ assertEquals("format = " + format + ", pattern = " + pattern + ", precisePattern = " + precisePattern,
+ expected, preciseBuilder.toString());
+ // System.out.println(preciseOptions[0] + ": " + precise);
+ }
+ }
+
+ @Test
+ public void testNewInstanceAllowsNullParameter() {
+ DatePatternConverter.newInstance(null); // no errors
+ }
+
+ // test with all formats from one 'n' (100s of millis) to 'nnnnnnnnn' (nanosecond precision)
+ @Test
+ public void testPredefinedFormatWithAnyValidNanoPrecision() {
+ final StringBuilder preciseBuilder = new StringBuilder();
+ final StringBuilder milliBuilder = new StringBuilder();
+ final LogEvent event = new MyLogEvent();
+
+ for (final String timeZone : new String[] { "PDT", null }) { // Pacific Daylight Time=UTC-8:00
+ for (final FixedDateFormat.FixedFormat format : FixedDateFormat.FixedFormat.values()) {
+ for (int i = 1; i <= 9; i++) {
+ final String pattern = format.getPattern();
+ if (pattern.endsWith("n") || pattern.matches(".+n+X*") || pattern.matches(".+n+Z*")
+ || pattern.indexOf("SSS") < 0) {
+ // ignore patterns that already have precise time formats
+ // ignore patterns that do not use seconds.
+ continue;
+ }
+ preciseBuilder.setLength(0);
+ milliBuilder.setLength(0);
+
+ final String precisePattern = precisePattern(pattern, i);
+ final String[] preciseOptions = { precisePattern, timeZone };
+ final DatePatternConverter preciseConverter = DatePatternConverter.newInstance(preciseOptions);
+ preciseConverter.format(event, preciseBuilder);
+
+ final String[] milliOptions = { pattern, timeZone };
+ DatePatternConverter.newInstance(milliOptions).format(event, milliBuilder);
+ FixedTimeZoneFormat timeZoneFormat = format.getTimeZoneFormat();
+ final int truncateLen = 3 + (timeZoneFormat != null ? timeZoneFormat.getLength() : 0);
+ final String tz = timeZoneFormat != null
+ ? milliBuilder.substring(milliBuilder.length() - timeZoneFormat.getLength(),
+ milliBuilder.length())
+ : Strings.EMPTY;
+ milliBuilder.setLength(milliBuilder.length() - truncateLen); // truncate millis
+ final String expected = milliBuilder.append("987123456".substring(0, i)).append(tz).toString();
+
+ assertEquals(
+ "format = " + format + ", pattern = " + pattern + ", precisePattern = " + precisePattern,
+ expected, preciseBuilder.toString());
+ // System.out.println(preciseOptions[0] + ": " + precise);
+ }
+ }
+ }
+ }
+
+ @Test
+ public void testPredefinedFormatWithoutTimezone() {
+ for (final FixedDateFormat.FixedFormat format : FixedDateFormat.FixedFormat.values()) {
+ final String[] options = {format.name()};
+ final DatePatternConverter converter = DatePatternConverter.newInstance(options);
+ assertEquals(format.getPattern(), converter.getPattern());
+ }
+ }
+
+ @Test
+ public void testPredefinedFormatWithTimezone() {
+ for (final FixedDateFormat.FixedFormat format : FixedDateFormat.FixedFormat.values()) {
+ final String[] options = {format.name(), "PDT"}; // Pacific Daylight Time=UTC-8:00
+ final DatePatternConverter converter = DatePatternConverter.newInstance(options);
+ assertEquals(format.getPattern(), converter.getPattern());
+ }
+ }
+
}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/HighlightConverterTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/HighlightConverterTest.java
index c3b1ba6..1ca8bdb 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/HighlightConverterTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/HighlightConverterTest.java
@@ -53,7 +53,7 @@
converter.format(event, buffer);
assertEquals("\u001B[32mINFO : message in a bottle\u001B[m", buffer.toString());
}
-
+
@Test
public void testLevelNamesBad() {
String colorName = "red";
@@ -77,6 +77,28 @@
}
@Test
+ public void testLevelNamesUnknown() {
+ String colorName = "blue";
+ final String[] options = { "%level", PatternParser.NO_CONSOLE_NO_ANSI + "=false, "
+ + PatternParser.DISABLE_ANSI + "=false, " + "DEBUG=" + colorName + ", CUSTOM1=" + colorName };
+ final HighlightConverter converter = HighlightConverter.newInstance(null, options);
+ Assert.assertNotNull(converter);
+ Assert.assertNotNull(converter.getLevelStyle(Level.INFO));
+ Assert.assertNotNull(converter.getLevelStyle(Level.DEBUG));
+ Assert.assertNotNull(converter.getLevelStyle(Level.forName("CUSTOM1", 412)));
+ Assert.assertNull(converter.getLevelStyle(Level.forName("CUSTOM2", 512)));
+
+ assertArrayEquals(new byte[]{27, '[', '3', '4', 'm', 'D', 'E', 'B', 'U', 'G', 27, '[', 'm'},
+ toFormattedCharSeq(converter, Level.DEBUG).toString().getBytes());
+ assertArrayEquals(new byte[]{27, '[', '3', '2', 'm', 'I', 'N', 'F', 'O', 27, '[', 'm'},
+ toFormattedCharSeq(converter, Level.INFO).toString().getBytes());
+ assertArrayEquals(new byte[]{27, '[', '3', '4', 'm', 'C', 'U', 'S', 'T', 'O', 'M', '1', 27, '[', 'm'},
+ toFormattedCharSeq(converter, Level.forName("CUSTOM1", 412)).toString().getBytes());
+ assertArrayEquals(new byte[]{'C', 'U', 'S', 'T', 'O', 'M', '2'},
+ toFormattedCharSeq(converter, Level.forName("CUSTOM2", 512)).toString().getBytes());
+ }
+
+ @Test
public void testLevelNamesNone() {
final String[] options = { "%-5level: %msg",
PatternParser.NO_CONSOLE_NO_ANSI + "=false, " + PatternParser.DISABLE_ANSI + "=false" };
@@ -109,4 +131,13 @@
converter.format(event, buffer);
assertEquals("INFO : message in a bottle", buffer.toString());
}
-}
\ No newline at end of file
+
+
+ private CharSequence toFormattedCharSeq(final HighlightConverter converter, final Level level) {
+ final StringBuilder sb= new StringBuilder();
+ converter.format(Log4jLogEvent.newBuilder()
+ .setLevel(level)
+ .build(), sb);
+ return sb;
+ }
+}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/MapPatternConverterTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/MapPatternConverterTest.java
index 811924a..a841b32 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/MapPatternConverterTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/MapPatternConverterTest.java
@@ -19,6 +19,7 @@
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.Test;
@@ -74,4 +75,30 @@
final String expected = "Log4j";
assertEquals(expected, str);
}
+
+ @Test
+ public void testConverterWithJavaFormat() {
+
+ final StringMapMessage msg = new StringMapMessage();
+ msg.put("subject", "I");
+ msg.put("verb", "love");
+ msg.put("object", "Log4j");
+ final MapPatternConverter converter = MapPatternConverter.newInstance(null, MapMessage.MapFormat.JAVA);
+ final LogEvent event = Log4jLogEvent.newBuilder() //
+ .setLoggerName("MyLogger") //
+ .setLevel(Level.DEBUG) //
+ .setMessage(msg) //
+ .build();
+ final StringBuilder sb = new StringBuilder();
+ converter.format(event, sb);
+ final String str = sb.toString();
+ String expected = "subject=\"I\"";
+ assertTrue("Missing or incorrect subject. Expected " + expected + ", actual " + str, str.contains(expected));
+ expected = "verb=\"love\"";
+ assertTrue("Missing or incorrect verb", str.contains(expected));
+ expected = "object=\"Log4j\"";
+ assertTrue("Missing or incorrect object", str.contains(expected));
+
+ assertEquals("{object=\"Log4j\", subject=\"I\", verb=\"love\"}", str);
+ }
}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/NameAbbreviatorTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/NameAbbreviatorTest.java
index 80af7ad..d49a6f3 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/NameAbbreviatorTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/NameAbbreviatorTest.java
@@ -50,6 +50,7 @@
{ "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" }
}
);
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/PatternParserTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/PatternParserTest.java
index 7fd7254..00f5c0e 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/PatternParserTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/PatternParserTest.java
@@ -33,8 +33,8 @@
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.time.internal.DummyNanoClock;
import org.apache.logging.log4j.core.time.SystemNanoClock;
+import org.apache.logging.log4j.core.time.internal.DummyNanoClock;
import org.apache.logging.log4j.message.SimpleMessage;
import org.apache.logging.log4j.util.StringMap;
import org.apache.logging.log4j.util.Strings;
@@ -406,4 +406,13 @@
assertEquals("|", options.getSeparator());
}
+ // LOG4J2-2564: Multiple newInstance methods.
+ @Test
+ public void testMapPatternConverter() {
+ final List<PatternFormatter> formatters = parser.parse("%K");
+ assertNotNull(formatters);
+ assertTrue(formatters.size() == 1);
+ PatternFormatter formatter = formatters.get(0);
+ assertTrue("Expected a MapPatternConverter", formatter.getConverter() instanceof MapPatternConverter);
+ }
}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/ProcessIdPatternConverterTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/ProcessIdPatternConverterTest.java
index d15221d..05d8db3 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/ProcessIdPatternConverterTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/ProcessIdPatternConverterTest.java
@@ -29,4 +29,4 @@
final String actual = ProcessIdPatternConverter.newInstance(defaultValue).getProcessId();
assertNotEquals("???", actual);
}
-}
\ No newline at end of file
+}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/SequenceNumberPatternConverterZeroPaddedTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/SequenceNumberPatternConverterZeroPaddedTest.java
new file mode 100644
index 0000000..f7f2540
--- /dev/null
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/pattern/SequenceNumberPatternConverterZeroPaddedTest.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.logging.log4j.core.pattern;
+
+import java.util.List;
+
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.junit.LoggerContextRule;
+import org.apache.logging.log4j.test.appender.ListAppender;
+import org.junit.Test;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+import org.junit.Before;
+import org.junit.ClassRule;
+
+/**
+ *
+ */
+public class SequenceNumberPatternConverterZeroPaddedTest {
+
+ @ClassRule
+ public static LoggerContextRule ctx = new LoggerContextRule("SequenceNumberPatternConverterZeroPaddedTest.yml");
+
+ @Before
+ public void before() {
+ ctx.getListAppender("Padded").clear();
+ }
+
+ @Test
+ public void testPaddedSequence() throws Exception {
+ final Logger logger = ctx.getLogger();
+ logger.info("Message 1");
+ logger.info("Message 2");
+ logger.info("Message 3");
+ logger.info("Message 4");
+ logger.info("Message 5");
+
+ final ListAppender app = ctx.getListAppender("Padded");
+ final List<String> messages = app.getMessages();
+ System.out.println("Written messages "+messages);
+ assertThat(messages, contains("001", "002", "003", "004", "005"));
+ }
+}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/selector/ClassLoaderContextSelectorTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/selector/ClassLoaderContextSelectorTest.java
index 5b7e6ad..0e1460f 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/selector/ClassLoaderContextSelectorTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/selector/ClassLoaderContextSelectorTest.java
@@ -19,7 +19,7 @@
import java.lang.reflect.Field;
import org.apache.logging.log4j.core.Logger;
-import org.apache.logging.log4j.core.util.ReflectionUtil;
+import org.apache.logging.log4j.util.ReflectionUtil;
import org.junit.Before;
import org.junit.Test;
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/time/MutableInstantTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/time/MutableInstantTest.java
index 63d3138..cf5d563 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/time/MutableInstantTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/time/MutableInstantTest.java
@@ -229,4 +229,4 @@
instant.initFromEpochMilli(123456, 789012);
assertEquals("MutableInstant[epochSecond=123, nano=456789012]", instant.toString());
}
-}
\ No newline at end of file
+}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/time/internal/format/FixedDateFormatTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/time/internal/format/FixedDateFormatTest.java
index 575ab7c..3fe125d 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/time/internal/format/FixedDateFormatTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/time/internal/format/FixedDateFormatTest.java
@@ -36,6 +36,11 @@
*/
public class FixedDateFormatTest {
+ private boolean containsNanos(FixedFormat fixedFormat) {
+ final String pattern = fixedFormat.getPattern();
+ return pattern.endsWith("n") || pattern.matches(".+n+X*") || pattern.matches(".+n+Z*");
+ }
+
@Test
public void testFixedFormat_getDatePatternNullIfNoDateInPattern() {
assertNull(FixedFormat.ABSOLUTE.getDatePattern());
@@ -157,15 +162,16 @@
final long start = now - TimeUnit.HOURS.toMillis(25);
final long end = now + TimeUnit.HOURS.toMillis(25);
for (final FixedFormat format : FixedFormat.values()) {
- if (format.getPattern().endsWith("n")) {
- continue; // cannot compile precise timestamp formats with SimpleDateFormat
+ String pattern = format.getPattern();
+ if (containsNanos(format) || format.getTimeZoneFormat() != null) {
+ continue; // cannot compile precise timestamp formats with SimpleDateFormat
}
- final SimpleDateFormat simpleDF = new SimpleDateFormat(format.getPattern(), Locale.getDefault());
+ final SimpleDateFormat simpleDF = new SimpleDateFormat(pattern, Locale.getDefault());
final FixedDateFormat customTF = new FixedDateFormat(format, TimeZone.getDefault());
for (long time = start; time < end; time += 12345) {
final String actual = customTF.format(time);
final String expected = simpleDF.format(new Date(time));
- assertEquals(format + "(" + format.getPattern() + ")" + "/" + time, expected, actual);
+ assertEquals(format + "(" + pattern + ")" + "/" + time, expected, actual);
}
}
}
@@ -176,15 +182,16 @@
final long start = now - TimeUnit.HOURS.toMillis(25);
final long end = now + TimeUnit.HOURS.toMillis(25);
for (final FixedFormat format : FixedFormat.values()) {
- if (format.getPattern().endsWith("n")) {
- continue; // cannot compile precise timestamp formats with SimpleDateFormat
+ String pattern = format.getPattern();
+ if (containsNanos(format) || format.getTimeZoneFormat() != null) {
+ continue; // cannot compile precise timestamp formats with SimpleDateFormat
}
- final SimpleDateFormat simpleDF = new SimpleDateFormat(format.getPattern(), Locale.getDefault());
+ final SimpleDateFormat simpleDF = new SimpleDateFormat(pattern, Locale.getDefault());
final FixedDateFormat customTF = new FixedDateFormat(format, TimeZone.getDefault());
for (long time = end; time > start; time -= 12345) {
final String actual = customTF.format(time);
final String expected = simpleDF.format(new Date(time));
- assertEquals(format + "(" + format.getPattern() + ")" + "/" + time, expected, actual);
+ assertEquals(format + "(" + pattern + ")" + "/" + time, expected, actual);
}
}
}
@@ -196,16 +203,17 @@
final long end = now + TimeUnit.HOURS.toMillis(25);
final char[] buffer = new char[128];
for (final FixedFormat format : FixedFormat.values()) {
- if (format.getPattern().endsWith("n")) {
- continue; // cannot compile precise timestamp formats with SimpleDateFormat
+ String pattern = format.getPattern();
+ if (containsNanos(format) || format.getTimeZoneFormat() != null) {
+ continue; // cannot compile precise timestamp formats with SimpleDateFormat
}
- final SimpleDateFormat simpleDF = new SimpleDateFormat(format.getPattern(), Locale.getDefault());
+ final SimpleDateFormat simpleDF = new SimpleDateFormat(pattern, Locale.getDefault());
final FixedDateFormat customTF = new FixedDateFormat(format, TimeZone.getDefault());
for (long time = start; time < end; time += 12345) {
final int length = customTF.format(time, buffer, 23);
final String actual = new String(buffer, 23, length);
final String expected = simpleDF.format(new Date(time));
- assertEquals(format + "(" + format.getPattern() + ")" + "/" + time, expected, actual);
+ assertEquals(format + "(" + pattern + ")" + "/" + time, expected, actual);
}
}
}
@@ -217,16 +225,17 @@
final long end = now + TimeUnit.HOURS.toMillis(25);
final char[] buffer = new char[128];
for (final FixedFormat format : FixedFormat.values()) {
- if (format.getPattern().endsWith("n")) {
- continue; // cannot compile precise timestamp formats with SimpleDateFormat
+ String pattern = format.getPattern();
+ if (containsNanos(format) || format.getTimeZoneFormat() != null) {
+ continue; // cannot compile precise timestamp formats with SimpleDateFormat
}
- final SimpleDateFormat simpleDF = new SimpleDateFormat(format.getPattern(), Locale.getDefault());
+ final SimpleDateFormat simpleDF = new SimpleDateFormat(pattern, Locale.getDefault());
final FixedDateFormat customTF = new FixedDateFormat(format, TimeZone.getDefault());
for (long time = end; time > start; time -= 12345) {
final int length = customTF.format(time, buffer, 23);
final String actual = new String(buffer, 23, length);
final String expected = simpleDF.format(new Date(time));
- assertEquals(format + "(" + format.getPattern() + ")" + "/" + time, expected, actual);
+ assertEquals(format + "(" + pattern + ")" + "/" + time, expected, actual);
}
}
}
@@ -375,15 +384,16 @@
final long end = now + TimeUnit.HOURS.toMillis(1);
for (final FixedFormat format : FixedFormat.values()) {
- if (format.getPattern().endsWith("n")) {
- continue; // cannot compile precise timestamp formats with SimpleDateFormat
+ String pattern = format.getPattern();
+ if (containsNanos(format) || format.getTimeZoneFormat() != null) {
+ continue; // cannot compile precise timestamp formats with SimpleDateFormat
}
- final SimpleDateFormat simpleDF = new SimpleDateFormat(format.getPattern(), Locale.getDefault());
+ final SimpleDateFormat simpleDF = new SimpleDateFormat(pattern, Locale.getDefault());
final FixedDateFormat customTF = new FixedDateFormat(format, TimeZone.getDefault());
for (long time = end; time > start; time -= 12345) {
final String actual = customTF.format(time);
final String expected = simpleDF.format(new Date(time));
- assertEquals(format + "(" + format.getPattern() + ")" + "/" + time, expected, actual);
+ assertEquals(format + "(" + pattern + ")" + "/" + time, expected, actual);
}
}
}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/util/AssertTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/util/AssertTest.java
deleted file mode 100644
index 242c41e..0000000
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/util/AssertTest.java
+++ /dev/null
@@ -1,68 +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.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-
-import static org.junit.Assert.*;
-
-/**
- *
- */
-@RunWith(Parameterized.class)
-public class AssertTest {
-
- private final Object value;
- private final boolean isEmpty;
-
- @Parameterized.Parameters
- public static Object[][] data() {
- return new Object[][]{
- // value, isEmpty
- {null, true},
- {"", true},
- {new Object[0], true},
- {new ArrayList<>(), true},
- {new HashMap<>(), true},
- {0, false},
- {1, false},
- {false, false},
- {true, false},
- {new Object[]{null}, false},
- {Collections.singletonList(null), false},
- {Collections.singletonMap("", null), false},
- {"null", false}
- };
- }
-
- public AssertTest(final Object value, final boolean isEmpty) {
- this.value = value;
- this.isEmpty = isEmpty;
- }
-
- @Test
- public void isEmpty() throws Exception {
- assertEquals(isEmpty, Assert.isEmpty(value));
- }
-
-}
\ No newline at end of file
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/util/ContextDataProviderTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/util/ContextDataProviderTest.java
new file mode 100644
index 0000000..f52c87d
--- /dev/null
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/util/ContextDataProviderTest.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.util;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.ThreadContext;
+import org.apache.logging.log4j.core.LoggerContext;
+import org.apache.logging.log4j.core.config.ConfigurationFactory;
+import org.apache.logging.log4j.core.impl.ThreadContextDataInjector;
+import org.apache.logging.log4j.test.appender.ListAppender;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ *
+ */
+public class ContextDataProviderTest {
+
+ private static Logger logger;
+ private static ListAppender appender;
+
+ @BeforeClass
+ public static void beforeClass() {
+ ThreadContextDataInjector.contextDataProviders.add(new TestContextDataProvider());
+ System.setProperty(ConfigurationFactory.CONFIGURATION_FILE_PROPERTY, "target/test-classes/log4j-contextData.xml");
+ LoggerContext loggerContext = (LoggerContext) LogManager.getContext(false);
+ logger = loggerContext.getLogger(ContextDataProviderTest.class.getName());
+ appender = loggerContext.getConfiguration().getAppender("List");
+ assertNotNull("No List appender", appender);
+ }
+
+ @Test
+ public void testContextProvider() throws Exception {
+ ThreadContext.put("loginId", "jdoe");
+ logger.debug("This is a test");
+ List<String> messages = appender.getMessages();
+ assertEquals("Incorrect number of messages", 1, messages.size());
+ assertTrue("Context data missing", messages.get(0).contains("testKey=testValue"));
+ }
+
+ private static class TestContextDataProvider implements ContextDataProvider {
+
+ @Override
+ public Map<String, String> supplyContextData() {
+ Map<String, String> contextData = new HashMap<>();
+ contextData.put("testKey", "testValue");
+ return contextData;
+ }
+
+ }
+}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/util/CronExpressionTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/util/CronExpressionTest.java
index bafdcfa..c00d2f0 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/util/CronExpressionTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/util/CronExpressionTest.java
@@ -18,6 +18,8 @@
import org.junit.Test;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
@@ -93,7 +95,7 @@
}
/**
- * 35,45, and 55 minutes past the hour evern hour.
+ * 35,45, and 55 minutes past the hour every hour.
*/
@Test
public void testPrevFireTime3() throws Exception {
@@ -153,4 +155,22 @@
assertEquals("Dates not equal.", expected, fireDate);
}
+ /*
+ * Input time with milliseconds will correctly return the next
+ * scheduled time.
+ */
+ @Test
+ public void testTimeBeforeMilliseconds() throws Exception {
+ final CronExpression parser = new CronExpression("0 0 0 * * ?");
+ final GregorianCalendar cal = new GregorianCalendar(2015, 10, 2, 0, 0, 0);
+ cal.set(Calendar.MILLISECOND, 100);
+ final Date date = cal.getTime();
+ SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
+ System.err.println(sdf.format(date));
+ final Date fireDate = parser.getTimeBefore(date);
+ System.err.println(sdf.format(fireDate));
+ final Date expected = new GregorianCalendar(2015, 10, 1, 0, 0, 0).getTime();
+ assertEquals("Dates not equal.", expected, fireDate);
+ }
+
}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/util/FileUtilsTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/util/FileUtilsTest.java
index 4294423..17568dc 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/util/FileUtilsTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/util/FileUtilsTest.java
@@ -45,6 +45,35 @@
}
@Test
+ public void testAbsoluteFileFromUriWithPlusCharactersInName() throws Exception {
+ final String config = "target/test-classes/log4j+config+with+plus+characters.xml";
+ final URI uri = new File(config).toURI();
+ final File file = FileUtils.fileFromUri(uri);
+ assertEquals(LOG4J_CONFIG_WITH_PLUS, file.getName());
+ assertTrue("file exists", file.exists());
+ }
+
+ @Test
+ public void testAbsoluteFileFromUriWithSpacesInName() throws Exception {
+ final String config = "target/test-classes/s p a c e s/log4j+config+with+plus+characters.xml";
+ final URI uri = new File(config).toURI();
+ final File file = FileUtils.fileFromUri(uri);
+ assertEquals(LOG4J_CONFIG_WITH_PLUS, file.getName());
+ assertTrue("file exists", file.exists());
+ }
+
+ @Test
+ public void testAbsoluteFileFromJBossVFSUri() throws Exception {
+ final String config = "target/test-classes/log4j+config+with+plus+characters.xml";
+ final String uriStr = new File(config).toURI().toString().replaceAll("^file:", "vfsfile:");
+ assertTrue(uriStr.startsWith("vfsfile:"));
+ final URI uri = URI.create(uriStr);
+ final File file = FileUtils.fileFromUri(uri);
+ assertEquals(LOG4J_CONFIG_WITH_PLUS, file.getName());
+ assertTrue("file exists", file.exists());
+ }
+
+ @Test
public void testFileFromUriWithSpacesAndPlusCharactersInName() throws Exception {
final String config = "target/test-classes/s%20p%20a%20c%20e%20s/log4j%2Bconfig%2Bwith%2Bplus%2Bcharacters.xml";
final URI uri = new URI(config);
@@ -53,37 +82,4 @@
assertTrue("file exists", file.exists());
}
- /**
- * Helps figure out why {@link #testFileFromUriWithPlusCharactersInName()} fails in Jenkins but asserting different
- * parts of the implementation of {@link FileUtils#fileFromUri(URI)}.
- */
- @Test
- public void testFileExistsWithPlusCharactersInName() throws Exception {
- final String config = "target/test-classes/log4j+config+with+plus+characters.xml";
- final File file = new File(config);
- assertEquals(LOG4J_CONFIG_WITH_PLUS, file.getName());
- assertTrue("file exists", file.exists());
- //
- final URI uri1 = new URI(config);
- assertNull(uri1.getScheme());
- //
- final URI uri2 = new File(uri1.getPath()).toURI();
- assertNotNull(uri2);
- assertTrue("URI \"" + uri2 + "\" does not end with \"" + LOG4J_CONFIG_WITH_PLUS + "\"", uri2.toString()
- .endsWith(LOG4J_CONFIG_WITH_PLUS));
- //
- final String fileName = uri2.toURL().getFile();
- assertTrue("File name \"" + fileName + "\" does not end with \"" + LOG4J_CONFIG_WITH_PLUS + "\"",
- fileName.endsWith(LOG4J_CONFIG_WITH_PLUS));
- }
-
- @Test
- public void testFileFromUriWithPlusCharactersConvertedToSpacesIfFileDoesNotExist() throws Exception {
- final String config = "NON-EXISTING-PATH/this+file+does+not+exist.xml";
- final URI uri = new URI(config);
- final File file = FileUtils.fileFromUri(uri);
- assertEquals("this file does not exist.xml", file.getName());
- assertFalse("file does not exist", file.exists());
- }
-
}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/util/InitTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/util/InitTest.java
new file mode 100644
index 0000000..5744886
--- /dev/null
+++ b/log4j-core/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.Assert.assertTrue;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.util.Timer;
+import org.junit.Ignore;
+import org.junit.Test;
+
+/**
+ * Test initialization.
+ */
+@Ignore
+public class InitTest {
+
+ @Test
+ public void initTest() {
+ Timer timer = new Timer("Log4j Initialization");
+ timer.start();
+ Logger logger = LogManager.getLogger();
+ timer.stop();
+ long elapsed = timer.getElapsedNanoTime();
+ System.out.println(timer.toString());
+ assertTrue("Initialization time exceeded threshold; elapsed " + elapsed, elapsed < 1000000000);
+ }
+}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/util/LoaderTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/util/LoaderTest.java
index c97d3b6..f2918b6 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/util/LoaderTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/util/LoaderTest.java
@@ -37,4 +37,4 @@
assertEquals("Expect Class return value for null ClassLoader.", Loader.class,
Loader.loadClass(Loader.class.getCanonicalName(), Loader.getClassLoader()));
}
-}
\ No newline at end of file
+}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/util/NetUtilsTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/util/NetUtilsTest.java
index c624798..fc40037 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/util/NetUtilsTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/util/NetUtilsTest.java
@@ -32,7 +32,7 @@
private static final boolean IS_WINDOWS = PropertiesUtil.getProperties().isOsWindows();
@Test
- public void testToUriWithoutBackslashes() throws URISyntaxException {
+ public void testToUriWithoutBackslashes() {
final String config = "file:///path/to/something/on/unix";
final URI uri = NetUtils.toURI(config);
@@ -41,7 +41,7 @@
}
@Test
- public void testToUriWindowsWithBackslashes() throws URISyntaxException {
+ public void testToUriWindowsWithBackslashes() {
Assume.assumeTrue(IS_WINDOWS);
final String config = "file:///D:\\path\\to\\something/on/windows";
final URI uri = NetUtils.toURI(config);
@@ -51,7 +51,7 @@
}
@Test
- public void testToUriWindowsAbsolutePath() throws URISyntaxException {
+ public void testToUriWindowsAbsolutePath() {
Assume.assumeTrue(IS_WINDOWS);
final String config = "D:\\path\\to\\something\\on\\windows";
final URI uri = NetUtils.toURI(config);
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/util/ProcessIdUtilTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/util/ProcessIdUtilTest.java
new file mode 100644
index 0000000..44cee0a
--- /dev/null
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/util/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.core.util;
+
+import org.apache.logging.log4j.core.util.ProcessIdUtil;
+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-core/src/test/java/org/apache/logging/log4j/core/util/ThrowablesTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/util/ThrowablesTest.java
index e8f82f5..7e354bc 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/util/ThrowablesTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/util/ThrowablesTest.java
@@ -16,41 +16,54 @@
*/
package org.apache.logging.log4j.core.util;
+import org.junit.Assert;
import org.junit.Test;
public class ThrowablesTest {
@Test
- public void testGetRootCauseNone() throws Exception {
+ public void testGetRootCauseNone() {
final NullPointerException throwable = new NullPointerException();
- org.junit.Assert.assertEquals(throwable, Throwables.getRootCause(throwable));
+ Assert.assertEquals(throwable, Throwables.getRootCause(throwable));
}
@Test
- public void testGetRootCauseDepth1() throws Exception {
- final NullPointerException throwable = new NullPointerException();
- org.junit.Assert.assertEquals(throwable, Throwables.getRootCause(new UnsupportedOperationException(throwable)));
+ public void testGetRootCauseDepth1() {
+ final Throwable cause = new NullPointerException();
+ final Throwable error = new UnsupportedOperationException(cause);
+ Assert.assertEquals(cause, Throwables.getRootCause(error));
}
@Test
- public void testGetRootCauseDepth2() throws Exception {
- final NullPointerException throwable = new NullPointerException();
- org.junit.Assert.assertEquals(throwable,
- Throwables.getRootCause(new IllegalArgumentException(new UnsupportedOperationException(throwable))));
+ public void testGetRootCauseDepth2() {
+ final Throwable rootCause = new NullPointerException();
+ final Throwable cause = new UnsupportedOperationException(rootCause);
+ final Throwable error = new IllegalArgumentException(cause);
+ Assert.assertEquals(rootCause, Throwables.getRootCause(error));
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testGetRootCauseLoop() {
+ final Throwable cause1 = new RuntimeException();
+ final Throwable cause2 = new RuntimeException(cause1);
+ final Throwable cause3 = new RuntimeException(cause2);
+ cause1.initCause(cause3);
+ // noinspection ThrowableNotThrown
+ Throwables.getRootCause(cause3);
}
@Test(expected = NullPointerException.class)
- public void testRethrowRuntimeException() throws Exception {
+ public void testRethrowRuntimeException() {
Throwables.rethrow(new NullPointerException());
}
@Test(expected = UnknownError.class)
- public void testRethrowError() throws Exception {
+ public void testRethrowError() {
Throwables.rethrow(new UnknownError());
}
@Test(expected = NoSuchMethodException.class)
- public void testRethrowCheckedException() throws Exception {
+ public void testRethrowCheckedException() {
Throwables.rethrow(new NoSuchMethodException());
}
}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/util/UuidTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/util/UuidTest.java
index 556f765..dbb4517 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/core/util/UuidTest.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/util/UuidTest.java
@@ -64,6 +64,22 @@
}
@Test
+ public void testInitialize() {
+ // Test if no ArrayIndexOutOfBoundsException is thrown when Mac address array is null
+ UuidUtil.initialize(null);
+
+ // Test if no ArrayIndexOutOfBoundsException is thrown for different Mac address lengths
+ for (int i=0; i < 10; i++) {
+ // Create MAC address byte array with i as size
+ byte[] mac = new byte[i];
+ for(int j=0; j < i; j++) {
+ mac[j] = (byte)j;
+ }
+ UuidUtil.initialize(mac);
+ }
+ }
+
+ @Test
public void testThreads() throws Exception {
final Thread[] threads = new Thread[THREADS];
final UUID[] uuids = new UUID[COUNT * THREADS];
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/core/util/WatchHttpTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/core/util/WatchHttpTest.java
new file mode 100644
index 0000000..8ae5970
--- /dev/null
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/core/util/WatchHttpTest.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.logging.log4j.core.util;
+
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.List;
+import java.util.Queue;
+import java.util.TimeZone;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.logging.log4j.core.config.Configuration;
+import org.apache.logging.log4j.core.config.ConfigurationListener;
+import org.apache.logging.log4j.core.config.ConfigurationScheduler;
+import org.apache.logging.log4j.core.config.DefaultConfiguration;
+import org.apache.logging.log4j.core.config.HttpWatcher;
+import org.apache.logging.log4j.core.config.Reconfigurable;
+import org.apache.logging.log4j.core.net.ssl.TestConstants;
+import org.apache.logging.log4j.core.time.internal.format.FastDateFormat;
+import org.apache.logging.log4j.util.PropertiesUtil;
+import org.junit.After;
+import org.junit.Assume;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Rule;
+import org.junit.Test;
+
+import com.github.tomakehurst.wiremock.junit.WireMockRule;
+import com.github.tomakehurst.wiremock.stubbing.StubMapping;
+
+import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
+import static com.github.tomakehurst.wiremock.client.WireMock.get;
+import static com.github.tomakehurst.wiremock.client.WireMock.removeStub;
+import static com.github.tomakehurst.wiremock.client.WireMock.stubFor;
+import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo;
+import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+/**
+ * Test the WatchManager
+ */
+public class WatchHttpTest {
+
+ private static final String FORCE_RUN_KEY = WatchHttpTest.class.getSimpleName() + ".forceRun";
+ private final String file = "log4j-test1.xml";
+ private static FastDateFormat formatter;
+ private static final String XML = "application/xml";
+
+ private static final boolean IS_WINDOWS = PropertiesUtil.getProperties().isOsWindows();
+
+ @BeforeClass
+ public static void beforeClass() {
+ try {
+ formatter = FastDateFormat.getInstance("EEE, dd MMM yyyy HH:mm:ss", TimeZone.getTimeZone("UTC"));
+ } catch (Exception ex) {
+ System.err.println("Unable to create date format.");
+ ex.printStackTrace();
+ throw ex;
+ }
+ }
+
+ @Rule
+ public WireMockRule wireMockRule = new WireMockRule(wireMockConfig().dynamicPort().dynamicHttpsPort()
+ .keystorePath(TestConstants.KEYSTORE_FILE)
+ .keystorePassword(String.valueOf(TestConstants.KEYSTORE_PWD()))
+ .keystoreType(TestConstants.KEYSTORE_TYPE));
+
+ @Test
+ public void testWatchManager() throws Exception {
+ BlockingQueue<String> queue = new LinkedBlockingQueue<>();
+ List<ConfigurationListener> listeners = new ArrayList<>();
+ listeners.add(new TestConfigurationListener(queue, "log4j-test1.xml"));
+ TimeZone timeZone = TimeZone.getTimeZone("UTC");
+ Calendar now = Calendar.getInstance(timeZone);
+ Calendar previous = now;
+ previous.add(Calendar.MINUTE, -5);
+ Configuration configuration = new DefaultConfiguration();
+ Assume.assumeTrue(!IS_WINDOWS || Boolean.getBoolean(FORCE_RUN_KEY));
+ URL url = new URL("http://localhost:" + wireMockRule.port() + "/log4j-test1.xml");
+ StubMapping stubMapping = stubFor(get(urlPathEqualTo("/log4j-test1.xml"))
+ .willReturn(aResponse()
+ .withBodyFile(file)
+ .withStatus(200)
+ .withHeader("Last-Modified", formatter.format(previous) + " GMT")
+ .withHeader("Content-Type", XML)));
+ final ConfigurationScheduler scheduler = new ConfigurationScheduler();
+ scheduler.incrementScheduledItems();
+ final WatchManager watchManager = new WatchManager(scheduler);
+ watchManager.setIntervalSeconds(1);
+ scheduler.start();
+ watchManager.start();
+ try {
+ watchManager.watch(new Source(url.toURI(), previous.getTimeInMillis()), new HttpWatcher(configuration, null,
+ listeners, previous.getTimeInMillis()));
+ final String str = queue.poll(2, TimeUnit.SECONDS);
+ assertNotNull("File change not detected", str);
+ } finally {
+ removeStub(stubMapping);
+ watchManager.stop();
+ scheduler.stop();
+ }
+ }
+
+ @Test
+ public void testNotModified() throws Exception {
+ BlockingQueue<String> queue = new LinkedBlockingQueue<>();
+ List<ConfigurationListener> listeners = new ArrayList<>();
+ listeners.add(new TestConfigurationListener(queue, "log4j-test2.xml"));
+ TimeZone timeZone = TimeZone.getTimeZone("UTC");
+ Calendar now = Calendar.getInstance(timeZone);
+ Calendar previous = now;
+ previous.add(Calendar.MINUTE, -5);
+ Configuration configuration = new DefaultConfiguration();
+ Assume.assumeTrue(!IS_WINDOWS || Boolean.getBoolean(FORCE_RUN_KEY));
+ URL url = new URL("http://localhost:" + wireMockRule.port() + "/log4j-test2.xml");
+ StubMapping stubMapping = stubFor(get(urlPathEqualTo("/log4j-test2.xml"))
+ .willReturn(aResponse()
+ .withBodyFile(file)
+ .withStatus(304)
+ .withHeader("Last-Modified", formatter.format(now) + " GMT")
+ .withHeader("Content-Type", XML)));
+ final ConfigurationScheduler scheduler = new ConfigurationScheduler();
+ scheduler.incrementScheduledItems();
+ final WatchManager watchManager = new WatchManager(scheduler);
+ watchManager.setIntervalSeconds(1);
+ scheduler.start();
+ watchManager.start();
+ try {
+ watchManager.watch(new Source(url.toURI(), previous.getTimeInMillis()), new HttpWatcher(configuration, null,
+ listeners, previous.getTimeInMillis()));
+ final String str = queue.poll(2, TimeUnit.SECONDS);
+ assertNull("File changed.", str);
+ } finally {
+ removeStub(stubMapping);
+ watchManager.stop();
+ scheduler.stop();
+ }
+ }
+
+ private class TestConfigurationListener implements ConfigurationListener {
+ private final Queue<String> queue;
+ private final String name;
+
+ public TestConfigurationListener(final Queue<String> queue, String name) {
+ this.queue = queue;
+ this.name = name;
+ }
+
+ @Override
+ public void onChange(Reconfigurable reconfigurable) {
+ //System.out.println("Reconfiguration detected for " + name);
+ queue.add(name);
+ }
+ }
+}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/junit/AbstractExternalFileCleaner.java b/log4j-core/src/test/java/org/apache/logging/log4j/junit/AbstractExternalFileCleaner.java
deleted file mode 100644
index 2288124..0000000
--- a/log4j-core/src/test/java/org/apache/logging/log4j/junit/AbstractExternalFileCleaner.java
+++ /dev/null
@@ -1,185 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache license, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the license for the specific language governing permissions and
- * limitations under the license.
- */
-package org.apache.logging.log4j.junit;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.PrintStream;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Map;
-import java.util.Set;
-
-import org.junit.Assert;
-import org.junit.rules.ExternalResource;
-
-/**
- * This class should not perform logging using Log4j to avoid accidentally
- * loading or re-loading Log4j configurations.
- */
-public abstract class AbstractExternalFileCleaner extends ExternalResource {
-
- protected static final String CLEANER_MARKER = "CLEANER";
-
- private static final int SLEEP_RETRY_MILLIS = 200;
- private final boolean cleanAfter;
- private final boolean cleanBefore;
- private final Set<Path> 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<Path, IOException> 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<Path, IOException> 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<Path> 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) {
- println(msg);
- }
- }
-
- protected void printStackTrace(final Throwable t) {
- if (printStream != null) {
- t.printStackTrace(printStream);
- }
- }
-
- @Override
- public String toString() {
- return getClass().getSimpleName() + " [files=" + files + ", cleanAfter=" + cleanAfter + ", cleanBefore="
- + cleanBefore + "]";
- }
-
-}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/junit/CleanFolders.java b/log4j-core/src/test/java/org/apache/logging/log4j/junit/CleanFolders.java
deleted file mode 100644
index f66f263..0000000
--- a/log4j-core/src/test/java/org/apache/logging/log4j/junit/CleanFolders.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.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.
- * <p>
- * This class should not perform logging using Log4j to avoid accidentally
- * loading or re-loading Log4j configurations.
- * </p>
- */
-public class CleanFolders extends AbstractExternalFileCleaner {
-
- public static final class DeleteAllFileVisitor extends SimpleFileVisitor<Path> {
-
- private final PrintStream printStream;
-
- public DeleteAllFileVisitor(final PrintStream logger) {
- this.printStream = logger;
- }
-
- @Override
- public FileVisitResult postVisitDirectory(final Path dir, final IOException exc) throws IOException {
- printf("%s Deleting directory %s\n", CLEANER_MARKER, dir);
- final boolean deleted = Files.deleteIfExists(dir);
- printf("%s Deleted directory %s: %s\n", CLEANER_MARKER, dir, deleted);
- return FileVisitResult.CONTINUE;
- }
-
- protected void printf(final String format, final Object... args) {
- if (printStream != null) {
- printStream.printf(format, args);
- }
- }
-
- @Override
- public FileVisitResult visitFile(final Path file, final BasicFileAttributes attrs) throws IOException {
- printf("%s Deleting file %s with %s\n", CLEANER_MARKER, file, attrs);
- final boolean deleted = Files.deleteIfExists(file);
- printf(CLEANER_MARKER, "%s Deleted file %s: %s\n", file, deleted);
- return FileVisitResult.CONTINUE;
- }
- }
-
- private static final int MAX_TRIES = 10;
-
- public CleanFolders(final boolean before, final boolean after, final int maxTries, final File... files) {
- super(before, after, maxTries, null, files);
- }
-
- public CleanFolders(final boolean before, final boolean after, final int maxTries, final 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/src/test/java/org/apache/logging/log4j/junit/LoggerContextRule.java b/log4j-core/src/test/java/org/apache/logging/log4j/junit/LoggerContextRule.java
index 36cc883..2215350 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/junit/LoggerContextRule.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/junit/LoggerContextRule.java
@@ -51,7 +51,7 @@
private static final String SYS_PROP_KEY_CLASS_NAME = "org.apache.logging.log4j.junit.LoggerContextRule#ClassName";
private static final String SYS_PROP_KEY_DISPLAY_NAME = "org.apache.logging.log4j.junit.LoggerContextRule#DisplayName";
- private final String configLocation;
+ private final String configurationLocation;
private LoggerContext loggerContext;
private Class<? extends ContextSelector> contextSelectorClass;
private String testClassName;
@@ -68,36 +68,36 @@
/**
* Constructs a new LoggerContextRule for a given configuration file.
*
- * @param configLocation
+ * @param configurationLocation
* path to configuration file
*/
- public LoggerContextRule(final String configLocation) {
- this(configLocation, null);
+ public LoggerContextRule(final String configurationLocation) {
+ this(configurationLocation, null);
}
/**
* Constructs a new LoggerContextRule for a given configuration file and a custom {@link ContextSelector} class.
*
- * @param configLocation
+ * @param configurationLocation
* path to configuration file
* @param contextSelectorClass
* custom ContextSelector class to use instead of default
*/
- public LoggerContextRule(final String configLocation, final Class<? extends ContextSelector> contextSelectorClass) {
- this(configLocation, contextSelectorClass, AbstractLifeCycle.DEFAULT_STOP_TIMEOUT,
+ public LoggerContextRule(final String configurationLocation, final Class<? extends ContextSelector> contextSelectorClass) {
+ this(configurationLocation, contextSelectorClass, AbstractLifeCycle.DEFAULT_STOP_TIMEOUT,
AbstractLifeCycle.DEFAULT_STOP_TIMEUNIT);
}
- public LoggerContextRule(final String configLocation, final Class<? extends ContextSelector> contextSelectorClass,
+ public LoggerContextRule(final String configurationLocation, final Class<? extends ContextSelector> contextSelectorClass,
final long shutdownTimeout, final TimeUnit shutdownTimeUnit) {
- this.configLocation = configLocation;
+ this.configurationLocation = configurationLocation;
this.contextSelectorClass = contextSelectorClass;
this.shutdownTimeout = shutdownTimeout;
this.shutdownTimeUnit = shutdownTimeUnit;
}
- public LoggerContextRule(final String config, final int shutdownTimeout, final TimeUnit shutdownTimeUnit) {
- this(config, null, shutdownTimeout, shutdownTimeUnit);
+ public LoggerContextRule(final String configurationLocation, final int shutdownTimeout, final TimeUnit shutdownTimeUnit) {
+ this(configurationLocation, null, shutdownTimeout, shutdownTimeUnit);
}
@Override
@@ -118,7 +118,7 @@
System.setProperty(SYS_PROP_KEY_CLASS_NAME, description.getClassName());
System.setProperty(SYS_PROP_KEY_DISPLAY_NAME, description.getDisplayName());
loggerContext = Configurator.initialize(description.getDisplayName(),
- description.getTestClass().getClassLoader(), configLocation);
+ description.getTestClass().getClassLoader(), configurationLocation);
try {
base.evaluate();
} finally {
@@ -175,6 +175,15 @@
}
/**
+ * 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.
@@ -283,7 +292,7 @@
public String toString() {
final StringBuilder builder = new StringBuilder();
builder.append("LoggerContextRule [configLocation=");
- builder.append(configLocation);
+ builder.append(configurationLocation);
builder.append(", contextSelectorClass=");
builder.append(contextSelectorClass);
builder.append("]");
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/junit/package-info.java b/log4j-core/src/test/java/org/apache/logging/log4j/junit/package-info.java
index 41f4b85..cae1cf7 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/junit/package-info.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/junit/package-info.java
@@ -19,4 +19,4 @@
* JUnit helper classes and TestRules.
* @see org.junit.rules.TestRule
*/
-package org.apache.logging.log4j.junit;
\ No newline at end of file
+package org.apache.logging.log4j.junit;
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/test/ExtendedLevels.java b/log4j-core/src/test/java/org/apache/logging/log4j/test/ExtendedLevels.java
index d69433e..81a6707 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/test/ExtendedLevels.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/test/ExtendedLevels.java
@@ -17,7 +17,7 @@
package org.apache.logging.log4j.test;
import org.apache.logging.log4j.Level;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.plugins.Plugin;
/**
*
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/test/GetLogger.java b/log4j-core/src/test/java/org/apache/logging/log4j/test/GetLogger.java
new file mode 100644
index 0000000..13015f2
--- /dev/null
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/test/GetLogger.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.logging.log4j.test;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.core.LoggerContext;
+
+/**
+ * Used to profile obtaining a Logger
+ */
+public class GetLogger {
+
+ public static void main(String[] args) {
+ int count = Integer.parseInt(args[0]);
+ LoggerContext loggerContext = (LoggerContext) LogManager.getContext(false);
+ for (int i = 0; i < count; ++i) {
+ Logger logger = LogManager.getLogger("Logger" + i);
+ logger.debug("This is a test");
+ }
+ System.out.println("Number of Loggers: " + loggerContext.getLoggers().size());
+ }
+}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/test/appender/AlwaysFailAppender.java b/log4j-core/src/test/java/org/apache/logging/log4j/test/appender/AlwaysFailAppender.java
index 666fca4..380dfe5 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/test/appender/AlwaysFailAppender.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/test/appender/AlwaysFailAppender.java
@@ -20,10 +20,10 @@
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.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.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginAttribute;
+import org.apache.logging.log4j.plugins.PluginFactory;
+import org.apache.logging.log4j.plugins.validation.constraints.Required;
/**
*
@@ -32,7 +32,7 @@
public class AlwaysFailAppender extends AbstractAppender {
private AlwaysFailAppender(final String name) {
- super(name, null, null, false);
+ super(name, null, null, false, null);
}
@Override
@@ -42,7 +42,7 @@
@PluginFactory
public static AlwaysFailAppender createAppender(
- @PluginAttribute("name") @Required(message = "A name for the Appender must be specified") final String name) {
+ @PluginAttribute @Required(message = "A name for the Appender must be specified") final String name) {
return new AlwaysFailAppender(name);
}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/test/appender/BlockingAppender.java b/log4j-core/src/test/java/org/apache/logging/log4j/test/appender/BlockingAppender.java
index 7c73ed0..922f50b 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/test/appender/BlockingAppender.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/test/appender/BlockingAppender.java
@@ -21,10 +21,10 @@
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.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.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginAttribute;
+import org.apache.logging.log4j.plugins.PluginFactory;
+import org.apache.logging.log4j.plugins.validation.constraints.Required;
/**
*
@@ -34,7 +34,7 @@
public volatile boolean running = true;
private BlockingAppender(final String name) {
- super(name, null, null, false);
+ super(name, null, null, false, null);
}
@Override
@@ -59,7 +59,7 @@
@PluginFactory
public static BlockingAppender createAppender(
- @PluginAttribute("name") @Required(message = "A name for the Appender must be specified") final String name) {
+ @PluginAttribute @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/DeadlockAppender.java b/log4j-core/src/test/java/org/apache/logging/log4j/test/appender/DeadlockAppender.java
index 700ee8b..9afd483 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/test/appender/DeadlockAppender.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/test/appender/DeadlockAppender.java
@@ -24,10 +24,10 @@
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.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.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginAttribute;
+import org.apache.logging.log4j.plugins.PluginFactory;
+import org.apache.logging.log4j.plugins.validation.constraints.Required;
/**
*
@@ -38,7 +38,7 @@
private WorkerThread thread = null;
private DeadlockAppender(final String name) {
- super(name, null, null, false);
+ super(name, null, null, false, null);
thread = new WorkerThread();
}
@@ -69,7 +69,7 @@
@PluginFactory
public static DeadlockAppender createAppender(
- @PluginAttribute("name") @Required(message = "A name for the Appender must be specified") final String name) {
+ @PluginAttribute @Required(message = "A name for the Appender must be specified") final String name) {
return new DeadlockAppender(name);
}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/test/appender/EncodingListAppender.java b/log4j-core/src/test/java/org/apache/logging/log4j/test/appender/EncodingListAppender.java
index 4d7ee56..f0da1d6 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/test/appender/EncodingListAppender.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/test/appender/EncodingListAppender.java
@@ -16,20 +16,20 @@
*/
package org.apache.logging.log4j.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.
+ * This appender is primarily used for testing. Use in a real environment is discouraged as the List could eventually
+ * grow to cause an OutOfMemoryError.
*
- * This appender will use {@link Layout#encode(Object, ByteBufferDestination)} (and not {@link Layout#toByteArray(LogEvent)}).
+ * This appender will use {@link Layout#encode(Object, ByteBufferDestination)} (and not
+ * {@link Layout#toByteArray(LogEvent)}).
*/
public class EncodingListAppender extends ListAppender {
@@ -37,12 +37,14 @@
super(name);
}
- public EncodingListAppender(final String name, final Filter filter, final Layout<? extends Serializable> layout, final boolean newline, final boolean raw) {
+ public EncodingListAppender(final String name, final Filter filter, final Layout<? extends Serializable> 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]);
+
@Override
public ByteBuffer getByteBuffer() {
return byteBuffer;
@@ -69,14 +71,6 @@
final Layout<? extends Serializable> layout = getLayout();
if (layout == null) {
events.add(event);
- } else if (layout instanceof SerializedLayout) {
- final Destination content = new Destination();
- content.byteBuffer.put(layout.getHeader());
- layout.encode(event, content);
- content.getByteBuffer().flip();
- final byte[] record = new byte[content.getByteBuffer().remaining()];
- content.getByteBuffer().get(record);
- data.add(record);
} else {
final Destination content = new Destination();
layout.encode(event, content);
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/test/appender/FailOnceAppender.java b/log4j-core/src/test/java/org/apache/logging/log4j/test/appender/FailOnceAppender.java
index 95157b5..2a18bc3 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/test/appender/FailOnceAppender.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/test/appender/FailOnceAppender.java
@@ -23,10 +23,10 @@
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.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.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginAttribute;
+import org.apache.logging.log4j.plugins.PluginFactory;
+import org.apache.logging.log4j.plugins.validation.constraints.Required;
/**
*
@@ -39,7 +39,7 @@
private final List<LogEvent> events = new ArrayList<>();
private FailOnceAppender(final String name) {
- super(name, null, null, false);
+ super(name, null, null, false, null);
}
@Override
@@ -59,7 +59,7 @@
@PluginFactory
public static FailOnceAppender createAppender(
- @PluginAttribute("name") @Required(message = "A name for the Appender must be specified") final String name) {
+ @PluginAttribute @Required(message = "A name for the Appender must be specified") final String name) {
return new FailOnceAppender(name);
}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/test/appender/InMemoryAppender.java b/log4j-core/src/test/java/org/apache/logging/log4j/test/appender/InMemoryAppender.java
index 07ac7fe..d67c845 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/test/appender/InMemoryAppender.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/test/appender/InMemoryAppender.java
@@ -23,6 +23,7 @@
import org.apache.logging.log4j.core.Layout;
import org.apache.logging.log4j.core.appender.AbstractOutputStreamAppender;
import org.apache.logging.log4j.core.appender.OutputStreamManager;
+import org.apache.logging.log4j.core.config.Property;
import org.apache.logging.log4j.core.filter.CompositeFilter;
/**
@@ -31,8 +32,8 @@
public class InMemoryAppender extends AbstractOutputStreamAppender<InMemoryAppender.InMemoryManager> {
public InMemoryAppender(final String name, final Layout<? extends Serializable> layout, final CompositeFilter filters,
- final boolean ignoreExceptions, final boolean writeHeader) {
- super(name, layout, filters, ignoreExceptions, true, new InMemoryManager(name, layout, writeHeader));
+ final boolean ignoreExceptions, final boolean writeHeader, Property[] properties) {
+ super(name, layout, filters, ignoreExceptions, true, properties, new InMemoryManager(name, layout, writeHeader));
}
@Override
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/test/appender/ListAppender.java b/log4j-core/src/test/java/org/apache/logging/log4j/test/appender/ListAppender.java
index c5ac234..1f9ec2d 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/test/appender/ListAppender.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/test/appender/ListAppender.java
@@ -16,13 +16,6 @@
*/
package org.apache.logging.log4j.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;
@@ -30,17 +23,24 @@
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.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
-import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
-import org.apache.logging.log4j.core.config.plugins.PluginElement;
-import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required;
+import org.apache.logging.log4j.core.config.Property;
import org.apache.logging.log4j.core.impl.MutableLogEvent;
-import org.apache.logging.log4j.core.layout.SerializedLayout;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginAttribute;
+import org.apache.logging.log4j.plugins.PluginElement;
+import org.apache.logging.log4j.plugins.PluginFactory;
+import org.apache.logging.log4j.plugins.validation.constraints.Required;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
/**
- * This appender is primarily used for testing. Use in a real environment is discouraged as the
- * List could eventually grow to cause an OutOfMemoryError.
+ * This appender is 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.
*
@@ -51,13 +51,13 @@
@Plugin(name = "List", category = Core.CATEGORY_NAME, elementType = Appender.ELEMENT_TYPE, printObject = true)
public class ListAppender extends AbstractAppender {
- // Use CopyOnWriteArrayList?
+ // Use Collections.synchronizedList rather than CopyOnWriteArrayList because we expect
+ // more frequent writes than reads.
+ final List<LogEvent> events = Collections.synchronizedList(new ArrayList<>());
- final List<LogEvent> events = new ArrayList<>();
+ private final List<String> messages = Collections.synchronizedList(new ArrayList<>());
- private final List<String> messages = new ArrayList<>();
-
- final List<byte[]> data = new ArrayList<>();
+ final List<byte[]> data = Collections.synchronizedList(new ArrayList<>());
private final boolean newLine;
@@ -67,22 +67,23 @@
/**
* CountDownLatch for asynchronous logging tests. Example usage:
+ *
* <pre>
- * @Rule
+ * @Rule
* public LoggerContextRule context = new LoggerContextRule("log4j-list.xml");
* private ListAppender listAppender;
*
- * @Before
+ * @Before
* public void before() throws Exception {
* listAppender = context.getListAppender("List");
* }
*
- * @Test
+ * @Test
* public void testSomething() throws Exception {
* listAppender.countDownLatch = new CountDownLatch(1);
*
* Logger logger = LogManager.getLogger();
- * logger.info("log one event anynchronously");
+ * 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);
@@ -91,20 +92,20 @@
* }
* </pre>
*/
- public CountDownLatch countDownLatch = null;
+ public volatile CountDownLatch countDownLatch = null;
public ListAppender(final String name) {
- super(name, null, null);
+ super(name, null, null, true, Property.EMPTY_ARRAY);
newLine = false;
raw = false;
}
- public ListAppender(final String name, final Filter filter, final Layout<? extends Serializable> layout, final boolean newline,
- final boolean raw) {
- super(name, filter, layout);
+ public ListAppender(final String name, final Filter filter, final Layout<? extends Serializable> 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)) {
+ if (layout != null) {
final byte[] bytes = layout.getHeader();
if (bytes != null) {
write(bytes);
@@ -113,7 +114,7 @@
}
@Override
- public synchronized void append(final LogEvent event) {
+ public void append(final LogEvent event) {
final Layout<? extends Serializable> layout = getLayout();
if (layout == null) {
if (event instanceof MutableLogEvent) {
@@ -122,13 +123,6 @@
} else {
events.add(event);
}
- } 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));
}
@@ -189,85 +183,85 @@
return true;
}
- public synchronized ListAppender clear() {
+ public ListAppender clear() {
events.clear();
messages.clear();
data.clear();
return this;
}
- public synchronized List<LogEvent> getEvents() {
- return Collections.unmodifiableList(events);
+ /** Returns an immutable snapshot of captured log events */
+ public List<LogEvent> getEvents() {
+ return Collections.unmodifiableList(new ArrayList<>(events));
}
- public synchronized List<String> getMessages() {
- return Collections.unmodifiableList(messages);
+ /** Returns an immutable snapshot of captured messages */
+ public List<String> 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<String> getMessages(final int minSize, final long timeout, final TimeUnit timeUnit) throws InterruptedException {
+ public List<String> getMessages(final int minSize, final long timeout, final TimeUnit timeUnit)
+ throws InterruptedException {
final long endMillis = System.currentTimeMillis() + timeUnit.toMillis(timeout);
while (messages.size() < minSize && System.currentTimeMillis() < endMillis) {
Thread.sleep(100);
}
- return Collections.unmodifiableList(messages);
+ return getMessages();
}
- public synchronized List<byte[]> getData() {
- return Collections.unmodifiableList(data);
+ /** Returns an immutable snapshot of captured data */
+ public List<byte[]> getData() {
+ return Collections.unmodifiableList(new ArrayList<>(data));
}
public static ListAppender createAppender(final String name, final boolean newLine, final boolean raw,
- final Layout<? extends Serializable> layout, final Filter filter) {
+ final Layout<? extends Serializable> layout, final Filter filter) {
return new ListAppender(name, filter, layout, newLine, raw);
}
- @PluginBuilderFactory
+ @PluginFactory
public static Builder newBuilder() {
return new Builder();
}
- public static class Builder implements org.apache.logging.log4j.core.util.Builder<ListAppender> {
+ public static class Builder implements org.apache.logging.log4j.plugins.util.Builder<ListAppender> {
- @PluginBuilderAttribute
- @Required
private String name;
-
- @PluginBuilderAttribute
private boolean entryPerNewLine;
-
- @PluginBuilderAttribute
private boolean raw;
-
- @PluginElement("Layout")
private Layout<? extends Serializable> layout;
-
- @PluginElement("Filter")
private Filter filter;
+ @PluginAttribute
+ @Required
public Builder setName(final String name) {
this.name = name;
return this;
}
+ @PluginAttribute
public Builder setEntryPerNewLine(final boolean entryPerNewLine) {
this.entryPerNewLine = entryPerNewLine;
return this;
}
+ @PluginAttribute
public Builder setRaw(final boolean raw) {
this.raw = raw;
return this;
}
+ @PluginElement
public Builder setLayout(final Layout<? extends Serializable> layout) {
this.layout = layout;
return this;
}
+ @PluginElement
public Builder setFilter(final Filter filter) {
this.filter = filter;
return this;
@@ -282,7 +276,8 @@
/**
* Gets the named ListAppender if it has been registered.
*
- * @param name the name of the ListAppender
+ * @param name
+ * the name of the ListAppender
* @return the named ListAppender or {@code null} if it does not exist
* @see org.apache.logging.log4j.junit.LoggerContextRule#getListAppender(String)
*/
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/test/appender/UsesLoggingAppender.java b/log4j-core/src/test/java/org/apache/logging/log4j/test/appender/UsesLoggingAppender.java
index 5c80cb4..9b4abfd 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/test/appender/UsesLoggingAppender.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/test/appender/UsesLoggingAppender.java
@@ -22,11 +22,12 @@
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.config.Property;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginAttribute;
+import org.apache.logging.log4j.plugins.PluginElement;
+import org.apache.logging.log4j.plugins.PluginFactory;
+import org.apache.logging.log4j.plugins.validation.constraints.Required;
import org.apache.logging.log4j.test.SomethingThatUsesLogging;
/**
@@ -38,16 +39,16 @@
private final SomethingThatUsesLogging thing;
private UsesLoggingAppender(final String name, final Filter filter, final Layout<?> layout, final boolean ignoreExceptions) {
- super(name, filter, layout, ignoreExceptions);
+ super(name, filter, layout, ignoreExceptions, Property.EMPTY_ARRAY);
thing = new SomethingThatUsesLogging();
}
@PluginFactory
public static UsesLoggingAppender createAppender(
- @PluginAttribute("name") @Required(message = "A name for the Appender must be specified") final String name,
+ @PluginAttribute @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) {
+ @PluginElement final Layout<?> layout,
+ @PluginElement final Filter filter) {
return new UsesLoggingAppender(name, filter, layout, ignore);
}
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/test/layout/BasicLayout.java b/log4j-core/src/test/java/org/apache/logging/log4j/test/layout/BasicLayout.java
index 7c13ff9..eeae653 100644
--- a/log4j-core/src/test/java/org/apache/logging/log4j/test/layout/BasicLayout.java
+++ b/log4j-core/src/test/java/org/apache/logging/log4j/test/layout/BasicLayout.java
@@ -21,8 +21,8 @@
import org.apache.logging.log4j.core.Core;
import org.apache.logging.log4j.core.LogEvent;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginFactory;
import org.apache.logging.log4j.core.layout.AbstractStringLayout;
import org.apache.logging.log4j.util.Strings;
diff --git a/log4j-core/src/test/java/org/apache/logging/log4j/util/ProcessIdUtilTest.java b/log4j-core/src/test/java/org/apache/logging/log4j/util/ProcessIdUtilTest.java
deleted file mode 100644
index 13aaf9c..0000000
--- a/log4j-core/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-core/src/test/resources/GelfLayoutTest3.xml b/log4j-core/src/test/resources/GelfLayoutTest3.xml
new file mode 100644
index 0000000..fcb23d3
--- /dev/null
+++ b/log4j-core/src/test/resources/GelfLayoutTest3.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-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 "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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="GelfLayoutTest3">
+ <Appenders>
+ <List name="list">
+ <GelfLayout host="myhost" includeThreadContext="true" threadContextIncludes="requestId,loginId">
+ <MessagePattern>%d [%t] %-5p %X{requestId, loginId} %C{1.}.%M:%L - %m%n"</MessagePattern>
+ <KeyValuePair key="foo" value="FOO"/>
+ <KeyValuePair key="runtime" value="$${java:runtime}"/>
+ </GelfLayout>
+ </List>
+ </Appenders>
+
+ <Loggers>
+ <Root level="info">
+ <AppenderRef ref="list"/>
+ </Root>
+ </Loggers>
+</Configuration>
\ No newline at end of file
diff --git a/log4j-core/src/test/resources/JmsAppenderTest.xml b/log4j-core/src/test/resources/JmsAppenderTest.xml
index 9a5f951..4a11244 100644
--- a/log4j-core/src/test/resources/JmsAppenderTest.xml
+++ b/log4j-core/src/test/resources/JmsAppenderTest.xml
@@ -22,16 +22,21 @@
destinationBindingName="jms/destination">
<PatternLayout pattern="%m"/>
</JMS>
+ <JMS name="JmsAppender-MessageLayout"
+ factoryBindingName="jms/connectionFactory"
+ destinationBindingName="jms/destination-ml">
+ <MessageLayout />
+ </JMS>
<!-- backwards compatibility tests -->
<JMSQueue name="JmsQueueAppender"
factoryBindingName="jms/queues"
queueBindingName="jms/queue">
- <SerializedLayout/>
+ <PatternLayout pattern="%m"/>
</JMSQueue>
<JMSTopic name="JmsTopicAppender"
factoryBindingName="jms/topics"
topicBindingName="jms/topic">
- <SerializedLayout/>
+ <PatternLayout pattern="%m"/>
</JMSTopic>
</Appenders>
<Loggers>
@@ -39,4 +44,4 @@
<AppenderRef ref="JmsQueueAppender"/>
</Root>
</Loggers>
-</Configuration>
\ No newline at end of file
+</Configuration>
diff --git a/log4j-core/src/test/resources/KafkaAppenderTest.xml b/log4j-core/src/test/resources/KafkaAppenderTest.xml
deleted file mode 100644
index b775f88..0000000
--- a/log4j-core/src/test/resources/KafkaAppenderTest.xml
+++ /dev/null
@@ -1,54 +0,0 @@
-<?xml version="1.0" encoding="UTF-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 "License"); you may not use this file except in compliance with
- ~ the License. You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT 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="KafkaAppenderTest" status="OFF">
- <Appenders>
- <Kafka name="KafkaAppenderWithLayout" topic="kafka-topic">
- <PatternLayout pattern="[%m]"/>
- <Property name="bootstrap.servers">localhost:9092</Property>
- <Property name="timeout.ms">1000</Property>
- </Kafka>
- <Kafka name="KafkaAppenderWithSerializedLayout" topic="kafka-topic">
- <SerializedLayout/>
- <Property name="bootstrap.servers">localhost:9092</Property>
- <Property name="timeout.ms">1000</Property>
- </Kafka>
- <Kafka name="AsyncKafkaAppender" topic="kafka-topic">
- <PatternLayout pattern="%m"/>
- <Property name="bootstrap.servers">localhost:9092</Property>
- <Property name="syncSend">false</Property>
- </Kafka>
- <Kafka name="KafkaAppenderWithKey" topic="kafka-topic" key="key">
- <PatternLayout pattern="%m"/>
- <Property name="timeout.ms">1000</Property>
- <Property name="bootstrap.servers">localhost:9092</Property>
- </Kafka>
- <Kafka name="KafkaAppenderWithKeyLookup" topic="kafka-topic" key="$${date:dd-MM-yyyy}">
- <PatternLayout pattern="%m"/>
- <Property name="timeout.ms">1000</Property>
- <Property name="bootstrap.servers">localhost:9092</Property>
- </Kafka>
- </Appenders>
- <Loggers>
- <Root level="info">
- <AppenderRef ref="KafkaAppenderWithLayout"/>
- <AppenderRef ref="KafkaAppenderWithSerializedLayout"/>
- <AppenderRef ref="AsyncKafkaAppender"/>
- <AppenderRef ref="KafkaAppenderWithKey"/>
- </Root>
- </Loggers>
-</Configuration>
\ No newline at end of file
diff --git a/log4j-core/src/test/resources/README.md b/log4j-core/src/test/resources/README.md
index dcfc8d7..4afccdc 100644
--- a/log4j-core/src/test/resources/README.md
+++ b/log4j-core/src/test/resources/README.md
@@ -25,8 +25,7 @@
</Configuration>
Note that if you don't specify a layout for a ListAppender, your log messages will be stored in a list of LogEvents.
-If you use a SerializedLayout, your log messages will be stored in a list of byte arrays. If you specify any other
-type of layout, your log messages will be stored in a list of strings. For more details, check out the class
+With a layout, your log messages will be stored in a list of strings. For more details, check out the class
`org.apache.logging.log4j.test.appender.ListAppender`.
Specifying Configuration Files in JUnit Tests
diff --git a/log4j-core/src/test/resources/SequenceNumberPatternConverterZeroPaddedTest.yml b/log4j-core/src/test/resources/SequenceNumberPatternConverterZeroPaddedTest.yml
new file mode 100644
index 0000000..3dea4d7
--- /dev/null
+++ b/log4j-core/src/test/resources/SequenceNumberPatternConverterZeroPaddedTest.yml
@@ -0,0 +1,15 @@
+Configuration:
+ status: OFF
+ name: SequenceNumberPatternConverterTest
+
+ Appenders:
+ List:
+ - name: Padded
+ PatternLayout:
+ pattern: '%03sn'
+
+ Loggers:
+ Root:
+ level: INFO
+ AppenderRef:
+ - ref: Padded
diff --git a/log4j-core/src/test/resources/__files/log4j-test1.xml b/log4j-core/src/test/resources/__files/log4j-test1.xml
new file mode 100644
index 0000000..3598aec
--- /dev/null
+++ b/log4j-core/src/test/resources/__files/log4j-test1.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="UTF-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 "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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="XMLConfigTest">
+ <Properties>
+ <Property name="filename">target/test-xml.log</Property>
+ </Properties>
+ <ThresholdFilter level="debug"/>
+
+ <Appenders>
+ <Console name="STDOUT">
+ <PatternLayout pattern="%m%n"/>
+ </Console>
+ <File name="File" fileName="${filename}" bufferedIO="false">
+ <PatternLayout>
+ <Pattern>%d %p %C{1.} [%t] %m%n</Pattern>
+ </PatternLayout>
+ </File>
+ <List name="List">
+ <Filters>
+ <ThresholdFilter level="error"/>
+ </Filters>
+ </List>
+ </Appenders>
+
+ <Loggers>
+ <Logger name="org.apache.logging.log4j.test1" level="debug" additivity="false">
+ <ThreadContextMapFilter>
+ <KeyValuePair key="test" value="123"/>
+ </ThreadContextMapFilter>
+ <AppenderRef ref="STDOUT"/>
+ </Logger>
+
+ <Logger name="org.apache.logging.log4j.test2" level="debug" additivity="false">
+ <AppenderRef ref="File"/>
+ </Logger>
+
+ <Root level="error">
+ <AppenderRef ref="STDOUT"/>
+ </Root>
+ </Loggers>
+
+</Configuration>
\ No newline at end of file
diff --git a/log4j-core/src/test/resources/__files/onStartup.log b/log4j-core/src/test/resources/__files/onStartup.log
new file mode 100644
index 0000000..036b721
--- /dev/null
+++ b/log4j-core/src/test/resources/__files/onStartup.log
@@ -0,0 +1,16 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<html>
+<head>
+<meta charset="UTF-8"/>
+<title>Log4j Log Messages</title>
+<style type="text/css">
+<!--
+body, table {font-family:arial,sans-serif; font-size: medium
+;}th {background: #336699; color: #FFFFFF; text-align: left;}
+-->
+</style>
+</head>
+<body bgcolor="#FFFFFF" topmargin="6" leftmargin="6">
+
+<br>
+</body></html>
\ No newline at end of file
diff --git a/log4j-core/src/test/resources/customplugin/FixedStringLayout.java.source b/log4j-core/src/test/resources/customplugin/FixedStringLayout.java.source
index e3049c1..f853a49 100644
--- a/log4j-core/src/test/resources/customplugin/FixedStringLayout.java.source
+++ b/log4j-core/src/test/resources/customplugin/FixedStringLayout.java.source
@@ -21,9 +21,9 @@
import java.util.Map;
import org.apache.logging.log4j.core.LogEvent;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
-import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginAttribute;
+import org.apache.logging.log4j.plugins.PluginFactory;
import org.apache.logging.log4j.core.layout.AbstractStringLayout;
@Plugin(name = "FixedString", category = "Core", elementType = "layout", printObject = true)
diff --git a/log4j-core/src/test/resources/gcFreeLogging.xml b/log4j-core/src/test/resources/gcFreeLogging.xml
index 380c128..2f14f3f 100644
--- a/log4j-core/src/test/resources/gcFreeLogging.xml
+++ b/log4j-core/src/test/resources/gcFreeLogging.xml
@@ -18,21 +18,21 @@
<KeyValuePair key="id" value="Login"/>
<KeyValuePair key="id" value="Logout"/>
</StructuredDataFilter>
- <TimeFilter start="05:00:00" end="05:30:00" onMatch="ACCEPT" onMismatch="NEUTRAL"/>
+ <!--<TimeFilter start="05:00:00" end="05:30:00" onMatch="ACCEPT" onMismatch="NEUTRAL"/>-->
</Filters>
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%p %c{1.} [%t] %X{aKey} %X %m%ex%n" />
</Console>
<File name="File" fileName="target/gcfreefile.log" bufferedIO="false">
- <TimeFilter start="05:00:00" end="05:30:00" onMatch="ACCEPT" onMismatch="NEUTRAL"/>
+ <!--<TimeFilter start="05:00:00" end="05:30:00" onMatch="ACCEPT" onMismatch="NEUTRAL"/>-->
<PatternLayout>
<Pattern>%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}</Pattern>
</PatternLayout>
</File>
<RollingFile name="RollingFile" fileName="target/gcfreeRollingFile.log"
filePattern="target/gcfree-%d{MM-dd-yy-HH-mm-ss}.log.gz">
- <TimeFilter start="05:00:00" end="05:30:00" onMatch="ACCEPT" onMismatch="NEUTRAL"/>
+ <!--<TimeFilter start="05:00:00" end="05:30:00" onMatch="ACCEPT" onMismatch="NEUTRAL"/>-->
<PatternLayout>
<Pattern>%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}</Pattern>
</PatternLayout>
@@ -41,7 +41,7 @@
</Policies>
</RollingFile>
<RandomAccessFile name="RandomAccessFile" fileName="target/gcfreeRAF.log" immediateFlush="false" append="false">
- <TimeFilter start="05:00:00" end="05:30:00" onMatch="ACCEPT" onMismatch="NEUTRAL"/>
+ <!--<TimeFilter start="05:00:00" end="05:30:00" onMatch="ACCEPT" onMismatch="NEUTRAL"/>-->
<PatternLayout>
<Pattern>%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}</Pattern>
</PatternLayout>
diff --git a/log4j-core/src/test/resources/gcFreeMixedSyncAsyncLogging.xml b/log4j-core/src/test/resources/gcFreeMixedSyncAsyncLogging.xml
index 91de0a6..e85894c 100644
--- a/log4j-core/src/test/resources/gcFreeMixedSyncAsyncLogging.xml
+++ b/log4j-core/src/test/resources/gcFreeMixedSyncAsyncLogging.xml
@@ -18,21 +18,21 @@
<KeyValuePair key="id" value="Login"/>
<KeyValuePair key="id" value="Logout"/>
</StructuredDataFilter>
- <TimeFilter start="05:00:00" end="05:30:00" onMatch="ACCEPT" onMismatch="NEUTRAL"/>
+ <!--<TimeFilter start="05:00:00" end="05:30:00" onMatch="ACCEPT" onMismatch="NEUTRAL"/>-->
</Filters>
<Appenders>
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="%p %c{1.} [%t] %X{aKey} %X %m%ex%n" />
</Console>
<File name="File" fileName="target/gcfreefileMixed.log" bufferedIO="false">
- <TimeFilter start="05:00:00" end="05:30:00" onMatch="ACCEPT" onMismatch="NEUTRAL"/>
+ <!--<TimeFilter start="05:00:00" end="05:30:00" onMatch="ACCEPT" onMismatch="NEUTRAL"/>-->
<PatternLayout>
<Pattern>%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}</Pattern>
</PatternLayout>
</File>
<RollingFile name="RollingFile" fileName="target/gcfreeRollingFileMixed.log"
filePattern="target/gcfree-%d{MM-dd-yy-HH-mm-ss}.log.gz">
- <TimeFilter start="05:00:00" end="05:30:00" onMatch="ACCEPT" onMismatch="NEUTRAL"/>
+ <!--<TimeFilter start="05:00:00" end="05:30:00" onMatch="ACCEPT" onMismatch="NEUTRAL"/>-->
<PatternLayout>
<Pattern>%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}</Pattern>
</PatternLayout>
@@ -41,7 +41,7 @@
</Policies>
</RollingFile>
<RandomAccessFile name="RandomAccessFile" fileName="target/gcfreeRAFMixed.log" immediateFlush="false" append="false">
- <TimeFilter start="05:00:00" end="05:30:00" onMatch="ACCEPT" onMismatch="NEUTRAL"/>
+ <!--<TimeFilter start="05:00:00" end="05:30:00" onMatch="ACCEPT" onMismatch="NEUTRAL"/>-->
<PatternLayout>
<Pattern>%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}</Pattern>
</PatternLayout>
diff --git a/log4j-core/src/test/resources/legacy-plugins.xml b/log4j-core/src/test/resources/legacy-plugins.xml
new file mode 100644
index 0000000..725be17
--- /dev/null
+++ b/log4j-core/src/test/resources/legacy-plugins.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-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 "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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="ERROR" name="XMLConfigTest">
+
+ <Appenders>
+ <Console name="Console" target="SYSTEM_OUT">
+ <LogstashLayout dateTimeFormatPattern="yyyy-MM-dd'T'HH:mm:ss.SSSZZZ" prettyPrintEnabled="true"
+ stackTraceEnabled="true"/>
+ </Console>
+ <Console name="customConsole">
+ <CustomConsoleLayout/>
+ </Console>
+ </Appenders>
+ <Loggers>
+ <Root level="trace">
+ <AppenderRef ref="Console"/>
+ </Root>
+ </Loggers>
+
+</Configuration>
\ No newline at end of file
diff --git a/log4j-core/src/test/resources/log4j-contextData.xml b/log4j-core/src/test/resources/log4j-contextData.xml
new file mode 100644
index 0000000..80e83ee
--- /dev/null
+++ b/log4j-core/src/test/resources/log4j-contextData.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-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 "License"); you may not use this file except in compliance with
+ ~ the License. You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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="Context Data Test">
+ <Appenders>
+ <List name="List">
+ <PatternLayout pattern="[%-5level] ThreadContext%X %c{1.} %msg%n" />
+ </List>
+ </Appenders>
+ <Loggers>
+ <Root level="debug">
+ <AppenderRef ref="List" />
+ </Root>
+ </Loggers>
+</Configuration>
\ No newline at end of file
diff --git a/log4j-core/src/test/resources/log4j-rolling-count.xml b/log4j-core/src/test/resources/log4j-rolling-count.xml
new file mode 100644
index 0000000..28dd6e1
--- /dev/null
+++ b/log4j-core/src/test/resources/log4j-rolling-count.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="UTF-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 "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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="error" name="XmlProperties">
+ <Properties>
+ <Property name="baseFilename">target/rolling_count/rolling_test.log</Property>
+ <Property name="pattern">%d{HH:mm:ss.SSS} [%t] %-5level %logger{1.} - %msg%n</Property>
+ </Properties>
+
+ <Appenders>
+ <Console name="Console" target="SYSTEM_OUT">
+ <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/>
+ </Console>
+ <RollingFile name="RollingFile" fileName="${baseFilename}" filePattern="${baseFilename}.%02i" immediateFlush="true" append="true">
+ <PatternLayout pattern="${pattern}"/>
+ <Policies>
+ <SizeBasedTriggeringPolicy size="1 KB"/>
+ </Policies>
+ <DefaultRolloverStrategy max="15"/>
+ </RollingFile>
+ </Appenders>
+ <Loggers>
+ <Logger name="LogTest" level="trace"/>
+ <Logger name="org.apache.logging.log4j.status" level="trace"/>
+ <Root level="info">
+ <AppenderRef ref="RollingFile"/>
+ </Root>
+ </Loggers>
+</Configuration>
\ No newline at end of file
diff --git a/log4j-core/src/test/resources/log4j-rolling-cron-and-size.xml b/log4j-core/src/test/resources/log4j-rolling-cron-and-size.xml
new file mode 100644
index 0000000..8e32a7f
--- /dev/null
+++ b/log4j-core/src/test/resources/log4j-rolling-cron-and-size.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="UTF-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 "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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="ERROR" name="RollingCronTest" monitorInterval="1">
+ <Properties>
+ <Property name="filename">target/rolling1/rollingtest.log</Property>
+ <Property name="DIR">target/rolling-cron-size</Property>
+ </Properties>
+ <Filters>
+ <ThresholdFilter level="debug"/>
+ </Filters>
+
+ <Appenders>
+ <Console name="STDOUT">
+ <PatternLayout pattern="%m%n"/>
+ </Console>
+ <RollingFile name="RollingFile" fileName="${filename}" filePattern="${DIR}/rollingfile_%d{yyyy-MM-dd-HH-mm-ss}_%i.log">
+ <PatternLayout pattern="%d{yyyy-MM-dd-HH-mm-ss} [%level] [%c] %msg%n"/>
+ <Policies>
+ <CronTriggeringPolicy schedule="* * * * * ?"/>
+ <SizeBasedTriggeringPolicy size="1000" />
+ </Policies>
+ </RollingFile>
+ <List name="List">
+ <Filters>
+ <ThresholdFilter level="error"/>
+ </Filters>
+ </List>
+ </Appenders>
+
+ <Loggers>
+ <Logger name="org.apache.logging.log4j.core.appender.rolling" level="debug" additivity="false">
+ <AppenderRef ref="RollingFile"/>
+ </Logger>
+
+ <Root level="error">
+ <AppenderRef ref="STDOUT"/>
+ </Root>
+ </Loggers>
+
+</Configuration>
\ No newline at end of file
diff --git a/log4j-core/src/test/resources/log4j-rolling-direct-1906.xml b/log4j-core/src/test/resources/log4j-rolling-direct-1906.xml
new file mode 100644
index 0000000..326d0ca
--- /dev/null
+++ b/log4j-core/src/test/resources/log4j-rolling-direct-1906.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Configuration status="WARN" monitorInterval="30">
+ <Properties>
+ <Property name="baseDir">target/rolling-direct-1906</Property>
+ </Properties>
+ <Appenders>
+ <RollingFile name="RollingFile" filePattern="${baseDir}/rollingfile.%d{yyyy-MM-dd-HH-mm-ss}.log">
+ <PatternLayout pattern="%d{yyyy-MM-dd-HH-mm-ss} [%level] [%c] %msg%n" />
+ <Policies>
+ <TimeBasedTriggeringPolicy />
+ </Policies>
+ <DirectWriteRolloverStrategy />
+ </RollingFile>
+ </Appenders>
+ <Loggers>
+ <Root level="trace">
+ <AppenderRef ref="RollingFile" />
+ </Root>
+ </Loggers>
+</Configuration>
\ No newline at end of file
diff --git a/log4j-core/src/test/resources/log4j-rolling-direct-startup-size.xml b/log4j-core/src/test/resources/log4j-rolling-direct-startup-size.xml
new file mode 100644
index 0000000..4571c96
--- /dev/null
+++ b/log4j-core/src/test/resources/log4j-rolling-direct-startup-size.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-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 "License"); you may not use this file except in compliance with
+ ~ the License. You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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="INFO" name="RollingAppenderDirectWriteStartupSizeTest">
+ <Appenders>
+ <RollingFile name="RollingFile" filePattern="target/rolling-direct-startup-size/size-test.log">
+ <PatternLayout>
+ <Pattern>%d %p %c{1.} [%t] %m%n</Pattern>
+ </PatternLayout>
+ <Policies>
+ <SizeBasedTriggeringPolicy size="250 MB"/>
+ </Policies>
+ <DirectWriteRolloverStrategy maxFiles="10"/>
+ </RollingFile>
+ </Appenders>
+ <Loggers>
+ <Root level="debug">
+ <AppenderRef ref="RollingFile"/>
+ </Root>
+ </Loggers>
+</Configuration>
\ No newline at end of file
diff --git a/log4j-core/src/test/resources/log4j-rolling-direct.xml b/log4j-core/src/test/resources/log4j-rolling-direct.xml
index a16d86a..c3a7006 100644
--- a/log4j-core/src/test/resources/log4j-rolling-direct.xml
+++ b/log4j-core/src/test/resources/log4j-rolling-direct.xml
@@ -28,7 +28,7 @@
</Console>
<RollingFile name="RollingFile" filePattern="${logDir}/test1-%d{MM-dd-yy-HH-mm}-%i.log.gz">
<PatternLayout>
- <Pattern>%d %p %C{1.} [%t] %m%n</Pattern>
+ <Pattern>%d{MM-dd-yy-HH-mm} %p %C{1.} [%t] %m%n</Pattern>
</PatternLayout>
<Policies>
<TimeBasedTriggeringPolicy />
diff --git a/log4j-core/src/test/resources/log4j-rolling-numbered-gz.xml b/log4j-core/src/test/resources/log4j-rolling-numbered-gz.xml
new file mode 100644
index 0000000..f7aeb13
--- /dev/null
+++ b/log4j-core/src/test/resources/log4j-rolling-numbered-gz.xml
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="UTF-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 "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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="XMLConfigTest">
+ <Properties>
+ <Property name="filename">target/rolling1/rolling0-0.log</Property>
+ </Properties>
+ <ThresholdFilter level="debug"/>
+
+ <Appenders>
+ <Console name="STDOUT">
+ <PatternLayout pattern="%m%n"/>
+ </Console>
+ <RollingFile name="RollingFile" fileName="${filename}"
+ filePattern="target/rolling1/rolling0-%i.log.gz">
+ <PatternLayout>
+ <Pattern>%d %p %C{1.} [%t] %m%n</Pattern>
+ </PatternLayout>
+ <SizeBasedTriggeringPolicy size="500" />
+ <DefaultRolloverStrategy max="7" min="1" fileIndex="min"/>
+ </RollingFile>
+ <List name="List">
+ <ThresholdFilter level="error"/>
+ </List>
+ </Appenders>
+
+ <Loggers>
+ <Logger name="org.apache.logging.log4j.test1" level="debug" additivity="false">
+ <ThreadContextMapFilter>
+ <KeyValuePair key="test" value="123"/>
+ </ThreadContextMapFilter>
+ <AppenderRef ref="STDOUT"/>
+ </Logger>>
+
+ <Logger name="org.apache.logging.log4j.core.appender.rolling" level="debug" additivity="false">
+ <AppenderRef ref="RollingFile"/>
+ </Logger>>
+
+ <Root level="error">
+ <AppenderRef ref="STDOUT"/>
+ </Root>
+ </Loggers>
+
+</Configuration>
\ No newline at end of file
diff --git a/log4j-core/src/test/resources/log4j-rolling-restart.xml b/log4j-core/src/test/resources/log4j-rolling-restart.xml
new file mode 100644
index 0000000..eb65582
--- /dev/null
+++ b/log4j-core/src/test/resources/log4j-rolling-restart.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-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 "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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="ERROR" name="XMLConfigTest">
+ <Properties>
+ <Property name="filename">target/rolling-restart/test.log</Property>
+ </Properties>
+
+ <Appenders>
+ <RollingFile name="RollingFile" fileName="${filename}" filePattern="target/rolling-restart/test1-%d{MM-dd-yy}.log.gz" createOnDemand="true">
+ <PatternLayout>
+ <Pattern>%d %p %C{1.} [%t] %m%n</Pattern>
+ </PatternLayout>
+ <TimeBasedTriggeringPolicy interval="1" modulate="true"/>
+ </RollingFile>
+ </Appenders>
+
+ <Loggers>
+ <Root level="info">
+ <AppenderRef ref="RollingFile"/>
+ </Root>
+ </Loggers>
+
+</Configuration>
\ No newline at end of file
diff --git a/log4j-core/src/test/resources/log4j-rolling-size-with-time.xml b/log4j-core/src/test/resources/log4j-rolling-size-with-time.xml
new file mode 100644
index 0000000..a006627
--- /dev/null
+++ b/log4j-core/src/test/resources/log4j-rolling-size-with-time.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-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 "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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="XMLConfigTest">
+ <Properties>
+ <Property name="filename">target/rolling-size-test/rolling.log</Property>
+ </Properties>
+ <ThresholdFilter level="debug"/>
+
+ <Appenders>
+ <RollingFile name="RollingFile" fileName="${filename}"
+ filePattern="target/rolling-size-test/rollingtest-%d{yyyy-MM-DD'T'HH-mm-ss-SSS}.log">
+ <PatternLayout>
+ <Pattern>%m%n</Pattern>
+ </PatternLayout>
+ <SizeBasedTriggeringPolicy size="5000" />
+ <DefaultRolloverStrategy max="500"/>
+ </RollingFile>
+ </Appenders>
+
+ <Loggers>
+
+ <Root level="debug">
+ <AppenderRef ref="RollingFile"/>
+ </Root>
+ </Loggers>
+
+</Configuration>
\ No newline at end of file
diff --git a/log4j-core/src/test/resources/log4j-rolling-with-padding.xml b/log4j-core/src/test/resources/log4j-rolling-with-padding.xml
new file mode 100644
index 0000000..4bb8d20
--- /dev/null
+++ b/log4j-core/src/test/resources/log4j-rolling-with-padding.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="UTF-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 "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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="RollingWithPadding">
+ <Properties>
+ <Property name="base">target/rolling-with-padding/</Property>
+ </Properties>
+
+ <Appenders>
+ <RollingFile name="RollingFile" fileName="${base}/rollingtest.log" filePattern="${base}/test-%03i.log">
+ <PatternLayout>
+ <Pattern>%d %p %c{1.} [%t] %m%n</Pattern>
+ </PatternLayout>
+ <Policies>
+ <SizeBasedTriggeringPolicy size="30" />
+ </Policies>
+ <DefaultRolloverStrategy max="5" />
+ </RollingFile>
+ </Appenders>
+
+ <Loggers>
+ <Root level="trace">
+ <AppenderRef ref="RollingFile" />
+ </Root>
+ </Loggers>
+
+</Configuration>
diff --git a/log4j-core/src/test/resources/log4j-rolling3.xml b/log4j-core/src/test/resources/log4j-rolling3.xml
index a4b0e71..56f6058 100644
--- a/log4j-core/src/test/resources/log4j-rolling3.xml
+++ b/log4j-core/src/test/resources/log4j-rolling3.xml
@@ -16,7 +16,7 @@
limitations under the License.
-->
-<Configuration status="OFF" name="XMLConfigTest">
+<Configuration status="ERROR" name="XMLConfigTest">
<Properties>
<Property name="filename">target/rolling3/rollingtest.log</Property>
</Properties>
@@ -26,14 +26,13 @@
<Console name="STDOUT">
<PatternLayout pattern="%m%n"/>
</Console>
- <RollingFile name="RollingFile" fileName="${filename}" filePattern="target/rolling3/test/test1-%d{MM-dd-yy-HH-mm}-%i.log.gz">
- <PatternLayout>
- <Pattern>%d %p %C{1.} [%t] %m%n</Pattern>
- </PatternLayout>
+ <RollingFile name="RollingFile" fileName="${filename}" filePattern="target/rolling3/test/rollingfile_%d{yyyy-MM-dd-HH-mm-ss}_%i.log">
+ <PatternLayout pattern="%d{yyyy-MM-dd-HH-mm-ss} [%level] [%c] %msg%n"/>
<Policies>
<TimeBasedTriggeringPolicy />
<SizeBasedTriggeringPolicy size="500" />
</Policies>
+ <DefaultRolloverStrategy max="9"/>
</RollingFile>
<List name="List">
<ThresholdFilter level="error"/>
diff --git a/log4j-core/src/test/resources/log4j-routing-2767.xml b/log4j-core/src/test/resources/log4j-routing-2767.xml
new file mode 100644
index 0000000..34b9c30
--- /dev/null
+++ b/log4j-core/src/test/resources/log4j-routing-2767.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-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 "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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="RoutingTest">
+ <Properties>
+ <Property name="filename">target/routing1/routingtest-$${sd:type}.log</Property>
+ </Properties>
+
+ <Appenders>
+ <Console name="STDOUT">
+ <PatternLayout pattern="%m%n"/>
+ </Console>
+ <Routing name="Routing">
+ <Routes>
+ <Route>
+ <RollingFile name="Routing-${sd:type}" fileName="${filename}"
+ filePattern="target/routing1/test1-${sd:type}.%i.log.gz">
+ <PatternLayout>
+ <Pattern>%d %p %C{1.} [%t] %m%n</Pattern>
+ </PatternLayout>
+ <SizeBasedTriggeringPolicy size="500" />
+ </RollingFile>
+ </Route>
+ </Routes>
+ </Routing>
+ </Appenders>
+
+ <Loggers>
+ <Logger name="EventLogger" level="info" additivity="false">
+ <AppenderRef ref="Routing"/>
+ </Logger>
+
+ <Root level="info">
+ <AppenderRef ref="Routing"/>
+ </Root>
+ </Loggers>
+
+</Configuration>
\ No newline at end of file
diff --git a/log4j-core/src/test/resources/log4j-routing-purge.xml b/log4j-core/src/test/resources/log4j-routing-purge.xml
index 0efc296..d1de288 100644
--- a/log4j-core/src/test/resources/log4j-routing-purge.xml
+++ b/log4j-core/src/test/resources/log4j-routing-purge.xml
@@ -30,6 +30,7 @@
<List name="List">
<ThresholdFilter level="debug"/>
</List>
+ <List name="ReferencedList"/>
<Routing name="RoutingPurgeIdle">
<Routes pattern="$${sd:id}">
<Route>
@@ -39,15 +40,17 @@
</PatternLayout>
</File>
</Route>
+ <Route ref="ReferencedList" key="2"/>
</Routes>
<IdlePurgePolicy timeToLive="2" timeUnit="seconds" />
</Routing>
-
+
<Routing name="RoutingPurgeIdleWithHangingAppender">
<Routes pattern="$${sd:id}">
<Route>
<Hanging name="Routing-${sd:id}" shutdownDelay="10000"/>
</Route>
+ <Route ref="ReferencedList" key="2"/>
</Routes>
<IdlePurgePolicy timeToLive="2" timeUnit="seconds" />
</Routing>
@@ -61,6 +64,7 @@
</PatternLayout>
</File>
</Route>
+ <Route ref="ReferencedList" key="2"/>
</Routes>
</Routing>
</Appenders>
diff --git a/log4j-core/src/test/resources/log4j-script-filters.xml b/log4j-core/src/test/resources/log4j-script-filters.xml
index 85d81d1..e2b38e0 100644
--- a/log4j-core/src/test/resources/log4j-script-filters.xml
+++ b/log4j-core/src/test/resources/log4j-script-filters.xml
@@ -29,7 +29,7 @@
var result = false;
if (logEvent.getMarker() != null && logEvent.getMarker().isInstanceOf("FLOW")) {
result = true;
- } else if (logEvent.getContextMap().containsKey("UserId")) {
+ } else if (logEvent.getContextData().containsKey("UserId")) {
result = true;
}
result;
@@ -42,7 +42,7 @@
<AppenderRef ref="List">
<ScriptFilter onMatch="ACCEPT" onMisMatch="DENY">
<Script name="GroovyFilter" language="groovy"><![CDATA[
- logEvent.marker?.isInstanceOf('FLOW') || logEvent.contextMap.containsKey('UserId')
+ logEvent.marker?.isInstanceOf('FLOW') || logEvent.contextData.containsKey('UserId')
]]>
</Script>
</ScriptFilter>
diff --git a/log4j-core/src/test/resources/log4j-socket.xml b/log4j-core/src/test/resources/log4j-socket.xml
index a437071..b511fa6 100644
--- a/log4j-core/src/test/resources/log4j-socket.xml
+++ b/log4j-core/src/test/resources/log4j-socket.xml
@@ -15,11 +15,11 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
-<Configuration status="OFF" name="MyApp">
+<Configuration status="ERROR" name="MyApp">
<Appenders>
<Socket name="socket" host="localhost" port="5514" protocol="TCP" ignoreExceptions="false"
- reconnectionDelay="100">
- <BasicLayout />
+ reconnectionDelay="100" immediateFlush="true">
+ <PatternLayout pattern="%m%n"/>
</Socket>
</Appenders>
<Loggers>
diff --git a/log4j-core/src/test/resources/log4j2-2134.yml b/log4j-core/src/test/resources/log4j2-2134.yml
new file mode 100644
index 0000000..8254f0f
--- /dev/null
+++ b/log4j-core/src/test/resources/log4j2-2134.yml
@@ -0,0 +1,13 @@
+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"
\ No newline at end of file
diff --git a/log4j-core/src/test/resources/logback-test.xml b/log4j-core/src/test/resources/logback-test.xml
new file mode 100644
index 0000000..a7885c9
--- /dev/null
+++ b/log4j-core/src/test/resources/logback-test.xml
@@ -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>
+ <appender name="TestLogfile" class="ch.qos.logback.core.FileAppender">
+ <file>target/testlogback.log</file>
+ <append>false</append>
+ <encoder>
+ <immediateFlush>false</immediateFlush>
+ <Pattern>%d %5p [%t] %c{0} %X{transactionId} - %m%n</Pattern>
+ </encoder>
+ </appender>
+
+ <root level="debug">
+ <appender-ref ref="TestLogfile" />
+ </root>
+</configuration>
diff --git a/log4j-core/src/test/resources/scripts/filter.groovy b/log4j-core/src/test/resources/scripts/filter.groovy
index 2beac2f..ef6998b 100644
--- a/log4j-core/src/test/resources/scripts/filter.groovy
+++ b/log4j-core/src/test/resources/scripts/filter.groovy
@@ -1 +1 @@
-return logEvent.marker?.isInstanceOf('FLOW') || logEvent.contextMap.containsKey('UserId')
+return logEvent.marker?.isInstanceOf('FLOW') || logEvent.contextData.containsKey('UserId')
diff --git a/log4j-core/src/test/resources/scripts/filter.js b/log4j-core/src/test/resources/scripts/filter.js
index 3619d5f..5d14aaf 100644
--- a/log4j-core/src/test/resources/scripts/filter.js
+++ b/log4j-core/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.getContextData().containsKey("UserId")) {
+ result = true;
+}
+result;
diff --git a/log4j-couchdb/pom.xml b/log4j-couchdb/pom.xml
index 72b0505..ebce220 100644
--- a/log4j-couchdb/pom.xml
+++ b/log4j-couchdb/pom.xml
@@ -129,6 +129,7 @@
project -->
<detectOfflineLinks>false</detectOfflineLinks>
<linksource>true</linksource>
+ <source>8</source>
</configuration>
<reportSets>
<reportSet>
diff --git a/log4j-couchdb/src/main/java/org/apache/logging/log4j/couchdb/CouchDbProvider.java b/log4j-couchdb/src/main/java/org/apache/logging/log4j/couchdb/CouchDbProvider.java
index 2b233a8..6eb207e 100644
--- a/log4j-couchdb/src/main/java/org/apache/logging/log4j/couchdb/CouchDbProvider.java
+++ b/log4j-couchdb/src/main/java/org/apache/logging/log4j/couchdb/CouchDbProvider.java
@@ -19,16 +19,16 @@
import java.lang.reflect.Method;
import org.apache.logging.log4j.Logger;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
-import org.apache.logging.log4j.core.config.plugins.PluginFactory;
-import org.apache.logging.log4j.core.config.plugins.convert.TypeConverters;
-import org.apache.logging.log4j.core.config.plugins.validation.constraints.ValidHost;
-import org.apache.logging.log4j.core.config.plugins.validation.constraints.ValidPort;
-import org.apache.logging.log4j.core.util.NameUtil;
import org.apache.logging.log4j.core.appender.nosql.NoSqlProvider;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginAttribute;
+import org.apache.logging.log4j.plugins.PluginFactory;
+import org.apache.logging.log4j.plugins.convert.TypeConverters;
+import org.apache.logging.log4j.plugins.validation.constraints.ValidHost;
+import org.apache.logging.log4j.plugins.validation.constraints.ValidPort;
import org.apache.logging.log4j.status.StatusLogger;
import org.apache.logging.log4j.util.LoaderUtil;
+import org.apache.logging.log4j.util.NameUtil;
import org.apache.logging.log4j.util.Strings;
import org.lightcouch.CouchDbClient;
import org.lightcouch.CouchDbProperties;
@@ -83,14 +83,14 @@
*/
@PluginFactory
public static CouchDbProvider createNoSqlProvider(
- @PluginAttribute("databaseName") final String databaseName,
- @PluginAttribute("protocol") String protocol,
- @PluginAttribute(value = "server", defaultString = "localhost") @ValidHost final String server,
- @PluginAttribute(value = "port", defaultString = "0") @ValidPort final String port,
- @PluginAttribute("username") final String username,
- @PluginAttribute(value = "password", sensitive = true) final String password,
- @PluginAttribute("factoryClassName") final String factoryClassName,
- @PluginAttribute("factoryMethodName") final String factoryMethodName) {
+ @PluginAttribute final String databaseName,
+ @PluginAttribute String protocol,
+ @PluginAttribute(defaultString = "localhost") @ValidHost final String server,
+ @PluginAttribute(defaultString = "0") @ValidPort final String port,
+ @PluginAttribute final String username,
+ @PluginAttribute(sensitive = true) final String password,
+ @PluginAttribute final String factoryClassName,
+ @PluginAttribute final String factoryMethodName) {
CouchDbClient client;
String description;
if (Strings.isNotEmpty(factoryClassName) && Strings.isNotEmpty(factoryMethodName)) {
diff --git a/log4j-csv/pom.xml b/log4j-csv/pom.xml
index 69396cf..71dcacb 100644
--- a/log4j-csv/pom.xml
+++ b/log4j-csv/pom.xml
@@ -122,6 +122,7 @@
<!-- module link generation is completely broken in the javadoc plugin for a multi-module non-aggregating project -->
<detectOfflineLinks>false</detectOfflineLinks>
<linksource>true</linksource>
+ <source>8</source>
</configuration>
<reportSets>
<reportSet>
diff --git a/log4j-csv/src/main/java/org/apache/logging/log4j/csv/layout/CsvLogEventLayout.java b/log4j-csv/src/main/java/org/apache/logging/log4j/csv/layout/CsvLogEventLayout.java
index 123e5a6..ce195ed 100644
--- a/log4j-csv/src/main/java/org/apache/logging/log4j/csv/layout/CsvLogEventLayout.java
+++ b/log4j-csv/src/main/java/org/apache/logging/log4j/csv/layout/CsvLogEventLayout.java
@@ -24,11 +24,11 @@
import org.apache.logging.log4j.core.Layout;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.config.Configuration;
-import org.apache.logging.log4j.core.config.Node;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
+import org.apache.logging.log4j.plugins.Node;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginAttribute;
import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
-import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+import org.apache.logging.log4j.plugins.PluginFactory;
import org.apache.logging.log4j.status.StatusLogger;
/**
@@ -53,16 +53,16 @@
public static CsvLogEventLayout createLayout(
// @formatter:off
@PluginConfiguration final Configuration config,
- @PluginAttribute(value = "format", defaultString = DEFAULT_FORMAT) final String format,
- @PluginAttribute("delimiter") final Character delimiter,
- @PluginAttribute("escape") final Character escape,
- @PluginAttribute("quote") final Character quote,
- @PluginAttribute("quoteMode") final QuoteMode quoteMode,
- @PluginAttribute("nullString") final String nullString,
- @PluginAttribute("recordSeparator") final String recordSeparator,
- @PluginAttribute(value = "charset", defaultString = DEFAULT_CHARSET) final Charset charset,
- @PluginAttribute("header") final String header,
- @PluginAttribute("footer") final String footer)
+ @PluginAttribute(defaultString = DEFAULT_FORMAT) final String format,
+ @PluginAttribute final Character delimiter,
+ @PluginAttribute final Character escape,
+ @PluginAttribute final Character quote,
+ @PluginAttribute final QuoteMode quoteMode,
+ @PluginAttribute final String nullString,
+ @PluginAttribute final String recordSeparator,
+ @PluginAttribute(defaultString = DEFAULT_CHARSET) final Charset charset,
+ @PluginAttribute final String header,
+ @PluginAttribute final String footer)
// @formatter:on
{
diff --git a/log4j-csv/src/main/java/org/apache/logging/log4j/csv/layout/CsvParameterLayout.java b/log4j-csv/src/main/java/org/apache/logging/log4j/csv/layout/CsvParameterLayout.java
index 075c65a..816271a 100644
--- a/log4j-csv/src/main/java/org/apache/logging/log4j/csv/layout/CsvParameterLayout.java
+++ b/log4j-csv/src/main/java/org/apache/logging/log4j/csv/layout/CsvParameterLayout.java
@@ -24,11 +24,11 @@
import org.apache.logging.log4j.core.Layout;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.config.Configuration;
-import org.apache.logging.log4j.core.config.Node;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
+import org.apache.logging.log4j.plugins.Node;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginAttribute;
import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
-import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+import org.apache.logging.log4j.plugins.PluginFactory;
import org.apache.logging.log4j.message.Message;
import org.apache.logging.log4j.status.StatusLogger;
@@ -62,16 +62,16 @@
public static AbstractCsvLayout createLayout(
// @formatter:off
@PluginConfiguration final Configuration config,
- @PluginAttribute(value = "format", defaultString = DEFAULT_FORMAT) final String format,
- @PluginAttribute("delimiter") final Character delimiter,
- @PluginAttribute("escape") final Character escape,
- @PluginAttribute("quote") final Character quote,
- @PluginAttribute("quoteMode") final QuoteMode quoteMode,
- @PluginAttribute("nullString") final String nullString,
- @PluginAttribute("recordSeparator") final String recordSeparator,
- @PluginAttribute(value = "charset", defaultString = DEFAULT_CHARSET) final Charset charset,
- @PluginAttribute("header") final String header,
- @PluginAttribute("footer") final String footer)
+ @PluginAttribute(defaultString = DEFAULT_FORMAT) final String format,
+ @PluginAttribute final Character delimiter,
+ @PluginAttribute final Character escape,
+ @PluginAttribute final Character quote,
+ @PluginAttribute final QuoteMode quoteMode,
+ @PluginAttribute final String nullString,
+ @PluginAttribute final String recordSeparator,
+ @PluginAttribute(defaultString = DEFAULT_CHARSET) final Charset charset,
+ @PluginAttribute final String header,
+ @PluginAttribute final String footer)
// @formatter:on
{
diff --git a/log4j-csv/src/site/manual/index.md b/log4j-csv/src/site/manual/index.md
index 66fe24f..84fca13 100644
--- a/log4j-csv/src/site/manual/index.md
+++ b/log4j-csv/src/site/manual/index.md
@@ -18,7 +18,7 @@
# Apache Log4j ZeroMQ using JeroMQ module
-As of Log4j 2.11.0, ZeroMQ using JeroMQ support has moved from the existing module logj-core to the new module log4j-jeromq.
+As of Log4j 2.11.0, ZeroMQ using JeroMQ support has moved from the existing module log4j-core to the new module log4j-jeromq.
## Requirements
diff --git a/log4j-distribution/pom.xml b/log4j-distribution/pom.xml
index 3107f22..291ccae 100644
--- a/log4j-distribution/pom.xml
+++ b/log4j-distribution/pom.xml
@@ -46,6 +46,24 @@
<version>${project.version}</version>
<classifier>javadoc</classifier>
</dependency>
+ <!-- log4j-plugins -->
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-plugins</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-plugins</artifactId>
+ <version>${project.version}</version>
+ <classifier>sources</classifier>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-plugins</artifactId>
+ <version>${project.version}</version>
+ <classifier>javadoc</classifier>
+ </dependency>
<!-- log4j-core -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
@@ -519,6 +537,40 @@
<version>${project.version}</version>
<classifier>javadoc</classifier>
</dependency>
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-docker</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-docker</artifactId>
+ <version>${project.version}</version>
+ <classifier>sources</classifier>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-docker</artifactId>
+ <version>${project.version}</version>
+ <classifier>javadoc</classifier>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-spring-cloud-config-client</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-spring-cloud-config-client</artifactId>
+ <version>${project.version}</version>
+ <classifier>sources</classifier>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-spring-cloud-config-client</artifactId>
+ <version>${project.version}</version>
+ <classifier>javadoc</classifier>
+ </dependency>
</dependencies>
<build>
<plugins>
diff --git a/log4j-docker/pom.xml b/log4j-docker/pom.xml
new file mode 100644
index 0000000..f99340d
--- /dev/null
+++ b/log4j-docker/pom.xml
@@ -0,0 +1,215 @@
+<?xml version="1.0" encoding="UTF-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 "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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 xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j</artifactId>
+ <version>3.0.0-SNAPSHOT</version>
+ <relativePath>../</relativePath>
+ </parent>
+ <artifactId>log4j-docker</artifactId>
+ <packaging>jar</packaging>
+ <name>Apache Log4j Docker Library</name>
+ <description>Apache Log4j Docker Support</description>
+ <properties>
+ <log4jParentDir>${basedir}/..</log4jParentDir>
+ <docLabel>Log4j Docker Library Documentation</docLabel>
+ <projectDir>/docker</projectDir>
+ <maven.compiler.source>1.8</maven.compiler.source>
+ <maven.compiler.target>1.8</maven.compiler.target>
+ <module.name>org.apache.logging.log4j.docker</module.name>
+ </properties>
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-core</artifactId>
+ </dependency>
+ <!-- Required for JSON support -->
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-annotations</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-databind</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-toolchains-plugin</artifactId>
+ <version>1.1</version>
+ <executions>
+ <execution>
+ <goals>
+ <goal>toolchain</goal>
+ </goals>
+ </execution>
+ </executions>
+ <configuration>
+ <toolchains>
+ <jdk>
+ <version>[8, )</version>
+ </jdk>
+ </toolchains>
+ </configuration>
+ </plugin>
+ <!-- Include the standard NOTICE and LICENSE -->
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-remote-resources-plugin</artifactId>
+ <executions>
+ <execution>
+ <goals>
+ <goal>process</goal>
+ </goals>
+ <configuration>
+ <skip>false</skip>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ </plugin>
+ <plugin>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <forkCount>1</forkCount>
+ <reuseForks>false</reuseForks>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ <reporting>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-changes-plugin</artifactId>
+ <version>${changes.plugin.version}</version>
+ <reportSets>
+ <reportSet>
+ <reports>
+ <report>changes-report</report>
+ </reports>
+ </reportSet>
+ </reportSets>
+ <configuration>
+ <issueLinkTemplate>%URL%/show_bug.cgi?id=%ISSUE%</issueLinkTemplate>
+ <useJql>true</useJql>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-checkstyle-plugin</artifactId>
+ <version>${checkstyle.plugin.version}</version>
+ <configuration>
+ <!--<propertiesLocation>${vfs.parent.dir}/checkstyle.properties</propertiesLocation> -->
+ <configLocation>${log4jParentDir}/checkstyle.xml</configLocation>
+ <suppressionsLocation>${log4jParentDir}/checkstyle-suppressions.xml</suppressionsLocation>
+ <enableRulesSummary>false</enableRulesSummary>
+ <propertyExpansion>basedir=${basedir}</propertyExpansion>
+ <propertyExpansion>licensedir=${log4jParentDir}/checkstyle-header.txt</propertyExpansion>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-javadoc-plugin</artifactId>
+ <version>${javadoc.plugin.version}</version>
+ <configuration>
+ <bottom><![CDATA[<p align="center">Copyright © {inceptionYear}-{currentYear} {organizationName}. All Rights Reserved.<br />
+ Apache Logging, Apache Log4j, Log4j, Apache, the Apache feather logo, the Apache Logging project logo,
+ and the Apache Log4j logo are trademarks of The Apache Software Foundation.</p>]]></bottom>
+ <!-- module link generation is completely broken in the javadoc plugin for a multi-module non-aggregating
+ project -->
+ <detectOfflineLinks>false</detectOfflineLinks>
+ <linksource>true</linksource>
+ <source>8</source>
+ </configuration>
+ <reportSets>
+ <reportSet>
+ <id>non-aggregate</id>
+ <reports>
+ <report>javadoc</report>
+ </reports>
+ </reportSet>
+ </reportSets>
+ </plugin>
+ <plugin>
+ <groupId>com.github.spotbugs</groupId>
+ <artifactId>spotbugs-maven-plugin</artifactId>
+ <configuration>
+ <fork>true</fork>
+ <jvmArgs>-Duser.language=en</jvmArgs>
+ <threshold>Normal</threshold>
+ <effort>Default</effort>
+ <excludeFilterFile>${log4jParentDir}/spotbugs-exclude-filter.xml</excludeFilterFile>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jxr-plugin</artifactId>
+ <version>${jxr.plugin.version}</version>
+ <reportSets>
+ <reportSet>
+ <id>non-aggregate</id>
+ <reports>
+ <report>jxr</report>
+ </reports>
+ </reportSet>
+ <reportSet>
+ <id>aggregate</id>
+ <reports>
+ <report>aggregate</report>
+ </reports>
+ </reportSet>
+ </reportSets>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-pmd-plugin</artifactId>
+ <version>${pmd.plugin.version}</version>
+ <configuration>
+ <targetJdk>${maven.compiler.target}</targetJdk>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>net.sourceforge.maven-taglib</groupId>
+ <artifactId>maven-taglib-plugin</artifactId>
+ <version>2.4</version>
+ </plugin>
+ </plugins>
+ </reporting>
+</project>
+
diff --git a/log4j-docker/src/main/java/org/apache/logging/log4j/docker/DockerLookup.java b/log4j-docker/src/main/java/org/apache/logging/log4j/docker/DockerLookup.java
new file mode 100644
index 0000000..061376b
--- /dev/null
+++ b/log4j-docker/src/main/java/org/apache/logging/log4j/docker/DockerLookup.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.docker;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.core.lookup.AbstractLookup;
+import org.apache.logging.log4j.core.lookup.StrLookup;
+import org.apache.logging.log4j.core.util.NetUtils;
+import org.apache.logging.log4j.docker.model.Container;
+import org.apache.logging.log4j.docker.model.Network;
+import org.apache.logging.log4j.status.StatusLogger;
+import org.apache.logging.log4j.util.PropertiesUtil;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+/**
+ *
+ */
+@Plugin(name = "docker", category = StrLookup.CATEGORY)
+public class DockerLookup extends AbstractLookup {
+
+ private static final Logger LOGGER = StatusLogger.getLogger();
+ private static final String DOCKER_URI = "DOCKER_URI";
+ private static final String HTTP = "http";
+ private final Container container;
+
+ public DockerLookup() {
+ String baseUri = System.getenv(DOCKER_URI);
+ if (baseUri == null) {
+ PropertiesUtil props = PropertiesUtil.getProperties();
+ baseUri = props.getStringProperty(DOCKER_URI);
+ }
+ if (baseUri == null) {
+ LOGGER.warn("No Docker URI provided. Docker information is unavailable");
+ container = null;
+ return;
+ }
+ Container current = null;
+ try {
+ URL url= new URL(baseUri + "/containers/json");
+ String hostName = NetUtils.getLocalHostname();
+ String macAddr = NetUtils.getMacAddressString();
+
+ if (url.getProtocol().equals(HTTP)) {
+ ObjectMapper objectMapper = new ObjectMapper();
+ List<Container> containerList = objectMapper.readValue(url, new TypeReference<List<Container>>(){});
+
+ for (Container container : containerList) {
+ if (macAddr != null && container.getNetworkSettings() != null) {
+ Map<String, Network> networks = container.getNetworkSettings().getNetworks();
+ if (networks != null) {
+ for (Network network: networks.values()) {
+ if (macAddr.equals(network.getMacAddress())) {
+ current = container;
+ break;
+ }
+ }
+ }
+ }
+ if (current != null) {
+ break;
+ }
+ }
+ }
+ if (current == null) {
+ LOGGER.warn("Unable to determine current container");
+ }
+ } catch (IOException ioe) {
+ LOGGER.warn("Unable to read container information: " + ioe.getMessage());
+ }
+ container = current;
+ }
+
+ @Override
+ public String lookup(LogEvent event, String key) {
+ if (container == null) {
+ return null;
+ }
+ switch (key) {
+ case "shortContainerId": {
+ return container.getId().substring(0, 12);
+ }
+ case "containerId": {
+ return container.getId();
+ }
+ case "containerName": {
+ if (container.getNames().size() > 1) {
+ return container.getNames().toString();
+ }
+ return container.getNames().get(0);
+ }
+ case "shortImageId": {
+ return container.getImageId().substring(0, 12);
+ }
+ case "imageId": {
+ return container.getImageId();
+ }
+ case "imageName": {
+ return container.getImage();
+ }
+ default:
+ return null;
+ }
+ }
+}
diff --git a/log4j-docker/src/main/java/org/apache/logging/log4j/docker/model/Container.java b/log4j-docker/src/main/java/org/apache/logging/log4j/docker/model/Container.java
new file mode 100644
index 0000000..aee7c51
--- /dev/null
+++ b/log4j-docker/src/main/java/org/apache/logging/log4j/docker/model/Container.java
@@ -0,0 +1,176 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.logging.log4j.docker.model;
+
+import java.util.List;
+import java.util.Map;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * Definition of a Docker Container
+ */
+public class Container {
+ @JsonProperty("Id")
+ private String id;
+
+ @JsonProperty("Names")
+ private List<String> names;
+
+ @JsonProperty("Path")
+ private String path;
+
+ @JsonProperty("Args")
+ private String[] args;
+
+ @JsonProperty("Image")
+ private String image;
+
+ @JsonProperty("ImageID")
+ private String imageId;
+
+ @JsonProperty("Command")
+ private String command;
+
+ @JsonProperty("Created")
+ private Long created;
+
+ @JsonProperty("Ports")
+ private List<PortDefinition> ports;
+
+ @JsonProperty("Labels")
+ private Map<String, String> labels;
+
+ @JsonProperty("State")
+ private String state;
+
+ @JsonProperty("Status")
+ private String status;
+
+ @JsonProperty("HostConfig")
+ private HostConfig hostConfig;
+
+ @JsonProperty("NetworkSettings")
+ private NetworkSettings networkSettings;
+
+ @JsonProperty("Mounts")
+ private List<Mount> mounts;
+
+ public String getId() {
+ return id;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public List<String> getNames() {
+ return names;
+ }
+
+ public void setNames(List<String> names) {
+ this.names = names;
+ }
+
+ public String getImage() {
+ return image;
+ }
+
+ public void setImage(String image) {
+ this.image = image;
+ }
+
+ public String getImageId() {
+ return imageId;
+ }
+
+ public void setImageId(String imageId) {
+ this.imageId = imageId;
+ }
+
+ public String getCommand() {
+ return command;
+ }
+
+ public void setCommand(String command) {
+ this.command = command;
+ }
+
+ public Long getCreated() {
+ return created;
+ }
+
+ public void setCreated(Long created) {
+ this.created = created;
+ }
+
+ public List<PortDefinition> getPorts() {
+ return ports;
+ }
+
+ public void setPorts(List<PortDefinition> ports) {
+ this.ports = ports;
+ }
+
+ public Map<String, String> getLabels() {
+ return labels;
+ }
+
+ public void setLabels(Map<String, String> labels) {
+ this.labels = labels;
+ }
+
+ public String getState() {
+ return state;
+ }
+
+ public void setState(String state) {
+ this.state = state;
+ }
+
+ public String getStatus() {
+ return status;
+ }
+
+ public void setStatus(String status) {
+ this.status = status;
+ }
+
+ public HostConfig getHostConfig() {
+ return hostConfig;
+ }
+
+ public void setHostConfig(HostConfig hostConfig) {
+ this.hostConfig = hostConfig;
+ }
+
+ public NetworkSettings getNetworkSettings() {
+ return networkSettings;
+ }
+
+ public void setNetworkSettings(NetworkSettings networkSettings) {
+ this.networkSettings = networkSettings;
+ }
+
+ public List<Mount> getMounts() {
+ return mounts;
+ }
+
+ public void setMounts(List<Mount> mounts) {
+ this.mounts = mounts;
+ }
+}
diff --git a/log4j-docker/src/main/java/org/apache/logging/log4j/docker/model/HostConfig.java b/log4j-docker/src/main/java/org/apache/logging/log4j/docker/model/HostConfig.java
new file mode 100644
index 0000000..f32c474
--- /dev/null
+++ b/log4j-docker/src/main/java/org/apache/logging/log4j/docker/model/HostConfig.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.docker.model;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ *
+ */
+public class HostConfig {
+
+ @JsonProperty("NetworkMode")
+ private String networkMode;
+}
diff --git a/log4j-docker/src/main/java/org/apache/logging/log4j/docker/model/IPAMConfig.java b/log4j-docker/src/main/java/org/apache/logging/log4j/docker/model/IPAMConfig.java
new file mode 100644
index 0000000..b17ad92
--- /dev/null
+++ b/log4j-docker/src/main/java/org/apache/logging/log4j/docker/model/IPAMConfig.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.docker.model;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ *
+ */
+public class IPAMConfig {
+
+ @JsonProperty("Subnet")
+ private String subnet;
+
+ @JsonProperty("IPRange")
+ private String ipRange;
+
+ @JsonProperty("Gateway")
+ private String gateway;
+
+ @JsonProperty("IPv4Address")
+ private String ipv4Address;
+
+ public String getSubnet() {
+ return subnet;
+ }
+
+ public void setSubnet(String subnet) {
+ this.subnet = subnet;
+ }
+
+ public String getIpRange() {
+ return ipRange;
+ }
+
+ public void setIpRange(String ipRange) {
+ this.ipRange = ipRange;
+ }
+
+ public String getGateway() {
+ return gateway;
+ }
+
+ public void setGateway(String gateway) {
+ this.gateway = gateway;
+ }
+
+ public String getIpv4Address() {
+ return ipv4Address;
+ }
+
+ public void setIpv4Address(String ipv4Address) {
+ this.ipv4Address = ipv4Address;
+ }
+}
diff --git a/log4j-docker/src/main/java/org/apache/logging/log4j/docker/model/Mount.java b/log4j-docker/src/main/java/org/apache/logging/log4j/docker/model/Mount.java
new file mode 100644
index 0000000..1c6ab46
--- /dev/null
+++ b/log4j-docker/src/main/java/org/apache/logging/log4j/docker/model/Mount.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.docker.model;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ *
+ */
+public class Mount {
+
+ @JsonProperty("Type")
+ private String type;
+
+ @JsonProperty("Name")
+ private String name;
+
+ @JsonProperty("Source")
+ private String source;
+
+ @JsonProperty("Destination")
+ private String destination;
+
+ @JsonProperty("Driver")
+ private String driver;
+
+ @JsonProperty("Mode")
+ private String mode;
+
+ @JsonProperty("RW")
+ private Boolean readWrite;
+
+ @JsonProperty("Propagation")
+ private String propagation;
+
+ public String getType() {
+ return type;
+ }
+
+ public void setType(String type) {
+ this.type = type;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public String getSource() {
+ return source;
+ }
+
+ public void setSource(String source) {
+ this.source = source;
+ }
+
+ public String getDestination() {
+ return destination;
+ }
+
+ public void setDestination(String destination) {
+ this.destination = destination;
+ }
+
+ public String getDriver() {
+ return driver;
+ }
+
+ public void setDriver(String driver) {
+ this.driver = driver;
+ }
+
+ public String getMode() {
+ return mode;
+ }
+
+ public void setMode(String mode) {
+ this.mode = mode;
+ }
+
+ public Boolean getReadWrite() {
+ return readWrite;
+ }
+
+ public void setReadWrite(Boolean readWrite) {
+ this.readWrite = readWrite;
+ }
+
+ public String getPropagation() {
+ return propagation;
+ }
+
+ public void setPropagation(String propagation) {
+ this.propagation = propagation;
+ }
+}
diff --git a/log4j-docker/src/main/java/org/apache/logging/log4j/docker/model/Network.java b/log4j-docker/src/main/java/org/apache/logging/log4j/docker/model/Network.java
new file mode 100644
index 0000000..5e0674a
--- /dev/null
+++ b/log4j-docker/src/main/java/org/apache/logging/log4j/docker/model/Network.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.logging.log4j.docker.model;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ *
+ */
+public class Network {
+
+ @JsonProperty("IPAMConfig")
+ private IPAMConfig ipamConfig;
+
+ @JsonProperty("Links")
+ private String links;
+
+ @JsonProperty("Aliases")
+ private String[] aliases;
+
+ @JsonProperty("NetworkID")
+ private String networkId;
+
+ @JsonProperty("EndpointID")
+ private String endpointId;
+
+ @JsonProperty("Gateway")
+ private String gateway;
+
+ @JsonProperty("IPAddress")
+ private String ipAddress;
+
+ @JsonProperty("IPPrefixLen")
+ private Integer ipPrefixLen;
+
+ @JsonProperty("IPv6Gateway")
+ private String ipv6Gateway;
+
+ @JsonProperty("GlobalIPv6Address")
+ private String globalIPv6Address;
+
+ @JsonProperty("GlobalIPv6PrefixLen")
+ private Integer globalIPv6PrefixLen;
+
+ @JsonProperty("MacAddress")
+ private String macAddress;
+
+ @JsonProperty("DriverOpts")
+ private String driverOpts;
+
+ public IPAMConfig getIpamConfig() {
+ return ipamConfig;
+ }
+
+ public void setIpamConfig(IPAMConfig ipamConfig) {
+ this.ipamConfig = ipamConfig;
+ }
+
+ public String getLinks() {
+ return links;
+ }
+
+ public void setLinks(String links) {
+ this.links = links;
+ }
+
+ public String[] getAliases() {
+ return aliases;
+ }
+
+ public void setAliases(String[] aliases) {
+ this.aliases = aliases;
+ }
+
+ public String getNetworkId() {
+ return networkId;
+ }
+
+ public void setNetworkId(String networkId) {
+ this.networkId = networkId;
+ }
+
+ public String getEndpointId() {
+ return endpointId;
+ }
+
+ public void setEndpointId(String endpointId) {
+ this.endpointId = endpointId;
+ }
+
+ public String getGateway() {
+ return gateway;
+ }
+
+ public void setGateway(String gateway) {
+ this.gateway = gateway;
+ }
+
+ public String getIpAddress() {
+ return ipAddress;
+ }
+
+ public void setIpAddress(String ipAddress) {
+ this.ipAddress = ipAddress;
+ }
+
+ public Integer getIpPrefixLen() {
+ return ipPrefixLen;
+ }
+
+ public void setIpPrefixLen(Integer ipPrefixLen) {
+ this.ipPrefixLen = ipPrefixLen;
+ }
+
+ public String getIpv6Gateway() {
+ return ipv6Gateway;
+ }
+
+ public void setIpv6Gateway(String ipv6Gateway) {
+ this.ipv6Gateway = ipv6Gateway;
+ }
+
+ public String getGlobalIPv6Address() {
+ return globalIPv6Address;
+ }
+
+ public void setGlobalIPv6Address(String globalIPv6Address) {
+ this.globalIPv6Address = globalIPv6Address;
+ }
+
+ public Integer getGlobalIPv6PrefixLen() {
+ return globalIPv6PrefixLen;
+ }
+
+ public void setGlobalIPv6PrefixLen(Integer globalIPv6PrefixLen) {
+ this.globalIPv6PrefixLen = globalIPv6PrefixLen;
+ }
+
+ public String getMacAddress() {
+ return macAddress;
+ }
+
+ public void setMacAddress(String macAddress) {
+ this.macAddress = macAddress;
+ }
+
+ public String getDriverOpts() {
+ return driverOpts;
+ }
+
+ public void setDriverOpts(String driverOpts) {
+ this.driverOpts = driverOpts;
+ }
+}
diff --git a/log4j-docker/src/main/java/org/apache/logging/log4j/docker/model/NetworkSettings.java b/log4j-docker/src/main/java/org/apache/logging/log4j/docker/model/NetworkSettings.java
new file mode 100644
index 0000000..a1835d6
--- /dev/null
+++ b/log4j-docker/src/main/java/org/apache/logging/log4j/docker/model/NetworkSettings.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.docker.model;
+
+import java.util.Map;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ *
+ */
+public class NetworkSettings {
+
+ @JsonProperty("Networks")
+ private Map<String, Network> networks;
+
+ @JsonProperty("Bridge")
+ private String bridge;
+
+ @JsonProperty("SandboxID")
+ private String sandboxId;
+
+ @JsonProperty("HairpinMode")
+ private boolean hairpinMode;
+
+ @JsonProperty("LinkLocalIPv6Address")
+ private String linkLocalIPv6Address;
+
+ @JsonProperty("LinkLocalIPv6PrefixLen")
+ private int linkLocalIPv6PrefixLen;
+
+ @JsonProperty("Ports")
+ private Map<String, String> ports;
+
+ @JsonProperty("SandboxKey")
+ private String sandboxKey;
+
+ @JsonProperty("SecondaryIPAddresses")
+ private String secondaryIPaddresses;
+
+ @JsonProperty("EndpointID")
+ private String endpointId;
+
+ @JsonProperty("Gateway")
+ private String gateway;
+
+ @JsonProperty("GlobalIPv6Address")
+ private String globalIPv6Address;
+
+ @JsonProperty("GlobalIPv6PrefixLen")
+ private int globalIPv6PrefixLen;
+
+ @JsonProperty("IPAddress")
+ private String ipAddress;
+
+ @JsonProperty("IPPrefixLen")
+ private int ipPrefixLen;
+
+ @JsonProperty("IPv6Gateway")
+ private String ipv6Gateway;
+
+ @JsonProperty("MacAddress")
+ private String macAddress;
+
+
+ public Map<String, Network> getNetworks() {
+ return networks;
+ }
+
+ public void setNetworks(Map<String, Network> networks) {
+ this.networks = networks;
+ }
+
+ public String getBridge() {
+ return bridge;
+ }
+
+ public void setBridge(String bridge) {
+ this.bridge = bridge;
+ }
+
+ public String getSandboxId() {
+ return sandboxId;
+ }
+
+ public void setSandboxId(String sandboxId) {
+ this.sandboxId = sandboxId;
+ }
+
+ public boolean isHairpinMode() {
+ return hairpinMode;
+ }
+
+ public void setHairpinMode(boolean hairpinMode) {
+ this.hairpinMode = hairpinMode;
+ }
+
+ public String getLinkLocalIPv6Address() {
+ return linkLocalIPv6Address;
+ }
+
+ public void setLinkLocalIPv6Address(String linkLocalIPv6Address) {
+ this.linkLocalIPv6Address = linkLocalIPv6Address;
+ }
+
+ public int getLinkLocalIPv6PrefixLen() {
+ return linkLocalIPv6PrefixLen;
+ }
+
+ public void setLinkLocalIPv6PrefixLen(int linkLocalIPv6PrefixLen) {
+ this.linkLocalIPv6PrefixLen = linkLocalIPv6PrefixLen;
+ }
+
+ public Map<String, String> getPorts() {
+ return ports;
+ }
+
+ public void setPorts(Map<String, String> ports) {
+ this.ports = ports;
+ }
+
+ public String getSandboxKey() {
+ return sandboxKey;
+ }
+
+ public void setSandboxKey(String sandboxKey) {
+ this.sandboxKey = sandboxKey;
+ }
+
+ public String getSecondaryIPaddresses() {
+ return secondaryIPaddresses;
+ }
+
+ public void setSecondaryIPaddresses(String secondaryIPaddresses) {
+ this.secondaryIPaddresses = secondaryIPaddresses;
+ }
+
+ public String getEndpointId() {
+ return endpointId;
+ }
+
+ public void setEndpointId(String endpointId) {
+ this.endpointId = endpointId;
+ }
+
+ public String getGateway() {
+ return gateway;
+ }
+
+ public void setGateway(String gateway) {
+ this.gateway = gateway;
+ }
+
+ public String getGlobalIPv6Address() {
+ return globalIPv6Address;
+ }
+
+ public void setGlobalIPv6Address(String globalIPv6Address) {
+ this.globalIPv6Address = globalIPv6Address;
+ }
+
+ public int getGlobalIPv6PrefixLen() {
+ return globalIPv6PrefixLen;
+ }
+
+ public void setGlobalIPv6PrefixLen(int globalIPv6PrefixLen) {
+ this.globalIPv6PrefixLen = globalIPv6PrefixLen;
+ }
+
+ public String getIpAddress() {
+ return ipAddress;
+ }
+
+ public void setIpAddress(String ipAddress) {
+ this.ipAddress = ipAddress;
+ }
+
+ public int getIpPrefixLen() {
+ return ipPrefixLen;
+ }
+
+ public void setIpPrefixLen(int ipPrefixLen) {
+ this.ipPrefixLen = ipPrefixLen;
+ }
+
+ public String getIpv6Gateway() {
+ return ipv6Gateway;
+ }
+
+ public void setIpv6Gateway(String ipv6Gateway) {
+ this.ipv6Gateway = ipv6Gateway;
+ }
+
+ public String getMacAddress() {
+ return macAddress;
+ }
+
+ public void setMacAddress(String macAddress) {
+ this.macAddress = macAddress;
+ }
+}
diff --git a/log4j-docker/src/main/java/org/apache/logging/log4j/docker/model/PortDefinition.java b/log4j-docker/src/main/java/org/apache/logging/log4j/docker/model/PortDefinition.java
new file mode 100644
index 0000000..66f6de9
--- /dev/null
+++ b/log4j-docker/src/main/java/org/apache/logging/log4j/docker/model/PortDefinition.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.docker.model;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ *
+ */
+public class PortDefinition {
+
+ @JsonProperty("IP")
+ private String ip;
+
+ @JsonProperty("PrivatePort")
+ private Integer privatePort;
+
+ @JsonProperty("PublicPort")
+ private Integer publicPort;
+
+ @JsonProperty("Type")
+ private String type;
+
+ public String getIp() {
+ return ip;
+ }
+
+ public void setIp(String ip) {
+ this.ip = ip;
+ }
+
+ public Integer getPrivatePort() {
+ return privatePort;
+ }
+
+ public void setPrivatePort(Integer privatePort) {
+ this.privatePort = privatePort;
+ }
+
+ public Integer getPublicPort() {
+ return publicPort;
+ }
+
+ public void setPublicPort(Integer publicPort) {
+ this.publicPort = publicPort;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public void setType(String type) {
+ this.type = type;
+ }
+}
diff --git a/log4j-docker/src/site/markdown/index.md.vm b/log4j-docker/src/site/markdown/index.md.vm
new file mode 100644
index 0000000..969e8e5
--- /dev/null
+++ b/log4j-docker/src/site/markdown/index.md.vm
@@ -0,0 +1,56 @@
+<!-- vim: set syn=markdown : -->
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+#set($dollar = '$')
+#set($h1='#')
+#set($h2='##')
+
+$h1 Log4j Docker Support
+
+Log4j supports Docker by providing a Lookup to retrieve container information.
+
+$h2 Accessing Docker
+
+The Log4j Docker support requires access to the Docker REST interface. In practical terms this means the
+application either needs access to unix:///var/run/docker.sock through a volume mount (not recommended),
+bind Docker to another host/port or unix socket. or use a proxy application to provide access. The
+[Log4j Spring Cloud sample application](https://github.com/apache/logging-log4j2/tree/master/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application)
+uses a socat proxy to access Docker.
+
+$h2 Lookup Attributes
+
+Log4j Docker provides access to the following container attributes:
+
+* containerId - The full id assigned to the container.
+* containerName - The name assigned to the container.
+* imageId - The id assigned to the image.
+* imageName - The name assigned to the image.
+* shortContainerId - The first 12 characters of the container id.
+* shortImageId - The first 12 characters of the image id.
+
+#set( $D = '${' )
+#set( $container = 'docker:containerId}')
+Attributes may be accessed by adding
+```
+$D$container
+```
+to the configuration. Note that docker variables are only resolved once during logging initializaton so they
+shouldn't be referenced with more than one '$' character.
+
+$h2 Requirements
+Log4j Docker requires Log4j Core, Log4j API and a minimum of Java 8.
+For more information, see [Runtime Dependencies](../runtime-dependencies.html).
diff --git a/log4j-docker/src/site/site.xml b/log4j-docker/src/site/site.xml
new file mode 100644
index 0000000..7322f3b
--- /dev/null
+++ b/log4j-docker/src/site/site.xml
@@ -0,0 +1,52 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+-->
+<project name="Log4j Docker Support"
+ xmlns="http://maven.apache.org/DECORATION/1.4.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/DECORATION/1.4.0 http://maven.apache.org/xsd/decoration-1.4.0.xsd">
+ <body>
+ <links>
+ <item name="Apache" href="http://www.apache.org/" />
+ <item name="Logging Services" href="http://logging.apache.org/"/>
+ <item name="Log4j" href="../index.html"/>
+ </links>
+
+ <!-- Component-specific reports -->
+ <menu ref="reports"/>
+
+ <!-- Overall Project Info -->
+ <menu name="Log4j Project Information" img="icon-info-sign">
+ <item name="Dependencies" href="../dependencies.html" />
+ <item name="Dependency Convergence" href="../dependency-convergence.html" />
+ <item name="Dependency Management" href="../dependency-management.html" />
+ <item name="Project Team" href="../team-list.html" />
+ <item name="Mailing Lists" href="../mail-lists.html" />
+ <item name="Issue Tracking" href="../issue-tracking.html" />
+ <item name="Project License" href="../license.html" />
+ <item name="Source Repository" href="../source-repository.html" />
+ <item name="Project Summary" href="../project-summary.html" />
+ </menu>
+
+ <menu name="Log4j Project Reports" img="icon-cog">
+ <item name="Changes Report" href="../changes-report.html" />
+ <item name="JIRA Report" href="../jira-report.html" />
+ <item name="Surefire Report" href="../surefire-report.html" />
+ <item name="RAT Report" href="../rat-report.html" />
+ </menu>
+ </body>
+</project>
diff --git a/log4j-flume-ng/pom.xml b/log4j-flume-ng/pom.xml
index 64eb0e9..ac331ee 100644
--- a/log4j-flume-ng/pom.xml
+++ b/log4j-flume-ng/pom.xml
@@ -200,6 +200,7 @@
project -->
<detectOfflineLinks>false</detectOfflineLinks>
<linksource>true</linksource>
+ <source>8</source>
</configuration>
<reportSets>
<reportSet>
diff --git a/log4j-flume-ng/revapi.json b/log4j-flume-ng/revapi.json
new file mode 100644
index 0000000..04bbf7a
--- /dev/null
+++ b/log4j-flume-ng/revapi.json
@@ -0,0 +1,12 @@
+[
+ {
+ "extension": "revapi.ignore",
+ "configuration": [
+ {
+ "code": "java.method.removed",
+ "old": "method java.util.Map<java.lang.String, java.lang.String> org.apache.logging.log4j.flume.appender.FlumeEvent::getContextMap()",
+ "justification": "Remove deprecated code"
+ }
+ ]
+ }
+]
diff --git a/log4j-flume-ng/src/main/java/org/apache/logging/log4j/flume/appender/Agent.java b/log4j-flume-ng/src/main/java/org/apache/logging/log4j/flume/appender/Agent.java
index e8cf2ea..0018c04 100644
--- a/log4j-flume-ng/src/main/java/org/apache/logging/log4j/flume/appender/Agent.java
+++ b/log4j-flume-ng/src/main/java/org/apache/logging/log4j/flume/appender/Agent.java
@@ -17,9 +17,9 @@
package org.apache.logging.log4j.flume.appender;
import org.apache.logging.log4j.Logger;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
-import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginAttribute;
+import org.apache.logging.log4j.plugins.PluginFactory;
import org.apache.logging.log4j.core.util.Integers;
import org.apache.logging.log4j.status.StatusLogger;
diff --git a/log4j-flume-ng/src/main/java/org/apache/logging/log4j/flume/appender/BatchEvent.java b/log4j-flume-ng/src/main/java/org/apache/logging/log4j/flume/appender/BatchEvent.java
index 41af4fa..9a6f86d 100644
--- a/log4j-flume-ng/src/main/java/org/apache/logging/log4j/flume/appender/BatchEvent.java
+++ b/log4j-flume-ng/src/main/java/org/apache/logging/log4j/flume/appender/BatchEvent.java
@@ -35,4 +35,8 @@
public List<Event> getEvents() {
return events;
}
+
+ public int size() {
+ return events.size();
+ }
}
diff --git a/log4j-flume-ng/src/main/java/org/apache/logging/log4j/flume/appender/FlumeAppender.java b/log4j-flume-ng/src/main/java/org/apache/logging/log4j/flume/appender/FlumeAppender.java
index 4524a26..c2e7a2e 100644
--- a/log4j-flume-ng/src/main/java/org/apache/logging/log4j/flume/appender/FlumeAppender.java
+++ b/log4j-flume-ng/src/main/java/org/apache/logging/log4j/flume/appender/FlumeAppender.java
@@ -26,15 +26,16 @@
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.PluginAliases;
-import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
-import org.apache.logging.log4j.core.config.plugins.PluginElement;
-import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginAliases;
+import org.apache.logging.log4j.plugins.PluginAttribute;
+import org.apache.logging.log4j.plugins.PluginElement;
+import org.apache.logging.log4j.plugins.PluginFactory;
import org.apache.logging.log4j.core.layout.Rfc5424Layout;
import org.apache.logging.log4j.core.net.Facility;
import org.apache.logging.log4j.core.util.Booleans;
import org.apache.logging.log4j.core.util.Integers;
+import org.apache.logging.log4j.util.Timer;
/**
* An Appender that uses the Avro protocol to route events to Flume.
@@ -61,6 +62,9 @@
private final FlumeEventFactory factory;
+ private final Timer timer = new Timer("FlumeEvent", 5000);
+ private volatile long count;
+
/**
* Which Manager will be used by the appender instance.
*/
@@ -73,10 +77,10 @@
}
private FlumeAppender(final String name, final Filter filter, final Layout<? extends Serializable> layout,
- final boolean ignoreExceptions, final String includes, final String excludes,
- final String required, final String mdcPrefix, final String eventPrefix,
- final boolean compress, final FlumeEventFactory factory, final AbstractFlumeManager manager) {
- super(name, filter, layout, ignoreExceptions);
+ final boolean ignoreExceptions, final String includes, final String excludes, final String required,
+ final String mdcPrefix, final String eventPrefix, final boolean compress, final FlumeEventFactory factory,
+ final Property[] properties, final AbstractFlumeManager manager) {
+ super(name, filter, layout, ignoreExceptions, properties);
this.manager = manager;
this.mdcIncludes = includes;
this.mdcExcludes = excludes;
@@ -101,12 +105,27 @@
}
}
}
+ timer.startOrResume();
final FlumeEvent flumeEvent = factory.createEvent(event, mdcIncludes, mdcExcludes, mdcRequired, mdcPrefix,
eventPrefix, compressBody);
flumeEvent.setBody(getLayout().toByteArray(flumeEvent));
+ if (update()) {
+ String msg = timer.stop();
+ LOGGER.debug(msg);
+ } else {
+ timer.pause();
+ }
manager.send(flumeEvent);
}
+ private synchronized boolean update() {
+ if (++count == 5000) {
+ count = 0;
+ return true;
+ }
+ return false;
+ }
+
@Override
public boolean stop(final long timeout, final TimeUnit timeUnit) {
setStopping();
@@ -166,32 +185,32 @@
* @return A Flume Avro Appender.
*/
@PluginFactory
- public static FlumeAppender createAppender(@PluginElement("Agents") final Agent[] agents,
- @PluginElement("Properties") final Property[] properties,
- @PluginAttribute("hosts") final String hosts,
- @PluginAttribute("embedded") final String embedded,
- @PluginAttribute("type") final String type,
- @PluginAttribute("dataDir") final String dataDir,
+ public static FlumeAppender createAppender(@PluginElement final Agent[] agents,
+ @PluginElement final Property[] properties,
+ @PluginAttribute final String hosts,
+ @PluginAttribute final String embedded,
+ @PluginAttribute final String type,
+ @PluginAttribute final String dataDir,
@PluginAliases("connectTimeout")
@PluginAttribute("connectTimeoutMillis") final String connectionTimeoutMillis,
@PluginAliases("requestTimeout")
- @PluginAttribute("requestTimeoutMillis") final String requestTimeoutMillis,
- @PluginAttribute("agentRetries") final String agentRetries,
+ @PluginAttribute final String requestTimeoutMillis,
+ @PluginAttribute final String agentRetries,
@PluginAliases("maxDelay") // deprecated
- @PluginAttribute("maxDelayMillis") final String maxDelayMillis,
- @PluginAttribute("name") final String name,
+ @PluginAttribute final String maxDelayMillis,
+ @PluginAttribute final String name,
@PluginAttribute("ignoreExceptions") final String ignore,
@PluginAttribute("mdcExcludes") final String excludes,
@PluginAttribute("mdcIncludes") final String includes,
@PluginAttribute("mdcRequired") final String required,
- @PluginAttribute("mdcPrefix") final String mdcPrefix,
- @PluginAttribute("eventPrefix") final String eventPrefix,
+ @PluginAttribute final String mdcPrefix,
+ @PluginAttribute final String eventPrefix,
@PluginAttribute("compress") final String compressBody,
- @PluginAttribute("batchSize") final String batchSize,
- @PluginAttribute("lockTimeoutRetries") final String lockTimeoutRetries,
- @PluginElement("FlumeEventFactory") final FlumeEventFactory factory,
- @PluginElement("Layout") Layout<? extends Serializable> layout,
- @PluginElement("Filter") final Filter filter) {
+ @PluginAttribute final String batchSize,
+ @PluginAttribute final String lockTimeoutRetries,
+ @PluginElement final FlumeEventFactory factory,
+ @PluginElement Layout<? extends Serializable> layout,
+ @PluginElement final Filter filter) {
final boolean embed = embedded != null ? Boolean.parseBoolean(embedded) :
(agents == null || agents.length == 0 || hosts == null || hosts.isEmpty()) && properties != null && properties.length > 0;
@@ -264,7 +283,7 @@
}
return new FlumeAppender(name, filter, layout, ignoreExceptions, includes,
- excludes, required, mdcPrefix, eventPrefix, compress, factory, manager);
+ excludes, required, mdcPrefix, eventPrefix, compress, factory, Property.EMPTY_ARRAY, manager);
}
private static Agent[] getAgents(Agent[] agents, final String hosts) {
diff --git a/log4j-flume-ng/src/main/java/org/apache/logging/log4j/flume/appender/FlumeAvroManager.java b/log4j-flume-ng/src/main/java/org/apache/logging/log4j/flume/appender/FlumeAvroManager.java
index c1dd288..1dac698 100644
--- a/log4j-flume-ng/src/main/java/org/apache/logging/log4j/flume/appender/FlumeAvroManager.java
+++ b/log4j-flume-ng/src/main/java/org/apache/logging/log4j/flume/appender/FlumeAvroManager.java
@@ -33,7 +33,7 @@
private static final int MAX_RECONNECTS = 3;
private static final int MINIMUM_TIMEOUT = 1000;
- private static AvroManagerFactory factory = new AvroManagerFactory();
+ private static final AvroManagerFactory factory = new AvroManagerFactory();
private final Agent[] agents;
@@ -50,7 +50,7 @@
private final int current = 0;
- private RpcClient rpcClient = null;
+ private volatile RpcClient rpcClient;
private BatchEvent batchEvent = new BatchEvent();
private long nextSend = 0;
@@ -97,9 +97,9 @@
if (batchSize <= 0) {
batchSize = 1;
- }
-
- final StringBuilder sb = new StringBuilder("FlumeAvro[");
+ };
+ final StringBuilder sb = new StringBuilder(name);
+ sb.append(" FlumeAvro[");
boolean first = true;
for (final Agent agent : agents) {
if (!first) {
@@ -149,9 +149,13 @@
return delayMillis;
}
- public synchronized void send(final BatchEvent events) {
+ public void send(final BatchEvent events) {
if (rpcClient == null) {
- rpcClient = connect(agents, retries, connectTimeoutMillis, requestTimeoutMillis);
+ synchronized (this) {
+ if (rpcClient == null) {
+ rpcClient = connect(agents, retries, connectTimeoutMillis, requestTimeoutMillis);
+ }
+ }
}
if (rpcClient != null) {
@@ -175,7 +179,7 @@
}
@Override
- public synchronized void send(final Event event) {
+ public void send(final Event event) {
if (batchSize == 1) {
if (rpcClient == null) {
rpcClient = connect(agents, retries, connectTimeoutMillis, requestTimeoutMillis);
@@ -199,14 +203,22 @@
throw new AppenderLoggingException("No Flume agents are available");
}
} else {
- batchEvent.addEvent(event);
- final int eventCount = batchEvent.getEvents().size();
- if (eventCount == 1) {
- nextSend = System.nanoTime() + delayNanos;
+ int eventCount;
+ BatchEvent batch = null;
+ synchronized(batchEvent) {
+ batchEvent.addEvent(event);
+ eventCount = batchEvent.size();
+ long now = System.nanoTime();
+ if (eventCount == 1) {
+ nextSend = now + delayNanos;
+ }
+ if (eventCount >= batchSize || now >= nextSend) {
+ batch = batchEvent;
+ batchEvent = new BatchEvent();
+ }
}
- if (eventCount >= batchSize || System.nanoTime() >= nextSend) {
- send(batchEvent);
- batchEvent = new BatchEvent();
+ if (batch != null) {
+ send(batch);
}
}
}
diff --git a/log4j-flume-ng/src/main/java/org/apache/logging/log4j/flume/appender/FlumeEmbeddedManager.java b/log4j-flume-ng/src/main/java/org/apache/logging/log4j/flume/appender/FlumeEmbeddedManager.java
index 7c6840a..7147a41 100644
--- a/log4j-flume-ng/src/main/java/org/apache/logging/log4j/flume/appender/FlumeEmbeddedManager.java
+++ b/log4j-flume-ng/src/main/java/org/apache/logging/log4j/flume/appender/FlumeEmbeddedManager.java
@@ -28,7 +28,7 @@
import org.apache.logging.log4j.core.appender.ManagerFactory;
import org.apache.logging.log4j.core.config.ConfigurationException;
import org.apache.logging.log4j.core.config.Property;
-import org.apache.logging.log4j.core.util.NameUtil;
+import org.apache.logging.log4j.util.NameUtil;
import org.apache.logging.log4j.util.PropertiesUtil;
import org.apache.logging.log4j.util.Strings;
@@ -41,7 +41,7 @@
private static final String IN_MEMORY = "InMemory";
- private static FlumeManagerFactory factory = new FlumeManagerFactory();
+ private static final FlumeManagerFactory factory = new FlumeManagerFactory();
private final EmbeddedAgent agent;
diff --git a/log4j-flume-ng/src/main/java/org/apache/logging/log4j/flume/appender/FlumeEvent.java b/log4j-flume-ng/src/main/java/org/apache/logging/log4j/flume/appender/FlumeEvent.java
index a471004..1e736f3 100644
--- a/log4j-flume-ng/src/main/java/org/apache/logging/log4j/flume/appender/FlumeEvent.java
+++ b/log4j-flume-ng/src/main/java/org/apache/logging/log4j/flume/appender/FlumeEvent.java
@@ -332,15 +332,6 @@
}
/**
- * Returns a copy of the context Map.
- * @return a copy of the context Map.
- */
- @Override
- public Map<String, String> getContextMap() {
- return contextMap;
- }
-
- /**
* Returns the context data of the {@code LogEvent} that this {@code FlumeEvent} was constructed with.
* @return the context data of the {@code LogEvent} that this {@code FlumeEvent} was constructed with.
*/
diff --git a/log4j-flume-ng/src/main/java/org/apache/logging/log4j/flume/appender/FlumePersistentManager.java b/log4j-flume-ng/src/main/java/org/apache/logging/log4j/flume/appender/FlumePersistentManager.java
index 76f9165..7a989c0 100644
--- a/log4j-flume-ng/src/main/java/org/apache/logging/log4j/flume/appender/FlumePersistentManager.java
+++ b/log4j-flume-ng/src/main/java/org/apache/logging/log4j/flume/appender/FlumePersistentManager.java
@@ -51,13 +51,13 @@
import org.apache.logging.log4j.LoggingException;
import org.apache.logging.log4j.core.appender.ManagerFactory;
import org.apache.logging.log4j.core.config.Property;
-import org.apache.logging.log4j.core.config.plugins.util.PluginManager;
-import org.apache.logging.log4j.core.config.plugins.util.PluginType;
import org.apache.logging.log4j.core.util.ExecutorServices;
import org.apache.logging.log4j.core.util.FileUtils;
import org.apache.logging.log4j.core.util.Log4jThread;
import org.apache.logging.log4j.core.util.Log4jThreadFactory;
import org.apache.logging.log4j.core.util.SecretKeyProvider;
+import org.apache.logging.log4j.plugins.util.PluginManager;
+import org.apache.logging.log4j.plugins.util.PluginType;
import org.apache.logging.log4j.util.Strings;
/**
@@ -76,7 +76,7 @@
private static final long LOCK_TIMEOUT_SLEEP_MILLIS = 500;
- private static BDBManagerFactory factory = new BDBManagerFactory();
+ private static final BDBManagerFactory factory = new BDBManagerFactory();
private final Database database;
@@ -469,7 +469,7 @@
* Thread that sends data to Flume and pulls it from Berkeley DB.
*/
private static class WriterThread extends Log4jThread {
- private volatile boolean shutdown = false;
+ private volatile boolean shutdown;
private final Database database;
private final Environment environment;
private final FlumePersistentManager manager;
diff --git a/log4j-flume-ng/src/main/java/org/apache/logging/log4j/flume/appender/Log4jEventSource.java b/log4j-flume-ng/src/main/java/org/apache/logging/log4j/flume/appender/Log4jEventSource.java
index d444db5..7dd573c 100644
--- a/log4j-flume-ng/src/main/java/org/apache/logging/log4j/flume/appender/Log4jEventSource.java
+++ b/log4j-flume-ng/src/main/java/org/apache/logging/log4j/flume/appender/Log4jEventSource.java
@@ -58,7 +58,7 @@
try {
getChannelProcessor().processEvent(event);
} catch (final ChannelException ex) {
- LOGGER.warn("Unabled to process event {}" + event, ex);
+ LOGGER.warn("Unable to process event {}" + event, ex);
throw ex;
}
sourceCounter.incrementAppendAcceptedCount();
diff --git a/log4j-flume-ng/src/test/java/org/apache/logging/log4j/flume/appender/FlumeEmbeddedAgentTest.java b/log4j-flume-ng/src/test/java/org/apache/logging/log4j/flume/appender/FlumeEmbeddedAgentTest.java
index 94c4926..1b314df 100644
--- a/log4j-flume-ng/src/test/java/org/apache/logging/log4j/flume/appender/FlumeEmbeddedAgentTest.java
+++ b/log4j-flume-ng/src/test/java/org/apache/logging/log4j/flume/appender/FlumeEmbeddedAgentTest.java
@@ -275,4 +275,4 @@
}
return stringMap;
}
-}
\ No newline at end of file
+}
diff --git a/log4j-flume-ng/src/test/java/org/apache/logging/log4j/flume/appender/FlumePersistentAppenderTest.java b/log4j-flume-ng/src/test/java/org/apache/logging/log4j/flume/appender/FlumePersistentAppenderTest.java
index bdf6d4f..49c0ce0 100644
--- a/log4j-flume-ng/src/test/java/org/apache/logging/log4j/flume/appender/FlumePersistentAppenderTest.java
+++ b/log4j-flume-ng/src/test/java/org/apache/logging/log4j/flume/appender/FlumePersistentAppenderTest.java
@@ -458,4 +458,4 @@
}
return stringMap;
}
-}
\ No newline at end of file
+}
diff --git a/log4j-flume-ng/src/test/java/org/apache/logging/log4j/flume/appender/FlumePersistentPerf.java b/log4j-flume-ng/src/test/java/org/apache/logging/log4j/flume/appender/FlumePersistentPerf.java
index ec4d7b0..4a3c723 100644
--- a/log4j-flume-ng/src/test/java/org/apache/logging/log4j/flume/appender/FlumePersistentPerf.java
+++ b/log4j-flume-ng/src/test/java/org/apache/logging/log4j/flume/appender/FlumePersistentPerf.java
@@ -216,4 +216,4 @@
}
return stringMap;
}
-}
\ No newline at end of file
+}
diff --git a/log4j-flume-ng/src/test/java/org/apache/logging/log4j/flume/test/FlumeKeyProvider.java b/log4j-flume-ng/src/test/java/org/apache/logging/log4j/flume/test/FlumeKeyProvider.java
index b4a8dbe..b288cdf 100644
--- a/log4j-flume-ng/src/test/java/org/apache/logging/log4j/flume/test/FlumeKeyProvider.java
+++ b/log4j-flume-ng/src/test/java/org/apache/logging/log4j/flume/test/FlumeKeyProvider.java
@@ -19,7 +19,7 @@
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.plugins.Plugin;
import org.apache.logging.log4j.core.util.SecretKeyProvider;
/**
diff --git a/log4j-iostreams/pom.xml b/log4j-iostreams/pom.xml
index fc41791..45231fd 100644
--- a/log4j-iostreams/pom.xml
+++ b/log4j-iostreams/pom.xml
@@ -145,6 +145,7 @@
project -->
<detectOfflineLinks>false</detectOfflineLinks>
<linksource>true</linksource>
+ <source>8</source>
</configuration>
<reportSets>
<reportSet>
diff --git a/log4j-iostreams/src/main/java/org/apache/logging/log4j/io/LoggerBufferedInputStream.java b/log4j-iostreams/src/main/java/org/apache/logging/log4j/io/LoggerBufferedInputStream.java
index 54d9796..6122b20 100644
--- a/log4j-iostreams/src/main/java/org/apache/logging/log4j/io/LoggerBufferedInputStream.java
+++ b/log4j-iostreams/src/main/java/org/apache/logging/log4j/io/LoggerBufferedInputStream.java
@@ -24,6 +24,8 @@
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.Marker;
+import org.apache.logging.log4j.io.internal.InternalBufferedInputStream;
+import org.apache.logging.log4j.io.internal.InternalInputStream;
import org.apache.logging.log4j.spi.ExtendedLogger;
/**
@@ -32,40 +34,43 @@
*/
public class LoggerBufferedInputStream extends BufferedInputStream {
private static final String FQCN = LoggerBufferedInputStream.class.getName();
+ private final InternalBufferedInputStream stream;
protected LoggerBufferedInputStream(final InputStream in, final Charset charset, final ExtendedLogger logger,
final String fqcn, final Level level, final Marker marker) {
- super(new LoggerInputStream(in, charset, logger, fqcn == null ? FQCN : fqcn, level, marker));
+ super(in);
+ stream = new InternalBufferedInputStream(in, charset, logger, fqcn == null ? FQCN: fqcn, level, marker);
}
protected LoggerBufferedInputStream(final InputStream in, final Charset charset, final int size,
final ExtendedLogger logger, final String fqcn, final Level level,
final Marker marker) {
- super(new LoggerInputStream(in, charset, logger, fqcn == null ? FQCN : fqcn, level, marker), size);
+ super(in);
+ stream = new InternalBufferedInputStream(in, charset, size, logger, fqcn == null ? FQCN: fqcn, level, marker);
}
@Override
public void close() throws IOException {
- super.close();
+ stream.close();
}
@Override
public synchronized int read() throws IOException {
- return super.read();
+ return stream.read();
}
@Override
public int read(final byte[] b) throws IOException {
- return super.read(b, 0, b.length);
+ return stream.read(b);
}
@Override
public synchronized int read(final byte[] b, final int off, final int len) throws IOException {
- return super.read(b, off, len);
+ return stream.read(b, off, len);
}
@Override
public String toString() {
- return LoggerBufferedInputStream.class.getSimpleName() + "{stream=" + this.in + '}';
+ return LoggerBufferedInputStream.class.getSimpleName() + stream.toString();
}
}
diff --git a/log4j-iostreams/src/main/java/org/apache/logging/log4j/io/LoggerBufferedReader.java b/log4j-iostreams/src/main/java/org/apache/logging/log4j/io/LoggerBufferedReader.java
index 584e61a..a981ca5 100644
--- a/log4j-iostreams/src/main/java/org/apache/logging/log4j/io/LoggerBufferedReader.java
+++ b/log4j-iostreams/src/main/java/org/apache/logging/log4j/io/LoggerBufferedReader.java
@@ -24,6 +24,7 @@
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.Marker;
+import org.apache.logging.log4j.io.internal.InternalBufferedReader;
import org.apache.logging.log4j.spi.ExtendedLogger;
/**
@@ -32,50 +33,47 @@
*/
public class LoggerBufferedReader extends BufferedReader {
private static final String FQCN = LoggerBufferedReader.class.getName();
+ private final InternalBufferedReader reader;
protected LoggerBufferedReader(final Reader reader, final ExtendedLogger logger, final String fqcn,
final Level level, final Marker marker) {
- super(new LoggerReader(reader, logger, fqcn == null ? FQCN : fqcn, level, marker));
+ super(reader);
+ this.reader = new InternalBufferedReader(reader, logger, fqcn == null ? FQCN: fqcn, level, marker);
}
protected LoggerBufferedReader(final Reader reader, final int size, final ExtendedLogger logger, final String fqcn,
final Level level, final Marker marker) {
- super(new LoggerReader(reader, logger, fqcn == null ? FQCN : fqcn, level, marker), size);
+ super(reader);
+ this.reader = new InternalBufferedReader(reader, size, logger, fqcn == null ? FQCN : fqcn, level, marker);
}
@Override
public void close() throws IOException {
- super.close();
+ reader.close();
}
@Override
public int read() throws IOException {
- return super.read();
+ return reader.read();
}
@Override
public int read(final char[] cbuf) throws IOException {
- return super.read(cbuf, 0, cbuf.length);
+ return reader.read(cbuf);
}
@Override
public int read(final char[] cbuf, final int off, final int len) throws IOException {
- return super.read(cbuf, off, len);
+ return reader.read(cbuf, off, len);
}
@Override
public int read(final CharBuffer target) throws IOException {
- final int len = target.remaining();
- final char[] cbuf = new char[len];
- final int charsRead = read(cbuf, 0, len);
- if (charsRead > 0) {
- target.put(cbuf, 0, charsRead);
- }
- return charsRead;
+ return reader.read(target);
}
@Override
public String readLine() throws IOException {
- return super.readLine();
+ return reader.readLine();
}
}
diff --git a/log4j-iostreams/src/main/java/org/apache/logging/log4j/io/LoggerFilterOutputStream.java b/log4j-iostreams/src/main/java/org/apache/logging/log4j/io/LoggerFilterOutputStream.java
index b608502..a103233 100644
--- a/log4j-iostreams/src/main/java/org/apache/logging/log4j/io/LoggerFilterOutputStream.java
+++ b/log4j-iostreams/src/main/java/org/apache/logging/log4j/io/LoggerFilterOutputStream.java
@@ -24,6 +24,7 @@
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.Marker;
+import org.apache.logging.log4j.io.internal.InternalFilterOutputStream;
import org.apache.logging.log4j.spi.ExtendedLogger;
/**
@@ -37,47 +38,42 @@
public class LoggerFilterOutputStream extends FilterOutputStream {
private static final String FQCN = LoggerFilterOutputStream.class.getName();
- private final ByteStreamLogger logger;
- private final String fqcn;
+ private final InternalFilterOutputStream logger;
protected LoggerFilterOutputStream(final OutputStream out, final Charset charset, final ExtendedLogger logger,
final String fqcn, final Level level, final Marker marker) {
super(out);
- this.logger = new ByteStreamLogger(logger, level, marker, charset);
- this.fqcn = fqcn == null ? FQCN : fqcn;
+ this.logger = new InternalFilterOutputStream(out, charset, logger, fqcn == null ? FQCN : fqcn,
+ level, marker);
}
@Override
public void close() throws IOException {
- this.out.close();
- this.logger.close(this.fqcn);
+ this.logger.close();
}
@Override
public void flush() throws IOException {
- this.out.flush();
+ this.logger.flush();
}
@Override
public String toString() {
- return LoggerFilterOutputStream.class.getSimpleName() + "{stream=" + this.out + '}';
+ return LoggerFilterOutputStream.class.getSimpleName() + logger.toString();
}
@Override
public void write(final byte[] b) throws IOException {
- this.out.write(b);
- this.logger.put(this.fqcn, b, 0, b.length);
+ this.logger.write(b);
}
@Override
public void write(final byte[] b, final int off, final int len) throws IOException {
- this.out.write(b, off, len);
- this.logger.put(this.fqcn, b, off, len);
+ this.logger.write(b, off, len);
}
@Override
public void write(final int b) throws IOException {
- this.out.write(b);
- this.logger.put(this.fqcn, (byte) (b & 0xFF));
+ this.logger.write(b);
}
}
diff --git a/log4j-iostreams/src/main/java/org/apache/logging/log4j/io/LoggerFilterWriter.java b/log4j-iostreams/src/main/java/org/apache/logging/log4j/io/LoggerFilterWriter.java
index 4b0991f..682e831 100644
--- a/log4j-iostreams/src/main/java/org/apache/logging/log4j/io/LoggerFilterWriter.java
+++ b/log4j-iostreams/src/main/java/org/apache/logging/log4j/io/LoggerFilterWriter.java
@@ -23,6 +23,7 @@
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.Marker;
+import org.apache.logging.log4j.io.internal.InternalFilterWriter;
import org.apache.logging.log4j.spi.ExtendedLogger;
/**
@@ -34,59 +35,51 @@
public class LoggerFilterWriter extends FilterWriter {
private static final String FQCN = LoggerFilterWriter.class.getName();
- private final CharStreamLogger logger;
- private final String fqcn;
+ private final InternalFilterWriter logger;
protected LoggerFilterWriter(final Writer out, final ExtendedLogger logger, final String fqcn, final Level level,
final Marker marker) {
super(out);
- this.logger = new CharStreamLogger(logger, level, marker);
- this.fqcn = fqcn == null ? FQCN : fqcn;
+ this.logger = new InternalFilterWriter(out, logger, fqcn == null ? FQCN : fqcn, level, marker);
}
@Override
public void close() throws IOException {
- this.out.close();
- this.logger.close(this.fqcn);
+ this.logger.close();
}
@Override
public void flush() throws IOException {
- this.out.flush();
+ this.logger.flush();
}
@Override
public String toString() {
- return LoggerFilterWriter.class.getSimpleName() + "{writer=" + this.out + '}';
+ return LoggerFilterWriter.class.getSimpleName() + logger.toString();
}
@Override
public void write(final char[] cbuf) throws IOException {
- this.out.write(cbuf);
- this.logger.put(this.fqcn, cbuf, 0, cbuf.length);
+ this.logger.write(cbuf);
}
@Override
public void write(final char[] cbuf, final int off, final int len) throws IOException {
- this.out.write(cbuf, off, len);
- this.logger.put(this.fqcn, cbuf, off, len);
+ this.logger.write(cbuf, off, len);
}
@Override
public void write(final int c) throws IOException {
- this.out.write(c);
- this.logger.put(this.fqcn, (char) c);
+ this.logger.write(c);
}
@Override
public void write(final String str) throws IOException {
- this.out.write(str);
- this.logger.put(this.fqcn, str, 0, str.length());
+ this.logger.write(str);
}
@Override
public void write(final String str, final int off, final int len) throws IOException {
- this.out.write(str, off, len);
- this.logger.put(this.fqcn, str, off, len);
+ this.logger.write(str, off, len);
}
}
diff --git a/log4j-iostreams/src/main/java/org/apache/logging/log4j/io/LoggerInputStream.java b/log4j-iostreams/src/main/java/org/apache/logging/log4j/io/LoggerInputStream.java
index 5366f19..8da122b 100644
--- a/log4j-iostreams/src/main/java/org/apache/logging/log4j/io/LoggerInputStream.java
+++ b/log4j-iostreams/src/main/java/org/apache/logging/log4j/io/LoggerInputStream.java
@@ -24,6 +24,7 @@
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.Marker;
+import org.apache.logging.log4j.io.internal.InternalInputStream;
import org.apache.logging.log4j.spi.ExtendedLogger;
/**
@@ -34,43 +35,36 @@
public class LoggerInputStream extends FilterInputStream {
private static final String FQCN = LoggerInputStream.class.getName();
- private final String fqcn;
- private final ByteStreamLogger logger;
+ private final InternalInputStream logger;
protected LoggerInputStream(final InputStream in, final Charset charset, final ExtendedLogger logger,
final String fqcn, final Level level, final Marker marker) {
super(in);
- this.logger = new ByteStreamLogger(logger, level, marker, charset);
- this.fqcn = fqcn == null ? FQCN : fqcn;
+ this.logger = new InternalInputStream(in, charset, logger, fqcn == null ? FQCN : fqcn, level, marker);
}
@Override
public void close() throws IOException {
- this.logger.close(this.fqcn);
- super.close();
+ this.logger.close();
}
@Override
public int read() throws IOException {
- final int b = super.read();
- this.logger.put(this.fqcn, b);
- return b;
+ return logger.read();
}
@Override
public int read(final byte[] b) throws IOException {
- return read(b, 0, b.length);
+ return logger.read(b);
}
@Override
public int read(final byte[] b, final int off, final int len) throws IOException {
- final int bytesRead = super.read(b, off, len);
- this.logger.put(this.fqcn, b, off, bytesRead);
- return bytesRead;
+ return logger.read(b, off, len);
}
@Override
public String toString() {
- return LoggerInputStream.class.getSimpleName() + "{stream=" + this.in + '}';
+ return LoggerInputStream.class.getSimpleName() + logger.toString();
}
}
diff --git a/log4j-iostreams/src/main/java/org/apache/logging/log4j/io/LoggerOutputStream.java b/log4j-iostreams/src/main/java/org/apache/logging/log4j/io/LoggerOutputStream.java
index b8ea392..e1bd658 100644
--- a/log4j-iostreams/src/main/java/org/apache/logging/log4j/io/LoggerOutputStream.java
+++ b/log4j-iostreams/src/main/java/org/apache/logging/log4j/io/LoggerOutputStream.java
@@ -23,6 +23,7 @@
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.Marker;
+import org.apache.logging.log4j.io.internal.InternalOutputStream;
import org.apache.logging.log4j.spi.ExtendedLogger;
/**
@@ -36,18 +37,16 @@
public class LoggerOutputStream extends OutputStream {
private static final String FQCN = LoggerOutputStream.class.getName();
- private final ByteStreamLogger logger;
- private final String fqcn;
+ private final InternalOutputStream logger;
protected LoggerOutputStream(final ExtendedLogger logger, final Level level, final Marker marker,
final Charset charset, final String fqcn) {
- this.logger = new ByteStreamLogger(logger, level, marker, charset);
- this.fqcn = fqcn == null ? FQCN : fqcn;
+ this.logger = new InternalOutputStream(logger, level, marker, charset, fqcn == null ? FQCN : fqcn);
}
@Override
public void close() throws IOException {
- this.logger.close(this.fqcn);
+ this.logger.close();
}
@Override
@@ -57,16 +56,16 @@
@Override
public void write(final byte[] b) throws IOException {
- this.logger.put(this.fqcn, b, 0, b.length);
+ logger.write(b);
}
@Override
public void write(final byte[] b, final int off, final int len) throws IOException {
- this.logger.put(this.fqcn, b, off, len);
+ logger.write(b, off, len);
}
@Override
public void write(final int b) throws IOException {
- this.logger.put(this.fqcn, (byte) (b & 0xFF));
+ logger.write(b);
}
}
diff --git a/log4j-iostreams/src/main/java/org/apache/logging/log4j/io/LoggerPrintStream.java b/log4j-iostreams/src/main/java/org/apache/logging/log4j/io/LoggerPrintStream.java
index 30bac34..0489119 100644
--- a/log4j-iostreams/src/main/java/org/apache/logging/log4j/io/LoggerPrintStream.java
+++ b/log4j-iostreams/src/main/java/org/apache/logging/log4j/io/LoggerPrintStream.java
@@ -17,6 +17,7 @@
package org.apache.logging.log4j.io;
+import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
@@ -26,6 +27,7 @@
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.Marker;
+import org.apache.logging.log4j.io.internal.InternalPrintStream;
import org.apache.logging.log4j.spi.ExtendedLogger;
/**
@@ -38,19 +40,20 @@
*/
public class LoggerPrintStream extends PrintStream {
private static final String FQCN = LoggerPrintStream.class.getName();
+ private final InternalPrintStream psLogger;
protected LoggerPrintStream(final ExtendedLogger logger, final boolean autoFlush, final Charset charset,
final String fqcn, final Level level, final Marker marker)
throws UnsupportedEncodingException {
- super(new LoggerOutputStream(logger, level, marker, ensureNonNull(charset), fqcn == null ? FQCN : fqcn),
- autoFlush, ensureNonNull(charset).name());
+ super(new PrintStream(new ByteArrayOutputStream()));
+ psLogger = new InternalPrintStream(logger, autoFlush, charset, fqcn == null ? FQCN : fqcn, level, marker);
}
protected LoggerPrintStream(final OutputStream out, final boolean autoFlush, final Charset charset,
final ExtendedLogger logger, final String fqcn, final Level level, final Marker marker)
throws UnsupportedEncodingException {
- super(new LoggerFilterOutputStream(out, ensureNonNull(charset), logger, fqcn == null ? FQCN : fqcn, level,
- marker), autoFlush, ensureNonNull(charset).name());
+ super(new PrintStream(out));
+ psLogger = new InternalPrintStream(out, autoFlush, charset, logger, fqcn == null ? FQCN : fqcn, level, marker);
}
private static Charset ensureNonNull(final Charset charset) {
@@ -59,173 +62,173 @@
@Override
public LoggerPrintStream append(final char c) {
- super.append(c);
+ psLogger.append(c);
return this;
}
@Override
public LoggerPrintStream append(final CharSequence csq) {
- super.append(csq);
+ psLogger.append(csq);
return this;
}
@Override
public LoggerPrintStream append(final CharSequence csq, final int start, final int end) {
- super.append(csq, start, end);
+ psLogger.append(csq, start, end);
return this;
}
@Override
public boolean checkError() {
- return super.checkError();
+ return psLogger.checkError();
}
@Override
public void close() {
- super.close();
+ psLogger.close();
}
@Override
public void flush() {
- super.flush();
+ psLogger.flush();
}
@Override
public LoggerPrintStream format(final Locale l, final String format, final Object... args) {
- super.format(l, format, args);
+ psLogger.format(l, format, args);
return this;
}
@Override
public LoggerPrintStream format(final String format, final Object... args) {
- super.format(format, args);
+ psLogger.format(format, args);
return this;
}
@Override
public void print(final boolean b) {
- super.print(b);
+ psLogger.print(b);
}
@Override
public void print(final char c) {
- super.print(c);
+ psLogger.print(c);
}
@Override
public void print(final char[] s) {
- super.print(s);
+ psLogger.print(s);
}
@Override
public void print(final double d) {
- super.print(d);
+ psLogger.print(d);
}
@Override
public void print(final float f) {
- super.print(f);
+ psLogger.print(f);
}
@Override
public void print(final int i) {
- super.print(i);
+ psLogger.print(i);
}
@Override
public void print(final long l) {
- super.print(l);
+ psLogger.print(l);
}
@Override
public void print(final Object obj) {
- super.print(obj);
+ psLogger.print(obj);
}
@Override
public void print(final String s) {
- super.print(s);
+ psLogger.print(s);
}
@Override
public LoggerPrintStream printf(final Locale l, final String format, final Object... args) {
- super.printf(l, format, args);
+ psLogger.printf(l, format, args);
return this;
}
@Override
public LoggerPrintStream printf(final String format, final Object... args) {
- super.printf(format, args);
+ psLogger.printf(format, args);
return this;
}
@Override
public void println() {
- super.println();
+ psLogger.println();
}
@Override
public void println(final boolean x) {
- super.println(x);
+ psLogger.println(x);
}
@Override
public void println(final char x) {
- super.println(x);
+ psLogger.println(x);
}
@Override
public void println(final char[] x) {
- super.println(x);
+ psLogger.println(x);
}
@Override
public void println(final double x) {
- super.println(x);
+ psLogger.println(x);
}
@Override
public void println(final float x) {
- super.println(x);
+ psLogger.println(x);
}
@Override
public void println(final int x) {
- super.println(x);
+ psLogger.println(x);
}
@Override
public void println(final long x) {
- super.println(x);
+ psLogger.println(x);
}
@Override
public void println(final Object x) {
- super.println(x);
+ psLogger.println(x);
}
@Override
public void println(final String x) {
- super.println(x);
+ psLogger.println(x);
}
@Override
public String toString() {
- return LoggerPrintStream.class.getSimpleName() + "{stream=" + this.out + '}';
+ return LoggerPrintStream.class.getSimpleName() + psLogger.toString();
}
@Override
public void write(final byte[] b) throws IOException {
- super.write(b);
+ psLogger.write(b);
}
@Override
public void write(final byte[] b, final int off, final int len) {
- super.write(b, off, len);
+ psLogger.write(b, off, len);
}
@Override
public void write(final int b) {
- super.write(b);
+ psLogger.write(b);
}
}
diff --git a/log4j-iostreams/src/main/java/org/apache/logging/log4j/io/LoggerPrintWriter.java b/log4j-iostreams/src/main/java/org/apache/logging/log4j/io/LoggerPrintWriter.java
index ab7977c..d63ab3b 100644
--- a/log4j-iostreams/src/main/java/org/apache/logging/log4j/io/LoggerPrintWriter.java
+++ b/log4j-iostreams/src/main/java/org/apache/logging/log4j/io/LoggerPrintWriter.java
@@ -17,12 +17,15 @@
package org.apache.logging.log4j.io;
+import java.io.IOException;
import java.io.PrintWriter;
+import java.io.StringWriter;
import java.io.Writer;
import java.util.Locale;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.Marker;
+import org.apache.logging.log4j.io.internal.InternalPrintWriter;
import org.apache.logging.log4j.spi.ExtendedLogger;
/**
@@ -41,200 +44,203 @@
* @since 2.1
*/
// TODO
-// All method implementations that call only super are apparently required for the unit tests to pass.
+// All method implementations that call only writer.are apparently required for the unit tests to pass.
// Not sure if this a bug in the tests or a feature.
public class LoggerPrintWriter extends PrintWriter {
private static final String FQCN = LoggerPrintWriter.class.getName();
+ private final InternalPrintWriter writer;
protected LoggerPrintWriter(final ExtendedLogger logger, final boolean autoFlush, final String fqcn,
final Level level, final Marker marker) {
- super(new LoggerWriter(logger, fqcn == null ? FQCN : fqcn, level, marker), autoFlush);
+ super(new StringWriter());
+ writer = new InternalPrintWriter(logger, autoFlush, fqcn == null ? FQCN : fqcn, level, marker);
}
protected LoggerPrintWriter(final Writer writer, final boolean autoFlush, final ExtendedLogger logger,
final String fqcn, final Level level, final Marker marker) {
- super(new LoggerFilterWriter(writer, logger, fqcn == null ? FQCN : fqcn, level, marker), autoFlush);
+ super(writer);
+ this.writer = new InternalPrintWriter(writer, autoFlush, logger, fqcn == null ? FQCN : fqcn, level, marker);
}
@Override
public LoggerPrintWriter append(final char c) {
- super.append(c);
+ writer.append(c);
return this;
}
@Override
public LoggerPrintWriter append(final CharSequence csq) {
- super.append(csq);
+ writer.append(csq);
return this;
}
@Override
public LoggerPrintWriter append(final CharSequence csq, final int start, final int end) {
- super.append(csq, start, end);
+ writer.append(csq, start, end);
return this;
}
@Override
public boolean checkError() {
- return super.checkError();
+ return writer.checkError();
}
@Override
public void close() {
- super.close();
+ writer.close();
}
@Override
public void flush() {
- super.flush();
+ writer.flush();
}
@Override
public LoggerPrintWriter format(final Locale l, final String format, final Object... args) {
- super.format(l, format, args);
+ writer.format(l, format, args);
return this;
}
@Override
public LoggerPrintWriter format(final String format, final Object... args) {
- super.format(format, args);
+ writer.format(format, args);
return this;
}
@Override
public void print(final boolean b) {
- super.print(b);
+ writer.print(b);
}
@Override
public void print(final char c) {
- super.print(c);
+ writer.print(c);
}
@Override
public void print(final char[] s) {
- super.print(s);
+ writer.print(s);
}
@Override
public void print(final double d) {
- super.print(d);
+ writer.print(d);
}
@Override
public void print(final float f) {
- super.print(f);
+ writer.print(f);
}
@Override
public void print(final int i) {
- super.print(i);
+ writer.print(i);
}
@Override
public void print(final long l) {
- super.print(l);
+ writer.print(l);
}
@Override
public void print(final Object obj) {
- super.print(obj);
+ writer.print(obj);
}
@Override
public void print(final String s) {
- super.print(s);
+ writer.print(s);
}
@Override
public LoggerPrintWriter printf(final Locale l, final String format, final Object... args) {
- super.printf(l, format, args);
+ writer.printf(l, format, args);
return this;
}
@Override
public LoggerPrintWriter printf(final String format, final Object... args) {
- super.printf(format, args);
+ writer.printf(format, args);
return this;
}
@Override
public void println() {
- super.println();
+ writer.println();
}
@Override
public void println(final boolean x) {
- super.println(x);
+ writer.println(x);
}
@Override
public void println(final char x) {
- super.println(x);
+ writer.println(x);
}
@Override
public void println(final char[] x) {
- super.println(x);
+ writer.println(x);
}
@Override
public void println(final double x) {
- super.println(x);
+ writer.println(x);
}
@Override
public void println(final float x) {
- super.println(x);
+ writer.println(x);
}
@Override
public void println(final int x) {
- super.println(x);
+ writer.println(x);
}
@Override
public void println(final long x) {
- super.println(x);
+ writer.println(x);
}
@Override
public void println(final Object x) {
- super.println(x);
+ writer.println(x);
}
@Override
public void println(final String x) {
- super.println(x);
+ writer.println(x);
}
@Override
public String toString() {
- return LoggerPrintWriter.class.getSimpleName() + "{stream=" + this.out + '}';
+ return LoggerPrintWriter.class.getSimpleName() + writer.toString();
}
@Override
public void write(final char[] buf) {
- super.write(buf);
+ writer.write(buf);
}
@Override
public void write(final char[] buf, final int off, final int len) {
- super.write(buf, off, len);
+ writer.write(buf, off, len);
}
@Override
public void write(final int c) {
- super.write(c);
+ writer.write(c);
}
@Override
public void write(final String s) {
- super.write(s);
+ writer.write(s);
}
@Override
public void write(final String s, final int off, final int len) {
- super.write(s, off, len);
+ writer.write(s, off, len);
}
}
diff --git a/log4j-iostreams/src/main/java/org/apache/logging/log4j/io/LoggerReader.java b/log4j-iostreams/src/main/java/org/apache/logging/log4j/io/LoggerReader.java
index 265e8e7..01834ce 100644
--- a/log4j-iostreams/src/main/java/org/apache/logging/log4j/io/LoggerReader.java
+++ b/log4j-iostreams/src/main/java/org/apache/logging/log4j/io/LoggerReader.java
@@ -24,6 +24,7 @@
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.Marker;
+import org.apache.logging.log4j.io.internal.InternalReader;
import org.apache.logging.log4j.spi.ExtendedLogger;
/**
@@ -34,54 +35,41 @@
public class LoggerReader extends FilterReader {
private static final String FQCN = LoggerReader.class.getName();
- private final CharStreamLogger logger;
- private final String fqcn;
+ private final InternalReader reader;
protected LoggerReader(final Reader reader, final ExtendedLogger logger, final String fqcn, final Level level,
final Marker marker) {
super(reader);
- this.logger = new CharStreamLogger(logger, level, marker);
- this.fqcn = fqcn == null ? FQCN : fqcn;
+ this.reader = new InternalReader(reader, logger, fqcn == null ? FQCN : fqcn, level, marker);
}
@Override
public void close() throws IOException {
- super.close();
- this.logger.close(this.fqcn);
+ reader.close();
}
@Override
public int read() throws IOException {
- final int c = super.read();
- this.logger.put(this.fqcn, c);
- return c;
+ return reader.read();
}
@Override
public int read(final char[] cbuf) throws IOException {
- return read(cbuf, 0, cbuf.length);
+ return reader.read(cbuf);
}
@Override
public int read(final char[] cbuf, final int off, final int len) throws IOException {
- final int charsRead = super.read(cbuf, off, len);
- this.logger.put(this.fqcn, cbuf, off, charsRead);
- return charsRead;
+ return reader.read(cbuf, off, len);
}
@Override
public int read(final CharBuffer target) throws IOException {
- final int len = target.remaining();
- final char[] cbuf = new char[len];
- final int charsRead = read(cbuf, 0, len);
- if (charsRead > 0) {
- target.put(cbuf, 0, charsRead);
- }
- return charsRead;
+ return reader.read(target);
}
@Override
public String toString() {
- return LoggerReader.class.getSimpleName() + "{stream=" + this.in + '}';
+ return LoggerReader.class.getSimpleName() + this.reader.toString();
}
}
diff --git a/log4j-iostreams/src/main/java/org/apache/logging/log4j/io/LoggerWriter.java b/log4j-iostreams/src/main/java/org/apache/logging/log4j/io/LoggerWriter.java
index 8fba24c..29d61fa 100644
--- a/log4j-iostreams/src/main/java/org/apache/logging/log4j/io/LoggerWriter.java
+++ b/log4j-iostreams/src/main/java/org/apache/logging/log4j/io/LoggerWriter.java
@@ -22,6 +22,7 @@
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.Marker;
+import org.apache.logging.log4j.io.internal.InternalWriter;
import org.apache.logging.log4j.spi.ExtendedLogger;
/**
@@ -33,17 +34,15 @@
public class LoggerWriter extends Writer {
private static final String FQCN = LoggerWriter.class.getName();
- private final CharStreamLogger logger;
- private final String fqcn;
+ private final InternalWriter writer;
protected LoggerWriter(final ExtendedLogger logger, final String fqcn, final Level level, final Marker marker) {
- this.logger = new CharStreamLogger(logger, level, marker);
- this.fqcn = fqcn == null ? FQCN : fqcn;
+ this.writer = new InternalWriter(logger, fqcn == null ? FQCN : fqcn, level, marker);
}
@Override
public void close() throws IOException {
- this.logger.close(this.fqcn);
+ writer.close();
}
@Override
@@ -53,31 +52,31 @@
@Override
public String toString() {
- return this.getClass().getSimpleName() + "[fqcn=" + this.fqcn + ", logger=" + this.logger + "]";
+ return this.getClass().getSimpleName() + "[fqcn=" + writer.toString();
}
@Override
public void write(final char[] cbuf) throws IOException {
- this.logger.put(this.fqcn, cbuf, 0, cbuf.length);
+ writer.write(cbuf);
}
@Override
public void write(final char[] cbuf, final int off, final int len) throws IOException {
- this.logger.put(this.fqcn, cbuf, off, len);
+ writer.write(cbuf, off, len);
}
@Override
public void write(final int c) throws IOException {
- this.logger.put(this.fqcn, (char) c);
+ writer.write(c);
}
@Override
public void write(final String str) throws IOException {
- this.logger.put(this.fqcn, str, 0, str.length());
+ writer.write(str);
}
@Override
public void write(final String str, final int off, final int len) throws IOException {
- this.logger.put(this.fqcn, str, off, len);
+ writer.write(str, off, len);
}
}
diff --git a/log4j-iostreams/src/main/java/org/apache/logging/log4j/io/internal/InternalBufferedInputStream.java b/log4j-iostreams/src/main/java/org/apache/logging/log4j/io/internal/InternalBufferedInputStream.java
new file mode 100644
index 0000000..bf1490c
--- /dev/null
+++ b/log4j-iostreams/src/main/java/org/apache/logging/log4j/io/internal/InternalBufferedInputStream.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.io.internal;
+
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.Charset;
+
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.Marker;
+import org.apache.logging.log4j.io.LoggerInputStream;
+import org.apache.logging.log4j.spi.ExtendedLogger;
+
+/**
+ * Internal class that exists primarily to allow location calculations to work.
+ * @since 2.12
+ */
+public class InternalBufferedInputStream extends BufferedInputStream {
+ private static final String FQCN = InternalBufferedInputStream.class.getName();
+
+ public InternalBufferedInputStream(final InputStream in, final Charset charset, final ExtendedLogger logger,
+ final String fqcn, final Level level, final Marker marker) {
+ super(new InternalInputStream(in, charset, logger, fqcn == null ? FQCN : fqcn, level, marker));
+ }
+
+ public InternalBufferedInputStream(final InputStream in, final Charset charset, final int size,
+ final ExtendedLogger logger, final String fqcn, final Level level,
+ final Marker marker) {
+ super(new InternalInputStream(in, charset, logger, fqcn == null ? FQCN : fqcn, level, marker), size);
+ }
+
+ @Override
+ public void close() throws IOException {
+ super.close();
+ }
+
+ @Override
+ public synchronized int read() throws IOException {
+ return super.read();
+ }
+
+ @Override
+ public int read(final byte[] b) throws IOException {
+ return super.read(b, 0, b.length);
+ }
+
+ @Override
+ public synchronized int read(final byte[] b, final int off, final int len) throws IOException {
+ return super.read(b, off, len);
+ }
+
+ @Override
+ public String toString() {
+ return "{stream=" + this.in + '}';
+ }
+}
diff --git a/log4j-iostreams/src/main/java/org/apache/logging/log4j/io/internal/InternalBufferedReader.java b/log4j-iostreams/src/main/java/org/apache/logging/log4j/io/internal/InternalBufferedReader.java
new file mode 100644
index 0000000..0379912
--- /dev/null
+++ b/log4j-iostreams/src/main/java/org/apache/logging/log4j/io/internal/InternalBufferedReader.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.io.internal;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.Reader;
+import java.nio.CharBuffer;
+
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.Marker;
+import org.apache.logging.log4j.spi.ExtendedLogger;
+
+/**
+ * Internal class that exists primarily to allow location calculations to work.
+ * @since 2.12
+ */
+public class InternalBufferedReader extends BufferedReader {
+ private static final String FQCN = InternalBufferedReader.class.getName();
+
+ public InternalBufferedReader(final Reader reader, final ExtendedLogger logger, final String fqcn,
+ final Level level, final Marker marker) {
+ super(new InternalLoggerReader(reader, logger, fqcn == null ? FQCN : fqcn, level, marker));
+ }
+
+ public InternalBufferedReader(final Reader reader, final int size, final ExtendedLogger logger, final String fqcn,
+ final Level level, final Marker marker) {
+ super(new InternalLoggerReader(reader, logger, fqcn == null ? FQCN : fqcn, level, marker), size);
+ }
+
+ @Override
+ public void close() throws IOException {
+ super.close();
+ }
+
+ @Override
+ public int read() throws IOException {
+ return super.read();
+ }
+
+ @Override
+ public int read(final char[] cbuf) throws IOException {
+ return super.read(cbuf, 0, cbuf.length);
+ }
+
+ @Override
+ public int read(final char[] cbuf, final int off, final int len) throws IOException {
+ return super.read(cbuf, off, len);
+ }
+
+ @Override
+ public int read(final CharBuffer target) throws IOException {
+ final int len = target.remaining();
+ final char[] cbuf = new char[len];
+ final int charsRead = read(cbuf, 0, len);
+ if (charsRead > 0) {
+ target.put(cbuf, 0, charsRead);
+ }
+ return charsRead;
+ }
+
+ @Override
+ public String readLine() throws IOException {
+ return super.readLine();
+ }
+}
diff --git a/log4j-iostreams/src/main/java/org/apache/logging/log4j/io/internal/InternalFilterOutputStream.java b/log4j-iostreams/src/main/java/org/apache/logging/log4j/io/internal/InternalFilterOutputStream.java
new file mode 100644
index 0000000..112e1dd
--- /dev/null
+++ b/log4j-iostreams/src/main/java/org/apache/logging/log4j/io/internal/InternalFilterOutputStream.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.io.internal;
+
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.charset.Charset;
+
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.Marker;
+import org.apache.logging.log4j.io.ByteStreamLogger;
+import org.apache.logging.log4j.spi.ExtendedLogger;
+
+/**
+ * Internal class that exists primarily to allow location calculations to work.
+ *
+ * @since 2.12
+ */
+public class InternalFilterOutputStream extends FilterOutputStream {
+
+ private final ByteStreamLogger logger;
+ private final String fqcn;
+
+ public InternalFilterOutputStream(final OutputStream out, final Charset charset, final ExtendedLogger logger,
+ final String fqcn, final Level level, final Marker marker) {
+ super(out);
+ this.logger = new ByteStreamLogger(logger, level, marker, charset);
+ this.fqcn = fqcn;
+ }
+
+ @Override
+ public void close() throws IOException {
+ this.out.close();
+ this.logger.close(this.fqcn);
+ }
+
+ @Override
+ public void flush() throws IOException {
+ this.out.flush();
+ }
+
+ @Override
+ public String toString() {
+ return "{stream=" + this.out + '}';
+ }
+
+ @Override
+ public void write(final byte[] b) throws IOException {
+ this.out.write(b);
+ this.logger.put(this.fqcn, b, 0, b.length);
+ }
+
+ @Override
+ public void write(final byte[] b, final int off, final int len) throws IOException {
+ this.out.write(b, off, len);
+ this.logger.put(this.fqcn, b, off, len);
+ }
+
+ @Override
+ public void write(final int b) throws IOException {
+ this.out.write(b);
+ this.logger.put(this.fqcn, (byte) (b & 0xFF));
+ }
+}
diff --git a/log4j-iostreams/src/main/java/org/apache/logging/log4j/io/internal/InternalFilterWriter.java b/log4j-iostreams/src/main/java/org/apache/logging/log4j/io/internal/InternalFilterWriter.java
new file mode 100644
index 0000000..67c4859
--- /dev/null
+++ b/log4j-iostreams/src/main/java/org/apache/logging/log4j/io/internal/InternalFilterWriter.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.io.internal;
+
+import java.io.FilterWriter;
+import java.io.IOException;
+import java.io.Writer;
+
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.Marker;
+import org.apache.logging.log4j.io.CharStreamLogger;
+import org.apache.logging.log4j.spi.ExtendedLogger;
+
+/**
+ * Internal class that exists primarily to allow location calculation to work.
+ *
+ * @since 2.12
+ */
+public class InternalFilterWriter extends FilterWriter {
+
+ private final CharStreamLogger logger;
+ private final String fqcn;
+
+ public InternalFilterWriter(final Writer out, final ExtendedLogger logger, final String fqcn, final Level level,
+ final Marker marker) {
+ super(out);
+ this.logger = new CharStreamLogger(logger, level, marker);
+ this.fqcn = fqcn;
+ }
+
+ @Override
+ public void close() throws IOException {
+ this.out.close();
+ this.logger.close(this.fqcn);
+ }
+
+ @Override
+ public void flush() throws IOException {
+ this.out.flush();
+ }
+
+ @Override
+ public String toString() {
+ return "{writer=" + this.out + '}';
+ }
+
+ @Override
+ public void write(final char[] cbuf) throws IOException {
+ this.out.write(cbuf);
+ this.logger.put(this.fqcn, cbuf, 0, cbuf.length);
+ }
+
+ @Override
+ public void write(final char[] cbuf, final int off, final int len) throws IOException {
+ this.out.write(cbuf, off, len);
+ this.logger.put(this.fqcn, cbuf, off, len);
+ }
+
+ @Override
+ public void write(final int c) throws IOException {
+ this.out.write(c);
+ this.logger.put(this.fqcn, (char) c);
+ }
+
+ @Override
+ public void write(final String str) throws IOException {
+ this.out.write(str);
+ this.logger.put(this.fqcn, str, 0, str.length());
+ }
+
+ @Override
+ public void write(final String str, final int off, final int len) throws IOException {
+ this.out.write(str, off, len);
+ this.logger.put(this.fqcn, str, off, len);
+ }
+}
diff --git a/log4j-iostreams/src/main/java/org/apache/logging/log4j/io/internal/InternalInputStream.java b/log4j-iostreams/src/main/java/org/apache/logging/log4j/io/internal/InternalInputStream.java
new file mode 100644
index 0000000..160766a
--- /dev/null
+++ b/log4j-iostreams/src/main/java/org/apache/logging/log4j/io/internal/InternalInputStream.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.io.internal;
+
+import java.io.FilterInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.Charset;
+
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.Marker;
+import org.apache.logging.log4j.io.ByteStreamLogger;
+import org.apache.logging.log4j.spi.ExtendedLogger;
+
+/**
+ * Internal class that exists primarily to allow location calculation to work.
+ *
+ * @since 2.12
+ */
+public class InternalInputStream extends FilterInputStream {
+
+ private final String fqcn;
+ private final ByteStreamLogger logger;
+
+ public InternalInputStream(final InputStream in, final Charset charset, final ExtendedLogger logger,
+ final String fqcn, final Level level, final Marker marker) {
+ super(in);
+ this.logger = new ByteStreamLogger(logger, level, marker, charset);
+ this.fqcn = fqcn;
+ }
+
+ @Override
+ public void close() throws IOException {
+ this.logger.close(this.fqcn);
+ super.close();
+ }
+
+ @Override
+ public int read() throws IOException {
+ final int b = super.read();
+ this.logger.put(this.fqcn, b);
+ return b;
+ }
+
+ @Override
+ public int read(final byte[] b) throws IOException {
+ return read(b, 0, b.length);
+ }
+
+ @Override
+ public int read(final byte[] b, final int off, final int len) throws IOException {
+ final int bytesRead = super.read(b, off, len);
+ this.logger.put(this.fqcn, b, off, bytesRead);
+ return bytesRead;
+ }
+
+ @Override
+ public String toString() {
+ return "{stream=" + this.in + '}';
+ }
+}
diff --git a/log4j-iostreams/src/main/java/org/apache/logging/log4j/io/internal/InternalLoggerReader.java b/log4j-iostreams/src/main/java/org/apache/logging/log4j/io/internal/InternalLoggerReader.java
new file mode 100644
index 0000000..86914ed
--- /dev/null
+++ b/log4j-iostreams/src/main/java/org/apache/logging/log4j/io/internal/InternalLoggerReader.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.io.internal;
+
+import java.io.FilterReader;
+import java.io.IOException;
+import java.io.Reader;
+import java.nio.CharBuffer;
+
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.Marker;
+import org.apache.logging.log4j.io.CharStreamLogger;
+import org.apache.logging.log4j.spi.ExtendedLogger;
+
+/**
+ * Internal class that exists primarily to allow location calculation to work.
+ *
+ * @since 2.12
+ */
+public class InternalLoggerReader extends FilterReader {
+
+ private final CharStreamLogger logger;
+ private final String fqcn;
+
+ public InternalLoggerReader(final Reader reader, final ExtendedLogger logger, final String fqcn, final Level level,
+ final Marker marker) {
+ super(reader);
+ this.logger = new CharStreamLogger(logger, level, marker);
+ this.fqcn = fqcn;
+ }
+
+ @Override
+ public void close() throws IOException {
+ super.close();
+ this.logger.close(this.fqcn);
+ }
+
+ @Override
+ public int read() throws IOException {
+ final int c = super.read();
+ this.logger.put(this.fqcn, c);
+ return c;
+ }
+
+ @Override
+ public int read(final char[] cbuf) throws IOException {
+ return read(cbuf, 0, cbuf.length);
+ }
+
+ @Override
+ public int read(final char[] cbuf, final int off, final int len) throws IOException {
+ final int charsRead = super.read(cbuf, off, len);
+ this.logger.put(this.fqcn, cbuf, off, charsRead);
+ return charsRead;
+ }
+
+ @Override
+ public int read(final CharBuffer target) throws IOException {
+ final int len = target.remaining();
+ final char[] cbuf = new char[len];
+ final int charsRead = read(cbuf, 0, len);
+ if (charsRead > 0) {
+ target.put(cbuf, 0, charsRead);
+ }
+ return charsRead;
+ }
+
+ @Override
+ public String toString() {
+ return "{stream=" + this.in + '}';
+ }
+}
diff --git a/log4j-iostreams/src/main/java/org/apache/logging/log4j/io/internal/InternalOutputStream.java b/log4j-iostreams/src/main/java/org/apache/logging/log4j/io/internal/InternalOutputStream.java
new file mode 100644
index 0000000..595a01c
--- /dev/null
+++ b/log4j-iostreams/src/main/java/org/apache/logging/log4j/io/internal/InternalOutputStream.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.io.internal;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.charset.Charset;
+
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.Marker;
+import org.apache.logging.log4j.io.ByteStreamLogger;
+import org.apache.logging.log4j.spi.ExtendedLogger;
+
+/**
+ * Internal class that exists primarily to allow location calculation to work.
+ *
+ * @since 2.12
+ */
+public class InternalOutputStream extends OutputStream {
+
+ private final ByteStreamLogger logger;
+ private final String fqcn;
+
+ public InternalOutputStream(final ExtendedLogger logger, final Level level, final Marker marker,
+ final Charset charset, final String fqcn) {
+ this.logger = new ByteStreamLogger(logger, level, marker, charset);
+ this.fqcn = fqcn;
+ }
+
+ @Override
+ public void close() throws IOException {
+ this.logger.close(this.fqcn);
+ }
+
+ @Override
+ public void flush() throws IOException {
+ // do nothing
+ }
+
+ @Override
+ public void write(final byte[] b) throws IOException {
+ this.logger.put(this.fqcn, b, 0, b.length);
+ }
+
+ @Override
+ public void write(final byte[] b, final int off, final int len) throws IOException {
+ this.logger.put(this.fqcn, b, off, len);
+ }
+
+ @Override
+ public void write(final int b) throws IOException {
+ this.logger.put(this.fqcn, (byte) (b & 0xFF));
+ }
+}
diff --git a/log4j-iostreams/src/main/java/org/apache/logging/log4j/io/internal/InternalPrintStream.java b/log4j-iostreams/src/main/java/org/apache/logging/log4j/io/internal/InternalPrintStream.java
new file mode 100644
index 0000000..529d4b6
--- /dev/null
+++ b/log4j-iostreams/src/main/java/org/apache/logging/log4j/io/internal/InternalPrintStream.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.io.internal;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.io.UnsupportedEncodingException;
+import java.nio.charset.Charset;
+import java.util.Locale;
+
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.Marker;
+import org.apache.logging.log4j.spi.ExtendedLogger;
+
+/**
+ * Internal class used primarily to allow location calculations to work properly.
+ *
+ * @since 2.12
+ */
+public class InternalPrintStream extends PrintStream {
+
+ public InternalPrintStream(final ExtendedLogger logger, final boolean autoFlush, final Charset charset,
+ final String fqcn, final Level level, final Marker marker)
+ throws UnsupportedEncodingException {
+ super(new InternalOutputStream(logger, level, marker, ensureNonNull(charset), fqcn),
+ autoFlush, ensureNonNull(charset).name());
+ }
+
+ public InternalPrintStream(final OutputStream out, final boolean autoFlush, final Charset charset,
+ final ExtendedLogger logger, final String fqcn, final Level level, final Marker marker)
+ throws UnsupportedEncodingException {
+ super(new InternalFilterOutputStream(out, ensureNonNull(charset), logger, fqcn, level,
+ marker), autoFlush, ensureNonNull(charset).name());
+ }
+
+ private static Charset ensureNonNull(final Charset charset) {
+ return charset == null ? Charset.defaultCharset() : charset;
+ }
+
+ @Override
+ public InternalPrintStream append(final char c) {
+ super.append(c);
+ return this;
+ }
+
+ @Override
+ public InternalPrintStream append(final CharSequence csq) {
+ super.append(csq);
+ return this;
+ }
+
+ @Override
+ public InternalPrintStream append(final CharSequence csq, final int start, final int end) {
+ super.append(csq, start, end);
+ return this;
+ }
+
+ @Override
+ public boolean checkError() {
+ return super.checkError();
+ }
+
+ @Override
+ public void close() {
+ super.close();
+ }
+
+ @Override
+ public void flush() {
+ super.flush();
+ }
+
+ @Override
+ public InternalPrintStream format(final Locale l, final String format, final Object... args) {
+ super.format(l, format, args);
+ return this;
+ }
+
+ @Override
+ public InternalPrintStream format(final String format, final Object... args) {
+ super.format(format, args);
+ return this;
+ }
+
+ @Override
+ public void print(final boolean b) {
+ super.print(b);
+ }
+
+ @Override
+ public void print(final char c) {
+ super.print(c);
+ }
+
+ @Override
+ public void print(final char[] s) {
+ super.print(s);
+ }
+
+ @Override
+ public void print(final double d) {
+ super.print(d);
+ }
+
+ @Override
+ public void print(final float f) {
+ super.print(f);
+ }
+
+ @Override
+ public void print(final int i) {
+ super.print(i);
+ }
+
+ @Override
+ public void print(final long l) {
+ super.print(l);
+ }
+
+ @Override
+ public void print(final Object obj) {
+ super.print(obj);
+ }
+
+ @Override
+ public void print(final String s) {
+ super.print(s);
+ }
+
+ @Override
+ public InternalPrintStream printf(final Locale l, final String format, final Object... args) {
+ super.printf(l, format, args);
+ return this;
+ }
+
+ @Override
+ public InternalPrintStream printf(final String format, final Object... args) {
+ super.printf(format, args);
+ return this;
+ }
+
+ @Override
+ public void println() {
+ super.println();
+ }
+
+ @Override
+ public void println(final boolean x) {
+ super.println(x);
+ }
+
+ @Override
+ public void println(final char x) {
+ super.println(x);
+ }
+
+ @Override
+ public void println(final char[] x) {
+ super.println(x);
+ }
+
+ @Override
+ public void println(final double x) {
+ super.println(x);
+ }
+
+ @Override
+ public void println(final float x) {
+ super.println(x);
+ }
+
+ @Override
+ public void println(final int x) {
+ super.println(x);
+ }
+
+ @Override
+ public void println(final long x) {
+ super.println(x);
+ }
+
+ @Override
+ public void println(final Object x) {
+ super.println(x);
+ }
+
+ @Override
+ public void println(final String x) {
+ super.println(x);
+ }
+
+ @Override
+ public String toString() {
+ return "{stream=" + this.out + '}';
+ }
+
+ @Override
+ public void write(final byte[] b) throws IOException {
+ super.write(b);
+ }
+
+ @Override
+ public void write(final byte[] b, final int off, final int len) {
+ super.write(b, off, len);
+ }
+
+ @Override
+ public void write(final int b) {
+ super.write(b);
+ }
+}
diff --git a/log4j-iostreams/src/main/java/org/apache/logging/log4j/io/internal/InternalPrintWriter.java b/log4j-iostreams/src/main/java/org/apache/logging/log4j/io/internal/InternalPrintWriter.java
new file mode 100644
index 0000000..d673fc8
--- /dev/null
+++ b/log4j-iostreams/src/main/java/org/apache/logging/log4j/io/internal/InternalPrintWriter.java
@@ -0,0 +1,228 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.logging.log4j.io.internal;
+
+import java.io.PrintWriter;
+import java.io.Writer;
+import java.util.Locale;
+
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.Marker;
+import org.apache.logging.log4j.io.LoggerFilterWriter;
+import org.apache.logging.log4j.io.LoggerWriter;
+import org.apache.logging.log4j.spi.ExtendedLogger;
+
+/**
+ * Internal class that exists primarily to allow location calculations to work.
+ *
+ * @since 2.12
+ */
+public class InternalPrintWriter extends PrintWriter {
+
+ public InternalPrintWriter(final ExtendedLogger logger, final boolean autoFlush, final String fqcn,
+ final Level level, final Marker marker) {
+ super(new InternalWriter(logger, fqcn, level, marker), autoFlush);
+ }
+
+ public InternalPrintWriter(final Writer writer, final boolean autoFlush, final ExtendedLogger logger,
+ final String fqcn, final Level level, final Marker marker) {
+ super(new InternalFilterWriter(writer, logger, fqcn, level, marker), autoFlush);
+ }
+
+ @Override
+ public InternalPrintWriter append(final char c) {
+ super.append(c);
+ return this;
+ }
+
+ @Override
+ public InternalPrintWriter append(final CharSequence csq) {
+ super.append(csq);
+ return this;
+ }
+
+ @Override
+ public InternalPrintWriter append(final CharSequence csq, final int start, final int end) {
+ super.append(csq, start, end);
+ return this;
+ }
+
+ @Override
+ public boolean checkError() {
+ return super.checkError();
+ }
+
+ @Override
+ public void close() {
+ super.close();
+ }
+
+ @Override
+ public void flush() {
+ super.flush();
+ }
+
+ @Override
+ public InternalPrintWriter format(final Locale l, final String format, final Object... args) {
+ super.format(l, format, args);
+ return this;
+ }
+
+ @Override
+ public InternalPrintWriter format(final String format, final Object... args) {
+ super.format(format, args);
+ return this;
+ }
+
+ @Override
+ public void print(final boolean b) {
+ super.print(b);
+ }
+
+ @Override
+ public void print(final char c) {
+ super.print(c);
+ }
+
+ @Override
+ public void print(final char[] s) {
+ super.print(s);
+ }
+
+ @Override
+ public void print(final double d) {
+ super.print(d);
+ }
+
+ @Override
+ public void print(final float f) {
+ super.print(f);
+ }
+
+ @Override
+ public void print(final int i) {
+ super.print(i);
+ }
+
+ @Override
+ public void print(final long l) {
+ super.print(l);
+ }
+
+ @Override
+ public void print(final Object obj) {
+ super.print(obj);
+ }
+
+ @Override
+ public void print(final String s) {
+ super.print(s);
+ }
+
+ @Override
+ public InternalPrintWriter printf(final Locale l, final String format, final Object... args) {
+ super.printf(l, format, args);
+ return this;
+ }
+
+ @Override
+ public InternalPrintWriter printf(final String format, final Object... args) {
+ super.printf(format, args);
+ return this;
+ }
+
+ @Override
+ public void println() {
+ super.println();
+ }
+
+ @Override
+ public void println(final boolean x) {
+ super.println(x);
+ }
+
+ @Override
+ public void println(final char x) {
+ super.println(x);
+ }
+
+ @Override
+ public void println(final char[] x) {
+ super.println(x);
+ }
+
+ @Override
+ public void println(final double x) {
+ super.println(x);
+ }
+
+ @Override
+ public void println(final float x) {
+ super.println(x);
+ }
+
+ @Override
+ public void println(final int x) {
+ super.println(x);
+ }
+
+ @Override
+ public void println(final long x) {
+ super.println(x);
+ }
+
+ @Override
+ public void println(final Object x) {
+ super.println(x);
+ }
+
+ @Override
+ public void println(final String x) {
+ super.println(x);
+ }
+
+ @Override
+ public String toString() {
+ return "{stream=" + this.out + '}';
+ }
+
+ @Override
+ public void write(final char[] buf) {
+ super.write(buf);
+ }
+
+ @Override
+ public void write(final char[] buf, final int off, final int len) {
+ super.write(buf, off, len);
+ }
+
+ @Override
+ public void write(final int c) {
+ super.write(c);
+ }
+
+ @Override
+ public void write(final String s) {
+ super.write(s);
+ }
+
+ @Override
+ public void write(final String s, final int off, final int len) {
+ super.write(s, off, len);
+ }
+}
diff --git a/log4j-iostreams/src/main/java/org/apache/logging/log4j/io/internal/InternalReader.java b/log4j-iostreams/src/main/java/org/apache/logging/log4j/io/internal/InternalReader.java
new file mode 100644
index 0000000..932ae9b
--- /dev/null
+++ b/log4j-iostreams/src/main/java/org/apache/logging/log4j/io/internal/InternalReader.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.io.internal;
+
+import java.io.FilterReader;
+import java.io.IOException;
+import java.io.Reader;
+import java.nio.CharBuffer;
+
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.Marker;
+import org.apache.logging.log4j.io.CharStreamLogger;
+import org.apache.logging.log4j.spi.ExtendedLogger;
+
+/**
+ * Internal class that exists primarily to allow location calculations to work.
+ *
+ * @since 2.12
+ */
+public class InternalReader extends FilterReader {
+
+ private final CharStreamLogger logger;
+ private final String fqcn;
+
+ public InternalReader(final Reader reader, final ExtendedLogger logger, final String fqcn, final Level level,
+ final Marker marker) {
+ super(reader);
+ this.logger = new CharStreamLogger(logger, level, marker);
+ this.fqcn = fqcn;
+ }
+
+ @Override
+ public void close() throws IOException {
+ super.close();
+ this.logger.close(this.fqcn);
+ }
+
+ @Override
+ public int read() throws IOException {
+ final int c = super.read();
+ this.logger.put(this.fqcn, c);
+ return c;
+ }
+
+ @Override
+ public int read(final char[] cbuf) throws IOException {
+ return read(cbuf, 0, cbuf.length);
+ }
+
+ @Override
+ public int read(final char[] cbuf, final int off, final int len) throws IOException {
+ final int charsRead = super.read(cbuf, off, len);
+ this.logger.put(this.fqcn, cbuf, off, charsRead);
+ return charsRead;
+ }
+
+ @Override
+ public int read(final CharBuffer target) throws IOException {
+ final int len = target.remaining();
+ final char[] cbuf = new char[len];
+ final int charsRead = read(cbuf, 0, len);
+ if (charsRead > 0) {
+ target.put(cbuf, 0, charsRead);
+ }
+ return charsRead;
+ }
+
+ @Override
+ public String toString() {
+ return "{stream=" + this.in + '}';
+ }
+}
diff --git a/log4j-iostreams/src/main/java/org/apache/logging/log4j/io/internal/InternalWriter.java b/log4j-iostreams/src/main/java/org/apache/logging/log4j/io/internal/InternalWriter.java
new file mode 100644
index 0000000..b117a57
--- /dev/null
+++ b/log4j-iostreams/src/main/java/org/apache/logging/log4j/io/internal/InternalWriter.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.io.internal;
+
+import java.io.IOException;
+import java.io.Writer;
+
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.Marker;
+import org.apache.logging.log4j.io.CharStreamLogger;
+import org.apache.logging.log4j.spi.ExtendedLogger;
+
+/**
+ * Internal class that exists primarily to allow location calculations to work.
+ * @since 2.12
+ */
+public class InternalWriter extends Writer {
+
+ private final CharStreamLogger logger;
+ private final String fqcn;
+
+ public InternalWriter(final ExtendedLogger logger, final String fqcn, final Level level, final Marker marker) {
+ this.logger = new CharStreamLogger(logger, level, marker);
+ this.fqcn = fqcn;
+ }
+
+ @Override
+ public void close() throws IOException {
+ this.logger.close(this.fqcn);
+ }
+
+ @Override
+ public void flush() throws IOException {
+ // do nothing
+ }
+
+ @Override
+ public String toString() {
+ return this.getClass().getSimpleName() + "[fqcn=" + this.fqcn + ", logger=" + this.logger + "]";
+ }
+
+ @Override
+ public void write(final char[] cbuf) throws IOException {
+ this.logger.put(this.fqcn, cbuf, 0, cbuf.length);
+ }
+
+ @Override
+ public void write(final char[] cbuf, final int off, final int len) throws IOException {
+ this.logger.put(this.fqcn, cbuf, off, len);
+ }
+
+ @Override
+ public void write(final int c) throws IOException {
+ this.logger.put(this.fqcn, (char) c);
+ }
+
+ @Override
+ public void write(final String str) throws IOException {
+ this.logger.put(this.fqcn, str, 0, str.length());
+ }
+
+ @Override
+ public void write(final String str, final int off, final int len) throws IOException {
+ this.logger.put(this.fqcn, str, off, len);
+ }
+}
diff --git a/log4j-iostreams/src/main/java/org/apache/logging/log4j/io/internal/package-info.java b/log4j-iostreams/src/main/java/org/apache/logging/log4j/io/internal/package-info.java
new file mode 100644
index 0000000..1c390d2
--- /dev/null
+++ b/log4j-iostreams/src/main/java/org/apache/logging/log4j/io/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.
+ */
+/**
+ * Internal classes. Should not be used by users.
+ */
+package org.apache.logging.log4j.io.internal;
diff --git a/log4j-jcl/pom.xml b/log4j-jcl/pom.xml
index 73c5d8d..3c167ca 100644
--- a/log4j-jcl/pom.xml
+++ b/log4j-jcl/pom.xml
@@ -135,6 +135,7 @@
project -->
<detectOfflineLinks>false</detectOfflineLinks>
<linksource>true</linksource>
+ <source>8</source>
</configuration>
<reportSets>
<reportSet>
diff --git a/log4j-jdbc-dbcp2/pom.xml b/log4j-jdbc-dbcp2/pom.xml
index c5fc301..2fe3312 100644
--- a/log4j-jdbc-dbcp2/pom.xml
+++ b/log4j-jdbc-dbcp2/pom.xml
@@ -127,6 +127,7 @@
<!-- module link generation is completely broken in the javadoc plugin for a multi-module non-aggregating project -->
<detectOfflineLinks>false</detectOfflineLinks>
<linksource>true</linksource>
+ <source>8</source>
</configuration>
<reportSets>
<reportSet>
diff --git a/log4j-jdbc-dbcp2/revapi.json b/log4j-jdbc-dbcp2/revapi.json
new file mode 100644
index 0000000..dd119a2
--- /dev/null
+++ b/log4j-jdbc-dbcp2/revapi.json
@@ -0,0 +1,28 @@
+[
+ {
+ "extension": "revapi.java",
+ "configuration": {
+ "filter": {
+ "classes": {
+ "exclude": [
+ ]
+ }
+ }
+ }
+ },
+ {
+ "extension": "revapi.ignore",
+ "configuration": [
+ {
+ "code": "java.class.removed",
+ "old": "class org.apache.logging.log4j.core.appender.db.jdbc.PoolingDriverConnectionSource",
+ "justification": "Package was renamed to comply with the Java 9 module system"
+ },
+ {
+ "code": "java.class.removed",
+ "old": "class org.apache.logging.log4j.core.appender.db.jdbc.PoolableConnectionFactoryConfig",
+ "justification": "Package was renamed to comply with the Java 9 module system"
+ }
+ ]
+ }
+]
\ No newline at end of file
diff --git a/log4j-jdbc-dbcp2/src/main/java/org/apache/logging/log4j/dbcp2/appender/PoolableConnectionFactoryConfig.java b/log4j-jdbc-dbcp2/src/main/java/org/apache/logging/log4j/dbcp2/appender/PoolableConnectionFactoryConfig.java
new file mode 100644
index 0000000..01993cf
--- /dev/null
+++ b/log4j-jdbc-dbcp2/src/main/java/org/apache/logging/log4j/dbcp2/appender/PoolableConnectionFactoryConfig.java
@@ -0,0 +1,284 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+
+package org.apache.logging.log4j.dbcp2.appender;
+
+import org.apache.commons.dbcp2.PoolableConnectionFactory;
+import org.apache.commons.pool2.impl.GenericKeyedObjectPoolConfig;
+import org.apache.logging.log4j.core.Core;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginBuilderAttribute;
+import org.apache.logging.log4j.plugins.PluginElement;
+import org.apache.logging.log4j.plugins.PluginFactory;
+import org.apache.logging.log4j.status.StatusLogger;
+import org.apache.logging.log4j.util.Strings;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * Configures an Apache Commons DBCP {@link PoolableConnectionFactory}.
+ *
+ * @since 2.11.2
+ */
+@Plugin(name = "PoolableConnectionFactory", category = Core.CATEGORY_NAME, printObject = true)
+public class PoolableConnectionFactoryConfig {
+
+ public static class Builder implements org.apache.logging.log4j.plugins.util.Builder<PoolableConnectionFactoryConfig> {
+
+ private static final PoolableConnectionFactory DEFAULT = new PoolableConnectionFactory(null, null);
+
+ /**
+ * Internal constant to indicate the level is not set.
+ */
+ private static final int UNKNOWN_TRANSACTION_ISOLATION = -1;
+
+ // All of these instance variables match DBCP WRT Boolean vs. boolean.
+ // All of these defaults are the same as in PoolableConnectionFactory.
+
+ @PluginBuilderAttribute
+ private boolean cacheState;
+
+ // TODO
+ @PluginElement("ConnectionInitSqls")
+ private String[] connectionInitSqls;
+
+ @PluginBuilderAttribute
+ private Boolean defaultAutoCommit;
+
+ @PluginBuilderAttribute
+ private String defaultCatalog;
+
+ @PluginBuilderAttribute
+ private Integer defaultQueryTimeoutSeconds = DEFAULT.getDefaultQueryTimeout();
+
+ @PluginBuilderAttribute
+ private Boolean defaultReadOnly;
+
+ @PluginBuilderAttribute
+ private int defaultTransactionIsolation = UNKNOWN_TRANSACTION_ISOLATION;
+
+ // TODO
+ @PluginElement("DisconnectionSqlCodes")
+ private String[] disconnectionSqlCodes = (String[]) (DEFAULT.getDisconnectionSqlCodes() == null ? null
+ : DEFAULT.getDisconnectionSqlCodes().toArray());
+
+ @PluginBuilderAttribute
+ private boolean autoCommitOnReturn = DEFAULT.isEnableAutoCommitOnReturn();
+
+ @PluginBuilderAttribute
+ private boolean fastFailValidation = DEFAULT.isFastFailValidation();
+
+ @PluginBuilderAttribute
+ private long maxConnLifetimeMillis = -1;
+
+ @PluginBuilderAttribute
+ private int maxOpenPreparedStatements = GenericKeyedObjectPoolConfig.DEFAULT_MAX_TOTAL_PER_KEY;
+
+ @PluginBuilderAttribute
+ private boolean poolStatements;
+
+ @PluginBuilderAttribute
+ private boolean rollbackOnReturn = DEFAULT.isRollbackOnReturn();
+
+ @PluginBuilderAttribute
+ private String validationQuery;
+
+ @PluginBuilderAttribute
+ private int validationQueryTimeoutSeconds = -1;
+
+ private List<String> asList(final String[] array) {
+ return array == null ? null : Arrays.asList(array);
+ }
+
+ @Override
+ public PoolableConnectionFactoryConfig build() {
+ return new PoolableConnectionFactoryConfig(cacheState, asList(connectionInitSqls), defaultAutoCommit,
+ defaultCatalog, defaultQueryTimeoutSeconds, defaultReadOnly, defaultTransactionIsolation,
+ asList(disconnectionSqlCodes), autoCommitOnReturn, fastFailValidation, maxConnLifetimeMillis,
+ maxOpenPreparedStatements, poolStatements, rollbackOnReturn, validationQuery,
+ validationQueryTimeoutSeconds);
+ }
+
+ public Builder setAutoCommitOnReturn(final boolean autoCommitOnReturn) {
+ this.autoCommitOnReturn = autoCommitOnReturn;
+ return this;
+ }
+
+ public Builder setCacheState(final boolean cacheState) {
+ this.cacheState = cacheState;
+ return this;
+ }
+
+ public Builder setConnectionInitSqls(final String... connectionInitSqls) {
+ this.connectionInitSqls = connectionInitSqls;
+ return this;
+ }
+
+ public Builder setDefaultAutoCommit(final Boolean defaultAutoCommit) {
+ this.defaultAutoCommit = defaultAutoCommit;
+ return this;
+ }
+
+ public Builder setDefaultCatalog(final String defaultCatalog) {
+ this.defaultCatalog = defaultCatalog;
+ return this;
+ }
+
+ public Builder setDefaultQueryTimeoutSeconds(final Integer defaultQueryTimeoutSeconds) {
+ this.defaultQueryTimeoutSeconds = defaultQueryTimeoutSeconds;
+ return this;
+ }
+
+ public Builder setDefaultReadOnly(final Boolean defaultReadOnly) {
+ this.defaultReadOnly = defaultReadOnly;
+ return this;
+ }
+
+ public Builder setDefaultTransactionIsolation(final int defaultTransactionIsolation) {
+ this.defaultTransactionIsolation = defaultTransactionIsolation;
+ return this;
+ }
+
+ public Builder setDisconnectionSqlCodes(final String... disconnectionSqlCodes) {
+ this.disconnectionSqlCodes = disconnectionSqlCodes;
+ return this;
+ }
+
+ public Builder setFastFailValidation(final boolean fastFailValidation) {
+ this.fastFailValidation = fastFailValidation;
+ return this;
+ }
+
+ public Builder setMaxConnLifetimeMillis(final long maxConnLifetimeMillis) {
+ this.maxConnLifetimeMillis = maxConnLifetimeMillis;
+ return this;
+ }
+
+ public Builder setMaxOpenPreparedStatements(final int maxOpenPreparedStatements) {
+ this.maxOpenPreparedStatements = maxOpenPreparedStatements;
+ return this;
+ }
+
+ public Builder setPoolStatements(final boolean poolStatements) {
+ this.poolStatements = poolStatements;
+ return this;
+ }
+
+ public Builder setRollbackOnReturn(final boolean rollbackOnReturn) {
+ this.rollbackOnReturn = rollbackOnReturn;
+ return this;
+ }
+
+ public Builder setValidationQuery(final String validationQuery) {
+ this.validationQuery = validationQuery;
+ return this;
+ }
+
+ public Builder setValidationQueryTimeoutSeconds(final int validationQueryTimeoutSeconds) {
+ this.validationQueryTimeoutSeconds = validationQueryTimeoutSeconds;
+ return this;
+ }
+ }
+
+ // ALL of these instance variables match DBCP WRT Boolean vs. boolean.
+
+ @PluginFactory
+ public static Builder newBuilder() {
+ return new Builder();
+ }
+
+ private final boolean cacheState;
+ private final Collection<String> connectionInitSqls;
+ private final Boolean defaultAutoCommit;
+ private final String defaultCatalog;
+ private final Integer defaultQueryTimeoutSeconds;
+ private final Boolean defaultReadOnly;
+ private final int defaultTransactionIsolation;
+ private final Collection<String> disconnectionSqlCodes;
+ private final boolean autoCommitOnReturn;
+ private final boolean fastFailValidation;
+ private final long maxConnLifetimeMillis;
+ private final int maxOpenPreparedStatements;
+ private final boolean poolStatements;
+ private final boolean rollbackOnReturn;
+ private final String validationQuery;
+
+ private final int validationQueryTimeoutSeconds;
+
+ private PoolableConnectionFactoryConfig(final boolean cacheState, final Collection<String> connectionInitSqls,
+ final Boolean defaultAutoCommit, final String defaultCatalog, final Integer defaultQueryTimeoutSeconds,
+ final Boolean defaultReadOnly, final int defaultTransactionIsolation,
+ final Collection<String> disconnectionSqlCodes, final boolean enableAutoCommitOnReturn,
+ final boolean fastFailValidation, final long maxConnLifetimeMillis, final int maxOpenPreparedStatements,
+ final boolean poolStatements, final boolean rollbackOnReturn, final String validationQuery,
+ final int validationQueryTimeoutSeconds) {
+ super();
+ this.cacheState = cacheState;
+ this.connectionInitSqls = connectionInitSqls;
+ this.defaultAutoCommit = defaultAutoCommit;
+ this.defaultCatalog = Strings.trimToNull(defaultCatalog);
+ this.defaultQueryTimeoutSeconds = defaultQueryTimeoutSeconds;
+ this.defaultReadOnly = defaultReadOnly;
+ this.defaultTransactionIsolation = defaultTransactionIsolation;
+ this.disconnectionSqlCodes = disconnectionSqlCodes;
+ this.autoCommitOnReturn = enableAutoCommitOnReturn;
+ this.fastFailValidation = fastFailValidation;
+ this.maxConnLifetimeMillis = maxConnLifetimeMillis;
+ this.maxOpenPreparedStatements = maxOpenPreparedStatements;
+ this.poolStatements = poolStatements;
+ this.rollbackOnReturn = rollbackOnReturn;
+ this.validationQuery = Strings.trimToNull(validationQuery);
+ this.validationQueryTimeoutSeconds = validationQueryTimeoutSeconds;
+ }
+
+ public void init(final PoolableConnectionFactory poolableConnectionFactory) {
+ if (poolableConnectionFactory != null) {
+ StatusLogger.getLogger().debug("Initializing PoolableConnectionFactory {} with {}",
+ poolableConnectionFactory, this);
+ poolableConnectionFactory.setCacheState(cacheState);
+ poolableConnectionFactory.setConnectionInitSql(connectionInitSqls);
+ poolableConnectionFactory.setDefaultAutoCommit(defaultAutoCommit);
+ poolableConnectionFactory.setDefaultCatalog(defaultCatalog);
+ poolableConnectionFactory.setDefaultQueryTimeout(defaultQueryTimeoutSeconds);
+ poolableConnectionFactory.setDefaultReadOnly(defaultReadOnly);
+ poolableConnectionFactory.setDefaultTransactionIsolation(defaultTransactionIsolation);
+ poolableConnectionFactory.setDisconnectionSqlCodes(disconnectionSqlCodes);
+ poolableConnectionFactory.setEnableAutoCommitOnReturn(autoCommitOnReturn);
+ poolableConnectionFactory.setFastFailValidation(fastFailValidation);
+ poolableConnectionFactory.setMaxConnLifetimeMillis(maxConnLifetimeMillis);
+ poolableConnectionFactory.setMaxOpenPreparedStatements(maxOpenPreparedStatements);
+ poolableConnectionFactory.setPoolStatements(poolStatements);
+ poolableConnectionFactory.setRollbackOnReturn(rollbackOnReturn);
+ poolableConnectionFactory.setValidationQuery(validationQuery);
+ poolableConnectionFactory.setValidationQueryTimeout(validationQueryTimeoutSeconds);
+ }
+
+ }
+
+ @Override
+ public String toString() {
+ return String.format(
+ "PoolableConnectionFactoryConfig [cacheState=%s, connectionInitSqls=%s, defaultAutoCommit=%s, defaultCatalog=%s, defaultQueryTimeoutSeconds=%s, defaultReadOnly=%s, defaultTransactionIsolation=%s, disconnectionSqlCodes=%s, enableAutoCommitOnReturn=%s, fastFailValidation=%s, maxConnLifetimeMillis=%s, maxOpenPreparedStatements=%s, poolStatements=%s, rollbackOnReturn=%s, validationQuery=%s, validationQueryTimeoutSeconds=%s]",
+ cacheState, connectionInitSqls, defaultAutoCommit, defaultCatalog, defaultQueryTimeoutSeconds,
+ defaultReadOnly, defaultTransactionIsolation, disconnectionSqlCodes, autoCommitOnReturn,
+ fastFailValidation, maxConnLifetimeMillis, maxOpenPreparedStatements, poolStatements, rollbackOnReturn,
+ validationQuery, validationQueryTimeoutSeconds);
+ }
+
+}
diff --git a/log4j-jdbc-dbcp2/src/main/java/org/apache/logging/log4j/dbcp2/appender/PoolingDriverConnectionSource.java b/log4j-jdbc-dbcp2/src/main/java/org/apache/logging/log4j/dbcp2/appender/PoolingDriverConnectionSource.java
index 347e7e3..a8da307 100644
--- a/log4j-jdbc-dbcp2/src/main/java/org/apache/logging/log4j/dbcp2/appender/PoolingDriverConnectionSource.java
+++ b/log4j-jdbc-dbcp2/src/main/java/org/apache/logging/log4j/dbcp2/appender/PoolingDriverConnectionSource.java
@@ -16,11 +16,6 @@
*/
package org.apache.logging.log4j.dbcp2.appender;
-import java.sql.DriverManager;
-import java.sql.SQLException;
-import java.util.Arrays;
-import java.util.concurrent.TimeUnit;
-
import org.apache.commons.dbcp2.ConnectionFactory;
import org.apache.commons.dbcp2.DriverManagerConnectionFactory;
import org.apache.commons.dbcp2.PoolableConnection;
@@ -30,10 +25,17 @@
import org.apache.commons.pool2.impl.GenericObjectPool;
import org.apache.logging.log4j.core.Core;
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.jdbc.appender.AbstractDriverManagerConnectionSource;
import org.apache.logging.log4j.jdbc.appender.ConnectionSource;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginBuilderAttribute;
+import org.apache.logging.log4j.plugins.PluginElement;
+import org.apache.logging.log4j.plugins.PluginFactory;
+
+import java.sql.DriverManager;
+import java.sql.SQLException;
+import java.util.Arrays;
+import java.util.concurrent.TimeUnit;
/**
* A {@link ConnectionSource} that uses a JDBC connection string, a user name, and a password to call
@@ -50,40 +52,50 @@
* This builder type or a subclass.
*/
public static class Builder<B extends Builder<B>> extends AbstractDriverManagerConnectionSource.Builder<B>
- implements org.apache.logging.log4j.core.util.Builder<PoolingDriverConnectionSource> {
+ implements org.apache.logging.log4j.plugins.util.Builder<PoolingDriverConnectionSource> {
public static final String DEFAULT_POOL_NAME = "example";
+
+ @PluginElement("PoolableConnectionFactoryConfig")
+ private PoolableConnectionFactoryConfig poolableConnectionFactoryConfig;
+
+ @PluginBuilderAttribute
private String poolName = DEFAULT_POOL_NAME;
@Override
- public PoolingDriverConnectionSource build() {
- try {
- return new PoolingDriverConnectionSource(getDriverClassName(), getConnectionString(), getUserName(),
- getPassword(), getProperties(), poolName);
- } catch (final SQLException e) {
- getLogger().error("Exception constructing {} to '{}' with {}", PoolingDriverConnectionSource.class,
- getConnectionString(), this, e);
- return null;
- }
+ public PoolingDriverConnectionSource build() {
+ try {
+ return new PoolingDriverConnectionSource(getDriverClassName(), getConnectionString(), getUserName(),
+ getPassword(), getProperties(), poolName, poolableConnectionFactoryConfig);
+ } catch (final SQLException e) {
+ getLogger().error("Exception constructing {} to '{}' with {}", PoolingDriverConnectionSource.class,
+ getConnectionString(), this, e);
+ return null;
+ }
+ }
+
+ public B setPoolableConnectionFactoryConfig(final PoolableConnectionFactoryConfig poolableConnectionFactoryConfig) {
+ this.poolableConnectionFactoryConfig = poolableConnectionFactoryConfig;
+ return asBuilder();
}
- public B setPoolName(final String poolName) {
+ public B setPoolName(final String poolName) {
this.poolName = poolName;
return asBuilder();
}
-
+
@Override
- public String toString() {
- return "Builder [poolName=" + poolName + ", connectionString=" + connectionString + ", driverClassName="
- + driverClassName + ", properties=" + Arrays.toString(properties) + ", userName="
- + Arrays.toString(userName) + "]";
- }
+ public String toString() {
+ return "Builder [poolName=" + poolName + ", connectionString=" + connectionString + ", driverClassName="
+ + driverClassName + ", properties=" + Arrays.toString(properties) + ", userName="
+ + Arrays.toString(userName) + "]";
+ }
}
public static final String URL_PREFIX = "jdbc:apache:commons:dbcp:";
// This method is not named newBuilder() to make the compiler happy.
- @PluginBuilderFactory
+ @PluginFactory
public static <B extends Builder<B>> B newPoolingDriverConnectionSourceBuilder() {
return new Builder<B>().asBuilder();
}
@@ -92,12 +104,25 @@
private final String poolName;
+ /**
+ * @deprecated Use {@link #newPoolingDriverConnectionSourceBuilder()}.
+ */
+ @Deprecated
public PoolingDriverConnectionSource(final String driverClassName, final String connectionString,
final char[] userName, final char[] password, final Property[] properties, final String poolName)
throws SQLException {
super(driverClassName, connectionString, URL_PREFIX + poolName, userName, password, properties);
this.poolName = poolName;
- setupDriver(connectionString);
+ setupDriver(connectionString, null);
+ }
+
+ private PoolingDriverConnectionSource(final String driverClassName, final String connectionString,
+ final char[] userName, final char[] password, final Property[] properties, final String poolName,
+ final PoolableConnectionFactoryConfig poolableConnectionFactoryConfig)
+ throws SQLException {
+ super(driverClassName, connectionString, URL_PREFIX + poolName, userName, password, properties);
+ this.poolName = poolName;
+ setupDriver(connectionString, poolableConnectionFactoryConfig);
}
@Override
@@ -114,7 +139,8 @@
return driver;
}
- private void setupDriver(final String connectionString) throws SQLException {
+ private void setupDriver(final String connectionString,
+ final PoolableConnectionFactoryConfig poolableConnectionFactoryConfig) throws SQLException {
//
// First, we'll create a ConnectionFactory that the
// pool will use to create Connections.
@@ -122,10 +148,10 @@
// using the connect string passed in the command line
// arguments.
//
- final Property[] properties = getProperties();
- final char[] userName = getUserName();
- final char[] password = getPassword();
- final ConnectionFactory connectionFactory;
+ final Property[] properties = getProperties();
+ final char[] userName = getUserName();
+ final char[] password = getPassword();
+ final ConnectionFactory connectionFactory;
if (properties != null && properties.length > 0) {
if (userName != null || password != null) {
throw new SQLException("Either set the userName and password, or set the Properties, but not both.");
@@ -142,6 +168,9 @@
//
final PoolableConnectionFactory poolableConnectionFactory = new PoolableConnectionFactory(connectionFactory,
null);
+ if (poolableConnectionFactoryConfig != null) {
+ poolableConnectionFactoryConfig.init(poolableConnectionFactory);
+ }
//
// Now we'll need a ObjectPool that serves as the
@@ -151,7 +180,7 @@
// any ObjectPool implementation will suffice.
//
@SuppressWarnings("resource")
- // This GenericObjectPool will be closed on shutown
+ // This GenericObjectPool will be closed on shutdown
final ObjectPool<PoolableConnection> connectionPool = new GenericObjectPool<>(poolableConnectionFactory);
// Set the factory's pool property to the owning pool
@@ -160,7 +189,7 @@
loadDriver(poolingDriverClassName);
final PoolingDriver driver = getPoolingDriver();
if (driver != null) {
- getLogger().debug("Registering DBCP pool '{}'", poolName);
+ getLogger().debug("Registering DBCP pool '{}' with pooling driver {}: {}", poolName, driver, connectionPool);
driver.registerPool(poolName, connectionPool);
}
//
@@ -170,15 +199,15 @@
}
@Override
- public boolean stop(long timeout, TimeUnit timeUnit) {
+ public boolean stop(final long timeout, final TimeUnit timeUnit) {
try {
final PoolingDriver driver = getPoolingDriver();
if (driver != null) {
- getLogger().debug("Closing DBCP pool '{}'", poolName);
+ getLogger().debug("Driver {} closing DBCP pool '{}'", driver, poolName);
driver.closePool(poolName);
}
return true;
- } catch (Exception e) {
+ } catch (final Exception e) {
getLogger().error("Exception stopping connection source for '{}' → '{}'", getConnectionString(),
getActualConnectionString(), e);
return false;
diff --git a/log4j-jdbc-dbcp2/src/test/java/org/apache/logging/log4j/dbcp2/appender/PoolableConnectionFactoryTest.java b/log4j-jdbc-dbcp2/src/test/java/org/apache/logging/log4j/dbcp2/appender/PoolableConnectionFactoryTest.java
new file mode 100644
index 0000000..1bb333d
--- /dev/null
+++ b/log4j-jdbc-dbcp2/src/test/java/org/apache/logging/log4j/dbcp2/appender/PoolableConnectionFactoryTest.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.dbcp2.appender;
+
+import org.apache.logging.log4j.core.Appender;
+import org.apache.logging.log4j.junit.LoggerContextRule;
+import org.junit.Assert;
+import org.junit.ClassRule;
+import org.junit.Test;
+
+public class PoolableConnectionFactoryTest {
+
+ private static final String REL_PATH = "src/test/resources/log4j2-jdbc-dbcp2.xml";
+
+ @ClassRule
+ public static final LoggerContextRule LCR = LoggerContextRule.createShutdownTimeoutLoggerContextRule(REL_PATH);
+
+ @Test
+ public void test() {
+ Appender appender = LCR.getAppender("databaseAppender");
+ Assert.assertNotNull("Problem loading configuration from " + REL_PATH, appender);
+ }
+}
diff --git a/log4j-jdbc-dbcp2/src/test/java/org/apache/logging/log4j/dbcp2/appender/PoolingDriverConnectionSourceTest.java b/log4j-jdbc-dbcp2/src/test/java/org/apache/logging/log4j/dbcp2/appender/PoolingDriverConnectionSourceTest.java
index d8ffd5c..4e8d36d 100644
--- a/log4j-jdbc-dbcp2/src/test/java/org/apache/logging/log4j/dbcp2/appender/PoolingDriverConnectionSourceTest.java
+++ b/log4j-jdbc-dbcp2/src/test/java/org/apache/logging/log4j/dbcp2/appender/PoolingDriverConnectionSourceTest.java
@@ -45,7 +45,7 @@
};
// @formatter:off
final PoolingDriverConnectionSource source = PoolingDriverConnectionSource.newPoolingDriverConnectionSourceBuilder()
- .setConnectionString(JdbcH2TestHelper.CONNECTION_STRING)
+ .setConnectionString(JdbcH2TestHelper.CONNECTION_STRING_MEM)
.setProperties(properties)
.build();
openAndClose(source);
@@ -61,7 +61,7 @@
};
// @formatter:off
final PoolingDriverConnectionSource source = PoolingDriverConnectionSource.newPoolingDriverConnectionSourceBuilder()
- .setConnectionString(JdbcH2TestHelper.CONNECTION_STRING)
+ .setConnectionString(JdbcH2TestHelper.CONNECTION_STRING_MEM)
.setProperties(properties)
.setPoolName("MyPoolName")
.build();
@@ -73,7 +73,7 @@
public void testH2UserAndPassword() throws SQLException {
// @formatter:off
final PoolingDriverConnectionSource source = PoolingDriverConnectionSource.newPoolingDriverConnectionSourceBuilder()
- .setConnectionString(JdbcH2TestHelper.CONNECTION_STRING)
+ .setConnectionString(JdbcH2TestHelper.CONNECTION_STRING_MEM)
.setUserName(JdbcH2TestHelper.USER_NAME.toCharArray())
.setPassword(JdbcH2TestHelper.PASSWORD.toCharArray())
.build();
@@ -84,11 +84,26 @@
public void testH2UserPasswordAndPoolName() throws SQLException {
// @formatter:off
final PoolingDriverConnectionSource source = PoolingDriverConnectionSource.newPoolingDriverConnectionSourceBuilder()
- .setConnectionString(JdbcH2TestHelper.CONNECTION_STRING)
+ .setConnectionString(JdbcH2TestHelper.CONNECTION_STRING_MEM)
.setUserName(JdbcH2TestHelper.USER_NAME.toCharArray())
.setPassword(JdbcH2TestHelper.PASSWORD.toCharArray())
.setPoolName("MyPoolName")
.build();
openAndClose(source);
}
+
+ @Test
+ public void testPoolableConnectionFactoryConfig() throws SQLException {
+ PoolableConnectionFactoryConfig poolableConnectionFactoryConfig = PoolableConnectionFactoryConfig.newBuilder().setMaxConnLifetimeMillis(30000).build();
+ // @formatter:off
+ final PoolingDriverConnectionSource source = PoolingDriverConnectionSource.newPoolingDriverConnectionSourceBuilder()
+ .setConnectionString(JdbcH2TestHelper.CONNECTION_STRING_MEM)
+ .setUserName(JdbcH2TestHelper.USER_NAME.toCharArray())
+ .setPassword(JdbcH2TestHelper.PASSWORD.toCharArray())
+ .setPoolName("MyPoolName")
+ .setPoolableConnectionFactoryConfig(poolableConnectionFactoryConfig)
+ .build();
+ // @formatter:on
+ openAndClose(source);
+ }
}
diff --git a/log4j-jdbc-dbcp2/src/test/resources/log4j2-jdbc-dbcp2.xml b/log4j-jdbc-dbcp2/src/test/resources/log4j2-jdbc-dbcp2.xml
new file mode 100644
index 0000000..4c8aadb
--- /dev/null
+++ b/log4j-jdbc-dbcp2/src/test/resources/log4j2-jdbc-dbcp2.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="UTF-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 "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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="STDOUT">
+ <PatternLayout pattern="%C{1.} %m %level MDC%X%n"/>
+ </Console>
+ <Jdbc name="databaseAppender" tableName="TableNameA" ignoreExceptions="false">
+ <PoolingDriver connectionString="jdbc:h2:${sys:java.io.tmpdir}/h2/h2_test0;TRACE_LEVEL_SYSTEM_OUT=0" driverClassName="org.h2.Driver">
+ <!-- PoolableConnectionFactory default values -->
+ <PoolableConnectionFactory
+ autoCommitOnReturn="true"
+ cacheState="false"
+ defaultAutoCommit="false"
+ defaultCatalog=""
+ defaultQueryTimeoutSeconds="0"
+ defaultReadOnly="false"
+ defaultTransactionIsolation="-1"
+ fastFailValidation="true"
+ maxConnLifetimeMillis="-1"
+ maxOpenPreparedStatements="8"
+ poolStatements="false"
+ rollbackOnReturn="true"
+ validationQuery=""
+ validationQueryTimeoutSeconds="-1">
+ </PoolableConnectionFactory>
+ </PoolingDriver>
+ <ColumnMapping name="ColumnA" />
+ </Jdbc>
+ </Appenders>
+
+ <Loggers>
+ <Logger name="org.apache.logging.log4j.core.appender.db.jdbc.JdbcAppenderColumnMappingLiteralTest" level="DEBUG" additivity="false">
+ <AppenderRef ref="databaseAppender" />
+ </Logger>
+
+ <Root level="FATAL">
+ <AppenderRef ref="STDOUT"/>
+ </Root>
+ </Loggers>
+
+</Configuration>
diff --git a/log4j-jdbc/pom.xml b/log4j-jdbc/pom.xml
index e56c2cd..9f06def 100644
--- a/log4j-jdbc/pom.xml
+++ b/log4j-jdbc/pom.xml
@@ -70,6 +70,11 @@
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
+ <dependency>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>commons-lang3</artifactId>
+ <scope>test</scope>
+ </dependency>
</dependencies>
<build>
@@ -156,6 +161,7 @@
<!-- module link generation is completely broken in the javadoc plugin for a multi-module non-aggregating project -->
<detectOfflineLinks>false</detectOfflineLinks>
<linksource>true</linksource>
+ <source>8</source>
</configuration>
<reportSets>
<reportSet>
diff --git a/log4j-jdbc/revapi.json b/log4j-jdbc/revapi.json
new file mode 100644
index 0000000..7c413e9
--- /dev/null
+++ b/log4j-jdbc/revapi.json
@@ -0,0 +1,23 @@
+[
+ {
+ "extension": "revapi.java",
+ "configuration": {
+ "filter": {
+ "classes": {
+ "exclude": [
+ ]
+ }
+ }
+ }
+ },
+ {
+ "extension": "revapi.ignore",
+ "configuration": [
+ {
+ "code": "java.class.removed",
+ "old": "class org.apache.logging.log4j.core.appender.db.jdbc.PoolingDriverConnectionSource",
+ "justification": "Refactored to log4j-jdbc-dbcp2"
+ }
+ ]
+ }
+]
\ No newline at end of file
diff --git a/log4j-jdbc/src/main/java/org/apache/logging/log4j/jdbc/appender/AbstractDriverManagerConnectionSource.java b/log4j-jdbc/src/main/java/org/apache/logging/log4j/jdbc/appender/AbstractDriverManagerConnectionSource.java
index 6258943..2b442e5 100644
--- a/log4j-jdbc/src/main/java/org/apache/logging/log4j/jdbc/appender/AbstractDriverManagerConnectionSource.java
+++ b/log4j-jdbc/src/main/java/org/apache/logging/log4j/jdbc/appender/AbstractDriverManagerConnectionSource.java
@@ -23,9 +23,9 @@
import org.apache.logging.log4j.Logger;
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.PluginElement;
-import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required;
+import org.apache.logging.log4j.plugins.PluginBuilderAttribute;
+import org.apache.logging.log4j.plugins.PluginElement;
+import org.apache.logging.log4j.plugins.validation.constraints.Required;
import org.apache.logging.log4j.status.StatusLogger;
/**
diff --git a/log4j-jdbc/src/main/java/org/apache/logging/log4j/jdbc/appender/ColumnConfig.java b/log4j-jdbc/src/main/java/org/apache/logging/log4j/jdbc/appender/ColumnConfig.java
index 6d1e2ca..16d744c 100644
--- a/log4j-jdbc/src/main/java/org/apache/logging/log4j/jdbc/appender/ColumnConfig.java
+++ b/log4j-jdbc/src/main/java/org/apache/logging/log4j/jdbc/appender/ColumnConfig.java
@@ -20,13 +20,12 @@
import org.apache.logging.log4j.core.Core;
import org.apache.logging.log4j.core.appender.db.ColumnMapping;
import org.apache.logging.log4j.core.config.Configuration;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
-import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
-import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required;
import org.apache.logging.log4j.core.layout.PatternLayout;
-import org.apache.logging.log4j.core.util.Booleans;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginBuilderAttribute;
+import org.apache.logging.log4j.plugins.PluginFactory;
+import org.apache.logging.log4j.plugins.validation.constraints.Required;
import org.apache.logging.log4j.status.StatusLogger;
import org.apache.logging.log4j.util.Strings;
@@ -37,91 +36,7 @@
*/
@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<ColumnConfig> {
+ public static class Builder implements org.apache.logging.log4j.plugins.util.Builder<ColumnConfig> {
@PluginConfiguration
private Configuration configuration;
@@ -145,79 +60,6 @@
@PluginBuilderAttribute
private boolean isClob;
- /**
- * The configuration object.
- *
- * @return this.
- */
- public Builder setConfiguration(final Configuration configuration) {
- this.configuration = configuration;
- return this;
- }
-
- /**
- * The name of the database column as it exists within the database table.
- *
- * @return this.
- */
- public Builder setName(final String name) {
- this.name = name;
- 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 setPattern(final String pattern) {
- this.pattern = pattern;
- 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.
- */
- public Builder setLiteral(final String literal) {
- this.literal = literal;
- 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 setEventTimestamp(final boolean eventTimestamp) {
- isEventTimestamp = eventTimestamp;
- return this;
- }
-
- /**
- * If {@code "true"}, indicates that the column is a Unicode String.
- *
- * @return this.
- */
- public Builder setUnicode(final boolean unicode) {
- isUnicode = unicode;
- return this;
- }
-
- /**
- * If {@code "true"}, indicates that the column is a character LOB (CLOB).
- *
- * @return this.
- */
- public Builder setClob(final boolean clob) {
- isClob = clob;
- return this;
- }
-
@Override
public ColumnConfig build() {
if (Strings.isEmpty(name)) {
@@ -244,9 +86,9 @@
if (isPattern) {
final PatternLayout layout =
PatternLayout.newBuilder()
- .withPattern(pattern)
- .withConfiguration(configuration)
- .withAlwaysWriteExceptions(false)
+ .setPattern(pattern)
+ .setConfiguration(configuration)
+ .setAlwaysWriteExceptions(false)
.build();
return new ColumnConfig(name, layout, null, false, isUnicode, isClob);
}
@@ -254,5 +96,139 @@
LOGGER.error("To configure a column you must specify a pattern or literal or set isEventDate to true.");
return null;
}
+
+ /**
+ * If {@code "true"}, indicates that the column is a character LOB (CLOB).
+ *
+ * @return this.
+ */
+ public Builder setClob(final boolean clob) {
+ isClob = clob;
+ return this;
+ }
+
+ /**
+ * The configuration object.
+ *
+ * @return this.
+ */
+ public Builder setConfiguration(final Configuration configuration) {
+ this.configuration = configuration;
+ 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 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.
+ */
+ public Builder setLiteral(final String literal) {
+ this.literal = literal;
+ return this;
+ }
+
+ /**
+ * The name of the database column as it exists within the database table.
+ *
+ * @return this.
+ */
+ public Builder setName(final String name) {
+ this.name = name;
+ 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 setPattern(final String pattern) {
+ this.pattern = pattern;
+ return this;
+ }
+
+ /**
+ * If {@code "true"}, indicates that the column is a Unicode String.
+ *
+ * @return this.
+ */
+ public Builder setUnicode(final boolean unicode) {
+ isUnicode = unicode;
+ return this;
+ }
+ }
+
+ private static final Logger LOGGER = StatusLogger.getLogger();
+ @PluginFactory
+ public static Builder newBuilder() {
+ return new Builder();
+ }
+ private final String columnName;
+ private final String columnNameKey;
+ 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.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-jdbc/src/main/java/org/apache/logging/log4j/jdbc/appender/ConnectionSource.java b/log4j-jdbc/src/main/java/org/apache/logging/log4j/jdbc/appender/ConnectionSource.java
index 6c05896..bb9becf 100644
--- a/log4j-jdbc/src/main/java/org/apache/logging/log4j/jdbc/appender/ConnectionSource.java
+++ b/log4j-jdbc/src/main/java/org/apache/logging/log4j/jdbc/appender/ConnectionSource.java
@@ -25,7 +25,7 @@
* 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-jdbc/src/main/java/org/apache/logging/log4j/jdbc/appender/DataSourceConnectionSource.java b/log4j-jdbc/src/main/java/org/apache/logging/log4j/jdbc/appender/DataSourceConnectionSource.java
index eaf5750..496902c 100644
--- a/log4j-jdbc/src/main/java/org/apache/logging/log4j/jdbc/appender/DataSourceConnectionSource.java
+++ b/log4j-jdbc/src/main/java/org/apache/logging/log4j/jdbc/appender/DataSourceConnectionSource.java
@@ -25,9 +25,9 @@
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.core.Core;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
-import org.apache.logging.log4j.core.config.plugins.PluginFactory;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginAttribute;
+import org.apache.logging.log4j.plugins.PluginFactory;
import org.apache.logging.log4j.status.StatusLogger;
import org.apache.logging.log4j.util.Strings;
@@ -64,7 +64,7 @@
* @return the created connection source.
*/
@PluginFactory
- public static DataSourceConnectionSource createConnectionSource(@PluginAttribute("jndiName") final String jndiName) {
+ public static DataSourceConnectionSource createConnectionSource(@PluginAttribute final String jndiName) {
if (Strings.isEmpty(jndiName)) {
LOGGER.error("No JNDI name provided.");
return null;
diff --git a/log4j-jdbc/src/main/java/org/apache/logging/log4j/jdbc/appender/DriverManagerConnectionSource.java b/log4j-jdbc/src/main/java/org/apache/logging/log4j/jdbc/appender/DriverManagerConnectionSource.java
index 93760d1..cfc1ff9 100644
--- a/log4j-jdbc/src/main/java/org/apache/logging/log4j/jdbc/appender/DriverManagerConnectionSource.java
+++ b/log4j-jdbc/src/main/java/org/apache/logging/log4j/jdbc/appender/DriverManagerConnectionSource.java
@@ -16,12 +16,12 @@
*/
package org.apache.logging.log4j.jdbc.appender;
-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;
-import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginFactory;
+
+import java.sql.DriverManager;
/**
* A {@link ConnectionSource} that uses a JDBC connection string, a user name, and a password to call
@@ -41,7 +41,7 @@
* This builder type or a subclass.
*/
public static class Builder<B extends Builder<B>> extends AbstractDriverManagerConnectionSource.Builder<B>
- implements org.apache.logging.log4j.core.util.Builder<DriverManagerConnectionSource> {
+ implements org.apache.logging.log4j.plugins.util.Builder<DriverManagerConnectionSource> {
@Override
public DriverManagerConnectionSource build() {
@@ -51,7 +51,7 @@
}
- @PluginBuilderFactory
+ @PluginFactory
public static <B extends Builder<B>> B newBuilder() {
return new Builder<B>().asBuilder();
}
diff --git a/log4j-jdbc/src/main/java/org/apache/logging/log4j/jdbc/appender/FactoryMethodConnectionSource.java b/log4j-jdbc/src/main/java/org/apache/logging/log4j/jdbc/appender/FactoryMethodConnectionSource.java
index a082e61..5e29666 100644
--- a/log4j-jdbc/src/main/java/org/apache/logging/log4j/jdbc/appender/FactoryMethodConnectionSource.java
+++ b/log4j-jdbc/src/main/java/org/apache/logging/log4j/jdbc/appender/FactoryMethodConnectionSource.java
@@ -20,16 +20,15 @@
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.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginAttribute;
+import org.apache.logging.log4j.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;
/**
@@ -80,7 +79,7 @@
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);
diff --git a/log4j-jdbc/src/main/java/org/apache/logging/log4j/jdbc/appender/JdbcAppender.java b/log4j-jdbc/src/main/java/org/apache/logging/log4j/jdbc/appender/JdbcAppender.java
index d68556c..8ea3bcc 100644
--- a/log4j-jdbc/src/main/java/org/apache/logging/log4j/jdbc/appender/JdbcAppender.java
+++ b/log4j-jdbc/src/main/java/org/apache/logging/log4j/jdbc/appender/JdbcAppender.java
@@ -16,11 +16,6 @@
*/
package org.apache.logging.log4j.jdbc.appender;
-import java.io.Serializable;
-import java.sql.PreparedStatement;
-import java.util.Arrays;
-import java.util.Objects;
-
import org.apache.logging.log4j.core.Appender;
import org.apache.logging.log4j.core.Core;
import org.apache.logging.log4j.core.Filter;
@@ -28,14 +23,18 @@
import org.apache.logging.log4j.core.appender.AbstractAppender;
import org.apache.logging.log4j.core.appender.db.AbstractDatabaseAppender;
import org.apache.logging.log4j.core.appender.db.ColumnMapping;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
-import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
-import org.apache.logging.log4j.core.config.plugins.PluginElement;
-import org.apache.logging.log4j.core.config.plugins.convert.TypeConverter;
-import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required;
-import org.apache.logging.log4j.core.util.Assert;
-import org.apache.logging.log4j.core.util.Booleans;
+import org.apache.logging.log4j.core.config.Property;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginBuilderAttribute;
+import org.apache.logging.log4j.plugins.PluginElement;
+import org.apache.logging.log4j.plugins.PluginFactory;
+import org.apache.logging.log4j.plugins.convert.TypeConverter;
+import org.apache.logging.log4j.plugins.validation.constraints.Required;
+import org.apache.logging.log4j.util.Assert;
+
+import java.io.Serializable;
+import java.sql.PreparedStatement;
+import java.util.Arrays;
/**
* This Appender writes logging events to a relational database using standard JDBC mechanisms. It takes a list of
@@ -52,63 +51,17 @@
@Plugin(name = "JDBC", category = Core.CATEGORY_NAME, elementType = Appender.ELEMENT_TYPE, printObject = true)
public final class JdbcAppender extends AbstractDatabaseAppender<JdbcDatabaseManager> {
- private final String description;
-
- private JdbcAppender(final String name, final Filter filter, final Layout<? extends Serializable> 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 <B extends Builder<B>> 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.<B>newBuilder()
- .setBufferSize(bufferSizeInt)
- .setColumnConfigs(columnConfigs)
- .setConnectionSource(connectionSource)
- .setTableName(tableName)
- .withName(name)
- .withIgnoreExceptions(ignoreExceptions)
- .withFilter(filter)
- .build();
- }
-
- @PluginBuilderFactory
- public static <B extends Builder<B>> B newBuilder() {
- return new Builder<B>().asBuilder();
- }
-
public static class Builder<B extends Builder<B>> extends AbstractAppender.Builder<B>
- implements org.apache.logging.log4j.core.util.Builder<JdbcAppender> {
+ implements org.apache.logging.log4j.plugins.util.Builder<JdbcAppender> {
@PluginElement("ConnectionSource")
@Required(message = "No ConnectionSource provided")
private ConnectionSource connectionSource;
@PluginBuilderAttribute
+ private boolean immediateFail;
+
+ @PluginBuilderAttribute
private int bufferSize;
@PluginBuilderAttribute
@@ -121,20 +74,46 @@
@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
+ 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,18 +122,10 @@
}
/**
- * The name of the database table to insert log events into.
- *
- * @return this
- */
- public B setTableName(final String tableName) {
- this.tableName = tableName;
- return asBuilder();
- }
-
- /**
* Information about the columns that log event data should be inserted into and how to insert that data.
- *
+ *
+ * @param columnConfigs Column configurations.
+ *
* @return this
*/
public B setColumnConfigs(final ColumnConfig... columnConfigs) {
@@ -167,22 +138,60 @@
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);
+ /**
+ * The connections source from which database connections should be retrieved.
+ *
+ * @param connectionSource The connections source.
+ *
+ * @return this
+ */
+ public B setConnectionSource(final ConnectionSource connectionSource) {
+ this.connectionSource = connectionSource;
+ return asBuilder();
}
+ 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();
+ }
+
+ public B setTruncateStrings(final boolean truncateStrings) {
+ this.truncateStrings = truncateStrings;
+ return asBuilder();
+ }
+
+ }
+
+ @PluginFactory
+ public static <B extends Builder<B>> B newBuilder() {
+ return new Builder<B>().asBuilder();
+ }
+
+ private final String description;
+
+ private JdbcAppender(final String name, final Filter filter, final Layout<? extends Serializable> layout,
+ final boolean ignoreExceptions, 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-jdbc/src/main/java/org/apache/logging/log4j/jdbc/appender/JdbcDatabaseManager.java b/log4j-jdbc/src/main/java/org/apache/logging/log4j/jdbc/appender/JdbcDatabaseManager.java
index 2afcaf7..4e9239d 100644
--- a/log4j-jdbc/src/main/java/org/apache/logging/log4j/jdbc/appender/JdbcDatabaseManager.java
+++ b/log4j-jdbc/src/main/java/org/apache/logging/log4j/jdbc/appender/JdbcDatabaseManager.java
@@ -23,13 +23,20 @@
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;
@@ -38,13 +45,14 @@
import org.apache.logging.log4j.core.appender.ManagerFactory;
import org.apache.logging.log4j.core.appender.db.AbstractDatabaseManager;
import org.apache.logging.log4j.core.appender.db.ColumnMapping;
-import org.apache.logging.log4j.core.config.plugins.convert.TypeConverters;
+import org.apache.logging.log4j.core.appender.db.DbAppenderLoggingException;
import org.apache.logging.log4j.core.util.Closer;
+import org.apache.logging.log4j.core.util.Log4jThread;
import org.apache.logging.log4j.jdbc.convert.DateTypeConverter;
import org.apache.logging.log4j.message.MapMessage;
+import org.apache.logging.log4j.plugins.convert.TypeConverters;
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,242 +62,316 @@
*/
public final class JdbcDatabaseManager extends AbstractDatabaseManager {
- private static StatusLogger logger() {
- return StatusLogger.getLogger();
+ /**
+ * Encapsulates data that {@link JdbcDatabaseManagerFactory} uses to create managers.
+ */
+ private static final class FactoryData extends AbstractDatabaseManager.AbstractFactoryData {
+ private final ConnectionSource connectionSource;
+ private final String tableName;
+ private final ColumnConfig[] columnConfigs;
+ private final ColumnMapping[] columnMappings;
+ private final boolean immediateFail;
+ private final boolean retry;
+ private final long reconnectIntervalMillis;
+ private final boolean truncateStrings;
+
+ protected FactoryData(final int bufferSize, final Layout<? extends Serializable> 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);
+ }
+ }
+
+ /**
+ * Creates managers.
+ */
+ private static final class JdbcDatabaseManagerFactory implements ManagerFactory<JdbcDatabaseManager, FactoryData> {
+
+ private static final char PARAMETER_MARKER = '?';
+
+ @Override
+ public JdbcDatabaseManager createManager(final String name, final FactoryData data) {
+ final StringBuilder sb = new StringBuilder("insert into ").append(data.tableName).append(" (");
+ // so this gets a little more complicated now that there are two ways to configure column mappings, but
+ // both mappings follow the same exact pattern for the prepared statement
+ 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<ColumnConfig> 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();
+
+ return new JdbcDatabaseManager(name, sqlStatement, columnConfigs, data);
+ }
+ }
+
+ /**
+ * Handles reconnecting to JDBC once on a Thread.
+ */
+ private final class Reconnector extends Log4jThread {
+
+ private final CountDownLatch latch = new CountDownLatch(1);
+ private volatile boolean shutdown;
+
+ private Reconnector() {
+ super("JdbcDatabaseManager-Reconnector");
+ }
+
+ public void latch() {
+ try {
+ latch.await();
+ } catch (final InterruptedException ex) {
+ // Ignore the exception.
+ }
+ }
+
+ void reconnect() throws SQLException {
+ closeResources(false);
+ connectAndPrepare();
+ reconnector = null;
+ shutdown = true;
+ logger().debug("Connection reestablished to {}", factoryData);
+ }
+
+ @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();
+ }
+ }
+ }
+
+ public void shutdown() {
+ shutdown = true;
+ }
+
+ @Override
+ public String toString() {
+ return String.format("Reconnector [latch=%s, shutdown=%s]", latch, shutdown);
+ }
+
+ }
+
+ private static final class ResultSetColumnMetaData {
+
+ 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;
+
+ 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) {
+ super();
+ 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;
+ }
}
private static final JdbcDatabaseManagerFactory INSTANCE = new JdbcDatabaseManagerFactory();
- // NOTE: prepared statements are prepared in this order: column mappings, then column configs
- private final List<ColumnMapping> columnMappings;
- private final List<ColumnConfig> columnConfigs;
- private final ConnectionSource connectionSource;
- private final String sqlStatement;
-
- private Connection connection;
- private PreparedStatement statement;
- private boolean isBatchSupported;
-
- private JdbcDatabaseManager(final String name, final int bufferSize, final ConnectionSource connectionSource,
- final String sqlStatement, final List<ColumnConfig> columnConfigs,
- final List<ColumnMapping> columnMappings) {
- super(name, bufferSize);
- this.connectionSource = connectionSource;
- this.sqlStatement = sqlStatement;
- this.columnConfigs = columnConfigs;
- this.columnMappings = columnMappings;
- }
-
- @Override
- protected void startupInternal() throws Exception {
- this.connection = this.connectionSource.getConnection();
- final DatabaseMetaData metaData = this.connection.getMetaData();
- this.isBatchSupported = metaData.supportsBatchUpdates();
- logger().debug("Closing Connection {}", this.connection);
- Closer.closeSilently(this.connection);
- }
-
- @Override
- protected boolean shutdownInternal() {
- if (this.connection != null || this.statement != null) {
- return this.commitAndClose();
+ private static void appendColumnName(final int i, final String columnName, final StringBuilder sb) {
+ if (i > 1) {
+ sb.append(',');
}
- if (connectionSource != null) {
- connectionSource.stop();
- }
- return true;
- }
-
- @Override
- protected void connectAndStart() {
- try {
- this.connection = this.connectionSource.getConnection();
- this.connection.setAutoCommit(false);
- logger().debug("Preparing SQL: {}", this.sqlStatement);
- this.statement = this.connection.prepareStatement(this.sqlStatement);
- } catch (final SQLException e) {
- throw new AppenderLoggingException(
- "Cannot write logging event or flush buffer; JDBC manager cannot connect to the database.", e);
- }
- }
-
- @Deprecated
- @Override
- protected void writeInternal(final LogEvent event) {
- writeInternal(event, null);
- }
-
- private void setFields(final MapMessage<?, ?> mapMessage) throws SQLException {
- final IndexedReadOnlyStringMap map = mapMessage.getIndexedReadOnlyStringMap();
- final String simpleName = statement.getClass().getName();
- int i = 1; // JDBC indices start at 1
- for (final ColumnMapping mapping : this.columnMappings) {
- final String source = mapping.getSource();
- final String key = Strings.isEmpty(source) ? mapping.getName() : source;
- final Object value = map.getValue(key);
- if (logger().isTraceEnabled()) {
- final String valueStr = value instanceof String ? "\"" + value + "\"" : Objects.toString(value, null);
- logger().trace("{} setObject({}, {}) for key '{}' and mapping '{}'", simpleName, i, valueStr, key,
- mapping.getName());
- }
- statement.setObject(i++, value);
- }
- }
-
- @Override
- protected void writeInternal(final LogEvent event, final Serializable serializable) {
- StringReader reader = null;
- try {
- if (!this.isRunning() || this.connection == null || this.connection.isClosed() || this.statement == null
- || this.statement.isClosed()) {
- throw new AppenderLoggingException(
- "Cannot write logging event; JDBC manager not connected to the database.");
- }
-
- if (serializable instanceof MapMessage) {
- setFields((MapMessage<?, ?>) serializable);
- }
- int i = 1; // JDBC indices start at 1
- for (final ColumnMapping mapping : this.columnMappings) {
- if (ThreadContextMap.class.isAssignableFrom(mapping.getType())
- || ReadOnlyStringMap.class.isAssignableFrom(mapping.getType())) {
- this.statement.setObject(i++, event.getContextData().toMap());
- } else if (ThreadContextStack.class.isAssignableFrom(mapping.getType())) {
- this.statement.setObject(i++, event.getContextStack().asList());
- } else if (Date.class.isAssignableFrom(mapping.getType())) {
- this.statement.setObject(i++, DateTypeConverter.fromMillis(event.getTimeMillis(),
- mapping.getType().asSubclass(Date.class)));
- } else {
- StringLayout layout = mapping.getLayout();
- if (layout != null) {
- if (Clob.class.isAssignableFrom(mapping.getType())) {
- this.statement.setClob(i++, new StringReader(layout.toSerializable(event)));
- } else if (NClob.class.isAssignableFrom(mapping.getType())) {
- this.statement.setNClob(i++, new StringReader(layout.toSerializable(event)));
- } else {
- final Object value = TypeConverters.convert(layout.toSerializable(event), mapping.getType(),
- null);
- if (value == null) {
- this.statement.setNull(i++, Types.NULL);
- } else {
- this.statement.setObject(i++, value);
- }
- }
- }
- }
- }
- for (final ColumnConfig column : this.columnConfigs) {
- if (column.isEventTimestamp()) {
- this.statement.setTimestamp(i++, new Timestamp(event.getTimeMillis()));
- } else if (column.isClob()) {
- reader = new StringReader(column.getLayout().toSerializable(event));
- if (column.isUnicode()) {
- this.statement.setNClob(i++, reader);
- } else {
- this.statement.setClob(i++, reader);
- }
- } else if (column.isUnicode()) {
- this.statement.setNString(i++, column.getLayout().toSerializable(event));
- } else {
- this.statement.setString(i++, column.getLayout().toSerializable(event));
- }
- }
-
- if (this.isBatchSupported) {
- this.statement.addBatch();
- } else if (this.statement.executeUpdate() == 0) {
- throw new AppenderLoggingException(
- "No records inserted in database table for log event in JDBC manager.");
- }
- } catch (final SQLException e) {
- throw new AppenderLoggingException("Failed to insert record for log event in JDBC manager: " +
- e.getMessage(), e);
- } finally {
- Closer.closeSilently(reader);
- }
- }
-
- @Override
- protected boolean commitAndClose() {
- boolean closed = true;
- try {
- if (this.connection != null && !this.connection.isClosed()) {
- if (this.isBatchSupported) {
- logger().debug("Executing batch PreparedStatement {}", this.statement);
- this.statement.executeBatch();
- }
- logger().debug("Committing Connection {}", this.connection);
- this.connection.commit();
- }
- } catch (final SQLException e) {
- throw new AppenderLoggingException("Failed to commit transaction logging event or flushing buffer.", e);
- } finally {
- try {
- logger().debug("Closing PreparedStatement {}", this.statement);
- Closer.close(this.statement);
- } catch (final Exception e) {
- logWarn("Failed to close SQL statement logging event or flushing buffer", e);
- closed = false;
- } finally {
- this.statement = null;
- }
-
- try {
- logger().debug("Closing Connection {}", this.connection);
- Closer.close(this.connection);
- } catch (final Exception e) {
- logWarn("Failed to close database connection logging event or flushing buffer", e);
- closed = false;
- } finally {
- this.connection = null;
- }
- }
- return closed;
+ sb.append(columnName);
}
/**
- * Creates a JDBC manager for use within the {@link JdbcAppender}, or returns a suitable one if it already exists.
- *
- * @param name The name of the manager, which should include connection details and hashed passwords where possible.
- * @param bufferSize The size of the log event buffer.
- * @param connectionSource The source for connections to the database.
- * @param tableName The name of the database table to insert log events into.
- * @param columnConfigs Configuration information about the log table columns.
- * @return a new or existing JDBC manager as applicable.
- * @deprecated use {@link #getManager(String, int, Layout, ConnectionSource, String, ColumnConfig[], ColumnMapping[])}
+ * Appends column names to the given buffer in the format {@code "A,B,C"}.
*/
- @Deprecated
- public static JdbcDatabaseManager getJDBCDatabaseManager(final String name, final int bufferSize,
- final ConnectionSource connectionSource,
- final String tableName,
- final ColumnConfig[] columnConfigs) {
-
- return getManager(name,
- new FactoryData(bufferSize, null, connectionSource, tableName, columnConfigs, new ColumnMapping[0]),
- getFactory());
+ 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++;
+ }
+ }
+ 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++;
+ }
+ }
}
- /**
- * Creates a JDBC manager for use within the {@link JdbcAppender}, or returns a suitable one if it already exists.
- *
- * @param name The name of the manager, which should include connection details and hashed passwords where possible.
- * @param bufferSize The size of the log event buffer.
- * @param connectionSource The source for connections to the database.
- * @param tableName The name of the database table to insert log events into.
- * @param columnConfigs Configuration information about the log table columns.
- * @param columnMappings column mapping configuration (including type conversion).
- * @return a new or existing JDBC manager as applicable.
- * @deprecated use {@link #getManager(String, int, Layout, ConnectionSource, String, ColumnConfig[], ColumnMapping[])}
- */
- @Deprecated
- public static JdbcDatabaseManager getManager(final String name,
- final int bufferSize,
- final ConnectionSource connectionSource,
- final String tableName,
- final ColumnConfig[] columnConfigs,
- final ColumnMapping[] columnMappings) {
- return getManager(name, new FactoryData(bufferSize, null, connectionSource, tableName, columnConfigs, columnMappings),
- getFactory());
+ private static JdbcDatabaseManagerFactory getFactory() {
+ return INSTANCE;
}
/**
@@ -302,103 +384,451 @@
* @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<? extends Serializable> 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<? extends Serializable> 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<ColumnConfig> columnConfigs;
+ private final String sqlStatement;
+ private final FactoryData factoryData;
+ private volatile Connection connection;
+ private volatile PreparedStatement statement;
+ private volatile Reconnector reconnector;
+ private volatile boolean isBatchSupported;
+ private volatile Map<String, ResultSetColumnMetaData> columnMetaData;
+
+ private JdbcDatabaseManager(final String name, final String sqlStatement, final List<ColumnConfig> columnConfigs,
+ final FactoryData factoryData) {
+ super(name, factoryData.getBufferSize());
+ this.sqlStatement = sqlStatement;
+ this.columnConfigs = columnConfigs;
+ this.factoryData = factoryData;
}
- /**
- * Encapsulates data that {@link JdbcDatabaseManagerFactory} uses to create managers.
- */
- private static final class FactoryData extends AbstractDatabaseManager.AbstractFactoryData {
- private final ConnectionSource connectionSource;
- private final String tableName;
- private final ColumnConfig[] columnConfigs;
- private final ColumnMapping[] columnMappings;
+ 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 FactoryData(final int bufferSize, final Layout<? extends Serializable> 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;
+ 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;
+ }
+
+ 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;
+ }
+
+ 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);
+ }
}
}
/**
- * Creates 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 JdbcDatabaseManagerFactory implements ManagerFactory<JdbcDatabaseManager, FactoryData> {
-
- private static final char PARAMETER_MARKER = '?';
+ private boolean isClosed(final Statement statement) throws SQLException {
+ return statement == null || statement.isClosed();
+ }
- @Override
- public JdbcDatabaseManager createManager(final String name, final FactoryData data) {
- final StringBuilder sb = new StringBuilder("INSERT INTO ").append(data.tableName).append(" (");
- // so this gets a little more complicated now that there are two ways to configure column mappings, but
- // both mappings follow the same exact pattern for the prepared statement
- int i = 1;
- for (final ColumnMapping mapping : data.columnMappings) {
- final String mappingName = mapping.getName();
- logger().trace("Adding INSERT ColumnMapping[{}]: {}={} ", i++, mappingName, mapping);
- sb.append(mappingName).append(',');
- }
- for (final ColumnConfig config : data.columnConfigs) {
- sb.append(config.getColumnName()).append(',');
- }
- // at least one of those arrays is guaranteed to be non-empty
- sb.setCharAt(sb.length() - 1, ')');
- sb.append(" VALUES (");
- i = 1;
- final List<ColumnMapping> columnMappings = new ArrayList<>(data.columnMappings.length);
- for (final ColumnMapping mapping : data.columnMappings) {
- final String mappingName = mapping.getName();
- if (Strings.isNotEmpty(mapping.getLiteralValue())) {
- logger().trace("Adding INSERT VALUES literal for ColumnMapping[{}]: {}={} ", i, mappingName, mapping.getLiteralValue());
- sb.append(mapping.getLiteralValue());
- }
- if (Strings.isNotEmpty(mapping.getParameter())) {
- logger().trace("Adding INSERT VALUES parameter for ColumnMapping[{}]: {}={} ", i, mappingName, mapping.getParameter());
- sb.append(mapping.getParameter());
- columnMappings.add(mapping);
- } else {
- logger().trace("Adding INSERT VALUES parameter marker for ColumnMapping[{}]: {}={} ", i, mappingName, PARAMETER_MARKER);
- sb.append(PARAMETER_MARKER);
- columnMappings.add(mapping);
- }
- sb.append(',');
- i++;
- }
- final List<ColumnConfig> columnConfigs = new ArrayList<>(data.columnConfigs.length);
- for (final ColumnConfig config : data.columnConfigs) {
- if (Strings.isNotEmpty(config.getLiteralValue())) {
- sb.append(config.getLiteralValue());
- } else {
- sb.append(PARAMETER_MARKER);
- columnConfigs.add(config);
- }
- sb.append(',');
- }
- // at least one of those arrays is guaranteed to be non-empty
- sb.setCharAt(sb.length() - 1, ')');
- final String sqlStatement = sb.toString();
+ /**
+ * 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();
+ }
- return new JdbcDatabaseManager(name, data.getBufferSize(), data.connectionSource, sqlStatement,
- columnConfigs, columnMappings);
+ 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
+ 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++;
+ }
+ }
+ }
+
+ /**
+ * Sets the given Object in the prepared statement. The value is truncated if needed.
+ */
+ 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
+ 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);
+ }
+ }
+ return value;
+ }
+
+ @Override
+ protected void writeInternal(final LogEvent event, final Serializable serializable) {
+ StringReader reader = null;
+ try {
+ if (!this.isRunning() || this.connection == null || this.connection.isClosed() || this.statement == null
+ || this.statement.isClosed()) {
+ throw new AppenderLoggingException(
+ "Cannot write logging event; JDBC manager not connected to the database, running=%s, [%s]).",
+ isRunning(), fieldsToString());
+ }
+ // Clear in case there are leftovers.
+ statement.clearParameters();
+ if (serializable instanceof MapMessage) {
+ setFields((MapMessage<?, ?>) serializable);
+ }
+ int j = 1; // JDBC indices start at 1
+ 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);
+ }
+ }
+ }
+ }
+ for (final ColumnConfig column : this.columnConfigs) {
+ if (column.isEventTimestamp()) {
+ this.statement.setTimestamp(j++, new Timestamp(event.getTimeMillis()));
+ } else if (column.isClob()) {
+ 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 {
+ this.statement.setString(j++, Objects.toString(
+ truncate(column.getColumnNameKey(), column.getLayout().toSerializable(event)), null));
+ }
+ }
+
+ 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
+ }
+ Closer.closeSilently(reader);
+ }
+ }
+
+ @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-jdbc/src/site/manual/index.md b/log4j-jdbc/src/site/manual/index.md
index 66fe24f..84fca13 100644
--- a/log4j-jdbc/src/site/manual/index.md
+++ b/log4j-jdbc/src/site/manual/index.md
@@ -18,7 +18,7 @@
# Apache Log4j ZeroMQ using JeroMQ module
-As of Log4j 2.11.0, ZeroMQ using JeroMQ support has moved from the existing module logj-core to the new module log4j-jeromq.
+As of Log4j 2.11.0, ZeroMQ using JeroMQ support has moved from the existing module log4j-core to the new module log4j-jeromq.
## Requirements
diff --git a/log4j-jdbc/src/test/java/org/apache/logging/log4j/jdbc/appender/AbstractJdbcAppenderFactoryMethodTest.java b/log4j-jdbc/src/test/java/org/apache/logging/log4j/jdbc/appender/AbstractJdbcAppenderFactoryMethodTest.java
index 39be14f..c176419 100644
--- a/log4j-jdbc/src/test/java/org/apache/logging/log4j/jdbc/appender/AbstractJdbcAppenderFactoryMethodTest.java
+++ b/log4j-jdbc/src/test/java/org/apache/logging/log4j/jdbc/appender/AbstractJdbcAppenderFactoryMethodTest.java
@@ -16,6 +16,10 @@
*/
package org.apache.logging.log4j.jdbc.appender;
+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;
@@ -23,10 +27,10 @@
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.jdbc.appender.FactoryMethodConnectionSource;
import org.apache.logging.log4j.junit.LoggerContextRule;
import org.apache.logging.log4j.util.Strings;
import org.h2.util.IOUtils;
@@ -34,10 +38,6 @@
import org.junit.Test;
import org.junit.rules.RuleChain;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
/**
* Abstract unit test for JdbcAppender using a {@link FactoryMethodConnectionSource} configuration.
*/
@@ -110,4 +110,12 @@
}
}
+ @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-jdbc/src/test/java/org/apache/logging/log4j/jdbc/appender/DriverManagerConnectionSourceTest.java b/log4j-jdbc/src/test/java/org/apache/logging/log4j/jdbc/appender/DriverManagerConnectionSourceTest.java
index e1e94ed..ac72c6d 100644
--- a/log4j-jdbc/src/test/java/org/apache/logging/log4j/jdbc/appender/DriverManagerConnectionSourceTest.java
+++ b/log4j-jdbc/src/test/java/org/apache/logging/log4j/jdbc/appender/DriverManagerConnectionSourceTest.java
@@ -37,7 +37,7 @@
};
// @formatter:off
DriverManagerConnectionSource source = DriverManagerConnectionSource.newBuilder()
- .setConnectionString(JdbcH2TestHelper.CONNECTION_STRING)
+ .setConnectionString(JdbcH2TestHelper.CONNECTION_STRING_MEM)
.setProperties(properties)
.build();
// @formatter:on
@@ -50,7 +50,7 @@
public void testH2UserAndPassword() throws SQLException {
// @formatter:off
DriverManagerConnectionSource source = DriverManagerConnectionSource.newBuilder()
- .setConnectionString(JdbcH2TestHelper.CONNECTION_STRING)
+ .setConnectionString(JdbcH2TestHelper.CONNECTION_STRING_MEM)
.setUserName(JdbcH2TestHelper.USER_NAME.toCharArray())
.setPassword(JdbcH2TestHelper.PASSWORD.toCharArray())
.build();
diff --git a/log4j-jdbc/src/test/java/org/apache/logging/log4j/jdbc/appender/H2TestConstants.java b/log4j-jdbc/src/test/java/org/apache/logging/log4j/jdbc/appender/H2TestConstants.java
new file mode 100644
index 0000000..5b838a4
--- /dev/null
+++ b/log4j-jdbc/src/test/java/org/apache/logging/log4j/jdbc/appender/H2TestConstants.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.jdbc.appender;
+
+import org.apache.commons.lang3.SystemUtils;
+
+/**
+ * Constants for testing H2.
+ */
+public class H2TestConstants {
+
+ public static final String JDBC_DRIVER_CLASS_NAME = "org.h2.Driver";
+ public static final String CONNECTION_STRING = "jdbc:h2:" + SystemUtils.JAVA_IO_TMPDIR
+ + "/h2/h2_test0;TRACE_LEVEL_SYSTEM_OUT=0";
+ public static final char[] USER = null;
+ public static final char[] PASSWORD = null;
+
+}
diff --git a/log4j-jdbc/src/test/java/org/apache/logging/log4j/jdbc/appender/JdbcAppenderColumnMappingLiteralTest.java b/log4j-jdbc/src/test/java/org/apache/logging/log4j/jdbc/appender/JdbcAppenderColumnMappingLiteralTest.java
new file mode 100644
index 0000000..91f44ae
--- /dev/null
+++ b/log4j-jdbc/src/test/java/org/apache/logging/log4j/jdbc/appender/JdbcAppenderColumnMappingLiteralTest.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.jdbc.appender;
+
+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.junit.LoggerContextRule;
+import org.apache.logging.log4j.test.RuleChainFactory;
+import org.h2.util.IOUtils;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.RuleChain;
+
+public class JdbcAppenderColumnMappingLiteralTest {
+
+ @Rule
+ public final RuleChain rules;
+ private final JdbcRule jdbcRule;
+
+ public JdbcAppenderColumnMappingLiteralTest() {
+ this(new JdbcRule(JdbcH2TestHelper.TEST_CONFIGURATION_SOURCE_TMPDIR,
+ "CREATE TABLE dsMappingLogEntry (id INTEGER IDENTITY, level VARCHAR(10), logger VARCHAR(255), message VARCHAR(1024), exception CLOB)",
+ "DROP TABLE dsMappingLogEntry"));
+ }
+
+ protected JdbcAppenderColumnMappingLiteralTest(final JdbcRule jdbcRule) {
+ this.rules = RuleChainFactory.create(jdbcRule,
+ new LoggerContextRule("org/apache/logging/log4j/jdbc/appender/log4j2-dm-column-mapping-literal.xml"));
+ this.jdbcRule = jdbcRule;
+ }
+
+ @Test
+ public void test() throws Exception {
+ try (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-jdbc/src/test/java/org/apache/logging/log4j/jdbc/appender/JdbcAppenderColumnMappingPatternTest.java b/log4j-jdbc/src/test/java/org/apache/logging/log4j/jdbc/appender/JdbcAppenderColumnMappingPatternTest.java
new file mode 100644
index 0000000..aee9b93
--- /dev/null
+++ b/log4j-jdbc/src/test/java/org/apache/logging/log4j/jdbc/appender/JdbcAppenderColumnMappingPatternTest.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.jdbc.appender;
+
+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.junit.LoggerContextRule;
+import org.apache.logging.log4j.test.RuleChainFactory;
+import org.h2.util.IOUtils;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.RuleChain;
+
+public class JdbcAppenderColumnMappingPatternTest {
+
+ @Rule
+ public final RuleChain rules;
+ private final JdbcRule jdbcRule;
+
+ public JdbcAppenderColumnMappingPatternTest() {
+ this(new JdbcRule(JdbcH2TestHelper.TEST_CONFIGURATION_SOURCE_MEM,
+ "CREATE TABLE dsMappingLogEntry (id INTEGER IDENTITY, level VARCHAR(10), logger VARCHAR(255), message VARCHAR(1024), exception CLOB)",
+ "DROP TABLE dsMappingLogEntry"));
+ }
+
+ protected JdbcAppenderColumnMappingPatternTest(final JdbcRule jdbcRule) {
+ this.rules = RuleChainFactory.create(jdbcRule, new LoggerContextRule(
+ "org/apache/logging/log4j/jdbc/appender/log4j2-dm-column-mapping-pattern.xml"));
+ this.jdbcRule = jdbcRule;
+ }
+
+ @Test
+ public void test() throws Exception {
+ try (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-jdbc/src/test/java/org/apache/logging/log4j/jdbc/appender/JdbcAppenderH2DataSourceTest.java b/log4j-jdbc/src/test/java/org/apache/logging/log4j/jdbc/appender/JdbcAppenderH2DataSourceTest.java
index bb1c243..011193a 100644
--- a/log4j-jdbc/src/test/java/org/apache/logging/log4j/jdbc/appender/JdbcAppenderH2DataSourceTest.java
+++ b/log4j-jdbc/src/test/java/org/apache/logging/log4j/jdbc/appender/JdbcAppenderH2DataSourceTest.java
@@ -21,7 +21,7 @@
*/
public class JdbcAppenderH2DataSourceTest extends AbstractJdbcAppenderDataSourceTest {
public JdbcAppenderH2DataSourceTest() {
- super(new JdbcRule(JdbcH2TestHelper.TEST_CONFIGURATION_SOURCE,
+ super(new JdbcRule(JdbcH2TestHelper.TEST_CONFIGURATION_SOURCE_MEM,
"CREATE TABLE dsLogEntry ("
+ "id INTEGER IDENTITY, eventDate DATETIME, literalColumn VARCHAR(255), level NVARCHAR(10), "
+ "logger NVARCHAR(255), message VARCHAR(1024), exception NCLOB, anotherDate TIMESTAMP" + ")",
diff --git a/log4j-jdbc/src/test/java/org/apache/logging/log4j/jdbc/appender/JdbcAppenderH2FactoryMethodTest.java b/log4j-jdbc/src/test/java/org/apache/logging/log4j/jdbc/appender/JdbcAppenderH2FactoryMethodTest.java
index ff88fe1..51f664b 100644
--- a/log4j-jdbc/src/test/java/org/apache/logging/log4j/jdbc/appender/JdbcAppenderH2FactoryMethodTest.java
+++ b/log4j-jdbc/src/test/java/org/apache/logging/log4j/jdbc/appender/JdbcAppenderH2FactoryMethodTest.java
@@ -24,14 +24,14 @@
*/
public class JdbcAppenderH2FactoryMethodTest extends AbstractJdbcAppenderFactoryMethodTest {
public JdbcAppenderH2FactoryMethodTest() {
- super(new JdbcRule(JdbcH2TestHelper.TEST_CONFIGURATION_SOURCE,
+ super(new JdbcRule(JdbcH2TestHelper.TEST_CONFIGURATION_SOURCE_MEM,
"CREATE TABLE fmLogEntry ("
+ "id INTEGER IDENTITY, eventDate DATETIME, literalColumn VARCHAR(255), level NVARCHAR(10), "
- + "logger NVARCHAR(255), message VARCHAR(1024), exception NCLOB, anotherDate TIMESTAMP" + ")",
+ + "logger NVARCHAR(255), message VARCHAR(1024), exception NCLOB, anotherDate TIMESTAMP)",
"DROP TABLE fmLogEntry"), "h2");
}
public static Connection getConnection() throws SQLException {
- return JdbcH2TestHelper.getConnection();
+ return JdbcH2TestHelper.getConnectionMem();
}
}
diff --git a/log4j-jdbc/src/test/java/org/apache/logging/log4j/jdbc/appender/JdbcAppenderMapMessageDataSourceTest.java b/log4j-jdbc/src/test/java/org/apache/logging/log4j/jdbc/appender/JdbcAppenderMapMessageDataSourceTest.java
index a60c3c0..3769bbb 100644
--- a/log4j-jdbc/src/test/java/org/apache/logging/log4j/jdbc/appender/JdbcAppenderMapMessageDataSourceTest.java
+++ b/log4j-jdbc/src/test/java/org/apache/logging/log4j/jdbc/appender/JdbcAppenderMapMessageDataSourceTest.java
@@ -30,6 +30,7 @@
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.util.Throwables;
@@ -53,7 +54,7 @@
private final JdbcRule jdbcRule;
public JdbcAppenderMapMessageDataSourceTest() {
- this(new JdbcRule(JdbcH2TestHelper.TEST_CONFIGURATION_SOURCE,
+ this(new JdbcRule(JdbcH2TestHelper.TEST_CONFIGURATION_SOURCE_MEM,
// @formatter:off
"CREATE TABLE dsLogEntry (Id INTEGER IDENTITY, ColumnA VARCHAR(255), ColumnB VARCHAR(255))",
"DROP TABLE dsLogEntry"));
@@ -114,4 +115,28 @@
}
}
}
+
+ @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-jdbc/src/test/java/org/apache/logging/log4j/jdbc/appender/JdbcAppenderStringSubstitutionTest.java b/log4j-jdbc/src/test/java/org/apache/logging/log4j/jdbc/appender/JdbcAppenderStringSubstitutionTest.java
new file mode 100644
index 0000000..3b137cb
--- /dev/null
+++ b/log4j-jdbc/src/test/java/org/apache/logging/log4j/jdbc/appender/JdbcAppenderStringSubstitutionTest.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.jdbc.appender;
+
+import org.apache.logging.log4j.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() {
+ super();
+ System.setProperty(KEY, VALUE);
+ }
+
+ @AfterClass
+ public static void afterClass() {
+ System.getProperties().remove(KEY);
+ }
+
+ @Rule
+ public final LoggerContextRule rule = new LoggerContextRule("org/apache/logging/log4j/jdbc/appender/log4j2-jdbc-string-substitution.xml");
+
+ @Test
+ public void test() throws Exception {
+ JdbcAppender appender = rule.getAppender("databaseAppender", JdbcAppender.class);
+ Assert.assertNotNull(appender);
+ @SuppressWarnings("resource")
+ JdbcDatabaseManager manager = appender.getManager();
+ Assert.assertNotNull(manager);
+ String sqlStatement = manager.getSqlStatement();
+ Assert.assertFalse(sqlStatement, sqlStatement.contains(KEY));
+ Assert.assertTrue(sqlStatement, sqlStatement.contains(VALUE));
+ }
+
+}
diff --git a/log4j-jdbc/src/test/java/org/apache/logging/log4j/jdbc/appender/JdbcH2TestHelper.java b/log4j-jdbc/src/test/java/org/apache/logging/log4j/jdbc/appender/JdbcH2TestHelper.java
index 9eb2713..94d4523 100644
--- a/log4j-jdbc/src/test/java/org/apache/logging/log4j/jdbc/appender/JdbcH2TestHelper.java
+++ b/log4j-jdbc/src/test/java/org/apache/logging/log4j/jdbc/appender/JdbcH2TestHelper.java
@@ -20,24 +20,44 @@
import java.sql.DriverManager;
import java.sql.SQLException;
-import org.apache.logging.log4j.jdbc.appender.AbstractConnectionSource;
-import org.apache.logging.log4j.jdbc.appender.ConnectionSource;
+import org.apache.commons.lang3.SystemUtils;
public class JdbcH2TestHelper {
- public static final String CONNECTION_STRING = "jdbc:h2:mem:Log4j";
+ /**
+ * A JDBC connection string for an H2 in-memory database.
+ */
+ public static final String CONNECTION_STRING_MEM = "jdbc:h2:mem:Log4j";
+
+ /**
+ * A JDBC connection string for an H2 database in the Java temporary directory.
+ */
+ static final String CONNECTION_STRING_TMPDIR = "jdbc:h2:" + SystemUtils.JAVA_IO_TMPDIR
+ + "/h2/test_log4j;TRACE_LEVEL_SYSTEM_OUT=0";
+
public static final String USER_NAME = "sa";
public static final String PASSWORD = "";
-
- public static ConnectionSource TEST_CONFIGURATION_SOURCE = new AbstractConnectionSource() {
+
+ public static ConnectionSource TEST_CONFIGURATION_SOURCE_MEM = new AbstractConnectionSource() {
@Override
public Connection getConnection() throws SQLException {
- return JdbcH2TestHelper.getConnection();
+ return JdbcH2TestHelper.getConnectionMem();
}
};
- public static Connection getConnection() throws SQLException {
- return DriverManager.getConnection(CONNECTION_STRING, USER_NAME, PASSWORD);
+ public static ConnectionSource TEST_CONFIGURATION_SOURCE_TMPDIR = new AbstractConnectionSource() {
+ @Override
+ public Connection getConnection() throws SQLException {
+ return JdbcH2TestHelper.getConnectionTmpDir();
+ }
+ };
+
+ public static Connection getConnectionMem() throws SQLException {
+ return DriverManager.getConnection(CONNECTION_STRING_MEM, USER_NAME, PASSWORD);
+ }
+
+ public static Connection getConnectionTmpDir() throws SQLException {
+ return DriverManager.getConnection(CONNECTION_STRING_TMPDIR, USER_NAME, PASSWORD);
}
}
diff --git a/log4j-jdbc/src/test/java/org/apache/logging/log4j/jdbc/appender/JdbcRule.java b/log4j-jdbc/src/test/java/org/apache/logging/log4j/jdbc/appender/JdbcRule.java
index bd06281..cc5866a 100644
--- a/log4j-jdbc/src/test/java/org/apache/logging/log4j/jdbc/appender/JdbcRule.java
+++ b/log4j-jdbc/src/test/java/org/apache/logging/log4j/jdbc/appender/JdbcRule.java
@@ -17,9 +17,11 @@
package org.apache.logging.log4j.jdbc.appender;
import java.sql.Connection;
+import java.sql.SQLException;
import java.sql.Statement;
+import java.util.Objects;
-import org.apache.logging.log4j.jdbc.appender.ConnectionSource;
+import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.junit.LoggerContextRule;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
@@ -34,45 +36,54 @@
*/
public class JdbcRule implements TestRule {
- private final ConnectionSource connectionSource;
- private final String createTableStatement;
- private final String dropTableStatement;
+ 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 source for obtaining a Connection
- * @param createTableStatement a SQL DDL statement to set up a table for use in a JUnit test
- * @param dropTableStatement a SQL DDL statement to drop the created table
- */
- public JdbcRule(final ConnectionSource connectionSource, final String createTableStatement,
- final String dropTableStatement) {
- this.dropTableStatement = dropTableStatement;
- this.connectionSource = connectionSource;
- this.createTableStatement = createTableStatement;
- }
+ /**
+ * 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;
+ }
- public ConnectionSource getConnectionSource() {
- return connectionSource;
- }
+ @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");
+ }
+ }
+ }
- @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 = connectionSource.getConnection()) {
- try (final Statement statement = connection.createStatement()) {
- statement.executeUpdate(createTableStatement);
- }
- base.evaluate();
- try (final Statement statement = connection.createStatement()) {
- statement.executeUpdate(dropTableStatement);
- statement.execute("SHUTDOWN");
- }
- }
- }
- };
- }
+ };
+ }
+
+ public Connection getConnection() throws SQLException {
+ return connectionSource.getConnection();
+ }
+
+ public ConnectionSource getConnectionSource() {
+ return connectionSource;
+ }
}
diff --git a/log4j-jdbc/src/test/java/org/apache/logging/log4j/jdbc/convert/DateTypeConverterTest.java b/log4j-jdbc/src/test/java/org/apache/logging/log4j/jdbc/convert/DateTypeConverterTest.java
index 494b3dc..1d240c7 100644
--- a/log4j-jdbc/src/test/java/org/apache/logging/log4j/jdbc/convert/DateTypeConverterTest.java
+++ b/log4j-jdbc/src/test/java/org/apache/logging/log4j/jdbc/convert/DateTypeConverterTest.java
@@ -58,4 +58,4 @@
public void testFromMillis() throws Exception {
assertEquals(expected, DateTypeConverter.fromMillis(timestamp, dateClass));
}
-}
\ No newline at end of file
+}
diff --git a/log4j-jdbc/src/test/resources/org/apache/logging/log4j/jdbc/appender/log4j2-dm-column-mapping-literal.xml b/log4j-jdbc/src/test/resources/org/apache/logging/log4j/jdbc/appender/log4j2-dm-column-mapping-literal.xml
new file mode 100644
index 0000000..b963d4d
--- /dev/null
+++ b/log4j-jdbc/src/test/resources/org/apache/logging/log4j/jdbc/appender/log4j2-dm-column-mapping-literal.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-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 "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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="ERROR">
+
+ <Appenders>
+ <Console name="STDOUT">
+ <PatternLayout pattern="%C{1.} %m %level MDC%X%n"/>
+ </Console>
+ <Jdbc name="databaseAppender" tableName="dsMappingLogEntry" ignoreExceptions="false">
+ <DriverManager connectionString="jdbc:h2:${sys:java.io.tmpdir}/h2/test_log4j;TRACE_LEVEL_SYSTEM_OUT=0" userName="sa" password="" />
+ <ColumnMapping name="level" pattern="%level" />
+ <ColumnMapping name="logger" pattern="%logger" />
+ <ColumnMapping name="message" literal="'Hello World!'" />
+ <ColumnMapping name="exception" pattern="%ex{full}" />
+ </Jdbc>
+ </Appenders>
+
+ <Loggers>
+ <Logger name="org.apache.logging.log4j.jdbc.appender.JdbcAppenderColumnMappingLiteralTest" level="DEBUG" additivity="false">
+ <AppenderRef ref="databaseAppender" />
+ </Logger>
+
+ <Root level="FATAL">
+ <AppenderRef ref="STDOUT"/>
+ </Root>
+ </Loggers>
+
+</Configuration>
diff --git a/log4j-jdbc/src/test/resources/org/apache/logging/log4j/jdbc/appender/log4j2-dm-column-mapping-pattern.xml b/log4j-jdbc/src/test/resources/org/apache/logging/log4j/jdbc/appender/log4j2-dm-column-mapping-pattern.xml
new file mode 100644
index 0000000..1a67c75
--- /dev/null
+++ b/log4j-jdbc/src/test/resources/org/apache/logging/log4j/jdbc/appender/log4j2-dm-column-mapping-pattern.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-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 "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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">
+
+ <Appenders>
+ <Console name="STDOUT">
+ <PatternLayout pattern="%C{1.} %m %level MDC%X%n"/>
+ </Console>
+ <Jdbc name="databaseAppender" tableName="dsMappingLogEntry" ignoreExceptions="false">
+ <DriverManager connectionString="jdbc:h2:mem:Log4j" userName="sa" password="" />
+ <ColumnMapping name="level" pattern="%level" />
+ <ColumnMapping name="logger" pattern="%logger" />
+ <ColumnMapping name="message" pattern="%message" />
+ <ColumnMapping name="exception" pattern="%ex{full}" />
+ </Jdbc>
+ </Appenders>
+
+ <Loggers>
+ <Logger name="org.apache.logging.log4j.jdbc.appender.JdbcAppenderColumnMappingPatternTest" level="DEBUG" additivity="false">
+ <AppenderRef ref="databaseAppender" />
+ </Logger>
+
+ <Root level="FATAL">
+ <AppenderRef ref="STDOUT"/>
+ </Root>
+ </Loggers>
+
+</Configuration>
diff --git a/log4j-jdbc/src/test/resources/org/apache/logging/log4j/jdbc/appender/log4j2-jdbc-string-substitution.xml b/log4j-jdbc/src/test/resources/org/apache/logging/log4j/jdbc/appender/log4j2-jdbc-string-substitution.xml
new file mode 100644
index 0000000..836c4b4
--- /dev/null
+++ b/log4j-jdbc/src/test/resources/org/apache/logging/log4j/jdbc/appender/log4j2-jdbc-string-substitution.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="UTF-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 "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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="ERROR">
+
+ <Appenders>
+ <Console name="STDOUT">
+ <PatternLayout pattern="%C{1.} %m %level MDC%X%n"/>
+ </Console>
+ <Jdbc name="databaseAppender" tableName="${sys:Test.TableName}" ignoreExceptions="false">
+ <DriverManager connectionString="jdbc:h2:${sys:java.io.tmpdir}/h2/test_log4j;TRACE_LEVEL_SYSTEM_OUT=0" userName="sa" password="" />
+ <ColumnMapping name="level" pattern="%level" />
+ <ColumnMapping name="logger" pattern="%logger" />
+ <ColumnMapping name="message" literal="'Hello World!'" />
+ <ColumnMapping name="exception" pattern="%ex{full}" />
+ </Jdbc>
+ </Appenders>
+
+ <Loggers>
+ <Logger name="org.apache.logging.log4j.core.appender.db.jdbc.JdbcAppenderColumnMappingLiteralTest" level="DEBUG" additivity="false">
+ <AppenderRef ref="databaseAppender" />
+ </Logger>
+
+ <Root level="FATAL">
+ <AppenderRef ref="STDOUT"/>
+ </Root>
+ </Loggers>
+
+</Configuration>
diff --git a/log4j-jeromq/pom.xml b/log4j-jeromq/pom.xml
index 6064add..a3f7814 100644
--- a/log4j-jeromq/pom.xml
+++ b/log4j-jeromq/pom.xml
@@ -111,6 +111,7 @@
<!-- module link generation is completely broken in the javadoc plugin for a multi-module non-aggregating project -->
<detectOfflineLinks>false</detectOfflineLinks>
<linksource>true</linksource>
+ <source>8</source>
</configuration>
<reportSets>
<reportSet>
diff --git a/log4j-jeromq/src/main/java/org/apache/logging/log4j/jeromq/appender/JeroMqAppender.java b/log4j-jeromq/src/main/java/org/apache/logging/log4j/jeromq/appender/JeroMqAppender.java
index 3e20288..09ce72c 100644
--- a/log4j-jeromq/src/main/java/org/apache/logging/log4j/jeromq/appender/JeroMqAppender.java
+++ b/log4j-jeromq/src/main/java/org/apache/logging/log4j/jeromq/appender/JeroMqAppender.java
@@ -27,13 +27,13 @@
import org.apache.logging.log4j.core.Layout;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.appender.AbstractAppender;
-import org.apache.logging.log4j.core.config.Node;
+import org.apache.logging.log4j.plugins.Node;
import org.apache.logging.log4j.core.config.Property;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
-import org.apache.logging.log4j.core.config.plugins.PluginElement;
-import org.apache.logging.log4j.core.config.plugins.PluginFactory;
-import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginAttribute;
+import org.apache.logging.log4j.plugins.PluginElement;
+import org.apache.logging.log4j.plugins.PluginFactory;
+import org.apache.logging.log4j.plugins.validation.constraints.Required;
import org.apache.logging.log4j.core.layout.PatternLayout;
import org.apache.logging.log4j.util.Strings;
@@ -69,8 +69,8 @@
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);
+ final long tcpKeepAliveInterval, final boolean xpubVerbose, 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,
@@ -83,33 +83,33 @@
@PluginFactory
public static JeroMqAppender createAppender(
// @formatter:off
- @Required(message = "No name provided for JeroMqAppender") @PluginAttribute("name") final String name,
- @PluginElement("Layout") Layout<?> layout,
- @PluginElement("Filter") final Filter filter,
- @PluginElement("Properties") final Property[] properties,
+ @Required(message = "No name provided for JeroMqAppender") @PluginAttribute final String name,
+ @PluginElement Layout<?> layout,
+ @PluginElement final Filter filter,
+ @PluginElement final Property[] properties,
// Super attributes
- @PluginAttribute("ignoreExceptions") final boolean ignoreExceptions,
+ @PluginAttribute final boolean ignoreExceptions,
// ZMQ attributes; defaults picked from zmq.Options.
- @PluginAttribute(value = "affinity", defaultLong = 0) final long affinity,
- @PluginAttribute(value = "backlog", defaultLong = DEFAULT_BACKLOG) final long backlog,
- @PluginAttribute(value = "delayAttachOnConnect") final boolean delayAttachOnConnect,
- @PluginAttribute(value = "identity") final byte[] identity,
- @PluginAttribute(value = "ipv4Only", defaultBoolean = true) final boolean ipv4Only,
- @PluginAttribute(value = "linger", defaultLong = -1) final long linger,
- @PluginAttribute(value = "maxMsgSize", defaultLong = -1) final long maxMsgSize,
- @PluginAttribute(value = "rcvHwm", defaultLong = DEFAULT_RCV_HWM) final long rcvHwm,
- @PluginAttribute(value = "receiveBufferSize", defaultLong = 0) final long receiveBufferSize,
- @PluginAttribute(value = "receiveTimeOut", defaultLong = -1) final int receiveTimeOut,
- @PluginAttribute(value = "reconnectIVL", defaultLong = DEFAULT_IVL) final long reconnectIVL,
- @PluginAttribute(value = "reconnectIVLMax", defaultLong = 0) final long reconnectIVLMax,
- @PluginAttribute(value = "sendBufferSize", defaultLong = 0) final long sendBufferSize,
- @PluginAttribute(value = "sendTimeOut", defaultLong = -1) final int sendTimeOut,
- @PluginAttribute(value = "sndHwm", defaultLong = DEFAULT_SND_HWM) final long sndHwm,
- @PluginAttribute(value = "tcpKeepAlive", defaultInt = -1) final int tcpKeepAlive,
- @PluginAttribute(value = "tcpKeepAliveCount", defaultLong = -1) final long tcpKeepAliveCount,
- @PluginAttribute(value = "tcpKeepAliveIdle", defaultLong = -1) final long tcpKeepAliveIdle,
- @PluginAttribute(value = "tcpKeepAliveInterval", defaultLong = -1) final long tcpKeepAliveInterval,
- @PluginAttribute(value = "xpubVerbose") final boolean xpubVerbose
+ @PluginAttribute(defaultLong = 0) final long affinity,
+ @PluginAttribute(defaultLong = DEFAULT_BACKLOG) final long backlog,
+ @PluginAttribute final boolean delayAttachOnConnect,
+ @PluginAttribute final byte[] identity,
+ @PluginAttribute(defaultBoolean = true) final boolean ipv4Only,
+ @PluginAttribute(defaultLong = -1) final long linger,
+ @PluginAttribute(defaultLong = -1) final long maxMsgSize,
+ @PluginAttribute(defaultLong = DEFAULT_RCV_HWM) final long rcvHwm,
+ @PluginAttribute(defaultLong = 0) final long receiveBufferSize,
+ @PluginAttribute(defaultLong = -1) final int receiveTimeOut,
+ @PluginAttribute(defaultLong = DEFAULT_IVL) final long reconnectIVL,
+ @PluginAttribute(defaultLong = 0) final long reconnectIVLMax,
+ @PluginAttribute(defaultLong = 0) final long sendBufferSize,
+ @PluginAttribute(defaultLong = -1) final int sendTimeOut,
+ @PluginAttribute(defaultLong = DEFAULT_SND_HWM) final long sndHwm,
+ @PluginAttribute(defaultInt = -1) final int tcpKeepAlive,
+ @PluginAttribute(defaultLong = -1) final long tcpKeepAliveCount,
+ @PluginAttribute(defaultLong = -1) final long tcpKeepAliveIdle,
+ @PluginAttribute(defaultLong = -1) final long tcpKeepAliveInterval,
+ @PluginAttribute final boolean xpubVerbose
// @formatter:on
) {
if (layout == null) {
@@ -134,7 +134,7 @@
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);
+ tcpKeepAliveCount, tcpKeepAliveIdle, tcpKeepAliveInterval, xpubVerbose, Property.EMPTY_ARRAY);
}
@Override
diff --git a/log4j-jeromq/src/site/manual/index.md b/log4j-jeromq/src/site/manual/index.md
index 66fe24f..84fca13 100644
--- a/log4j-jeromq/src/site/manual/index.md
+++ b/log4j-jeromq/src/site/manual/index.md
@@ -18,7 +18,7 @@
# Apache Log4j ZeroMQ using JeroMQ module
-As of Log4j 2.11.0, ZeroMQ using JeroMQ support has moved from the existing module logj-core to the new module log4j-jeromq.
+As of Log4j 2.11.0, ZeroMQ using JeroMQ support has moved from the existing module log4j-core to the new module log4j-jeromq.
## Requirements
diff --git a/log4j-jeromq/src/test/java/org/apache/logging/log4j/jeromq/appender/JeroMqTestClient.java b/log4j-jeromq/src/test/java/org/apache/logging/log4j/jeromq/appender/JeroMqTestClient.java
index 494be9d..0746a06 100644
--- a/log4j-jeromq/src/test/java/org/apache/logging/log4j/jeromq/appender/JeroMqTestClient.java
+++ b/log4j-jeromq/src/test/java/org/apache/logging/log4j/jeromq/appender/JeroMqTestClient.java
@@ -52,4 +52,4 @@
}
return messages;
}
-}
\ No newline at end of file
+}
diff --git a/log4j-jms/pom.xml b/log4j-jms/pom.xml
index 89b9c94..6a8a02d 100644
--- a/log4j-jms/pom.xml
+++ b/log4j-jms/pom.xml
@@ -125,6 +125,7 @@
<!-- module link generation is completely broken in the javadoc plugin for a multi-module non-aggregating project -->
<detectOfflineLinks>false</detectOfflineLinks>
<linksource>true</linksource>
+ <source>8</source>
</configuration>
<reportSets>
<reportSet>
diff --git a/log4j-jms/src/main/java/org/apache/logging/log4j/jms/appender/JmsAppender.java b/log4j-jms/src/main/java/org/apache/logging/log4j/jms/appender/JmsAppender.java
index fb21f80..955a367 100644
--- a/log4j-jms/src/main/java/org/apache/logging/log4j/jms/appender/JmsAppender.java
+++ b/log4j-jms/src/main/java/org/apache/logging/log4j/jms/appender/JmsAppender.java
@@ -17,27 +17,26 @@
package org.apache.logging.log4j.jms.appender;
-import java.io.Serializable;
-import java.util.Properties;
-import java.util.concurrent.TimeUnit;
-
-import javax.jms.JMSException;
-
import org.apache.logging.log4j.core.Appender;
import org.apache.logging.log4j.core.Filter;
import org.apache.logging.log4j.core.Layout;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.appender.AbstractAppender;
import org.apache.logging.log4j.core.appender.AbstractManager;
-import org.apache.logging.log4j.core.config.Node;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginAliases;
-import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
-import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
-import org.apache.logging.log4j.core.config.plugins.PluginElement;
-import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required;
+import org.apache.logging.log4j.core.config.Property;
import org.apache.logging.log4j.core.net.JndiManager;
import org.apache.logging.log4j.jms.appender.JmsManager.JmsManagerConfiguration;
+import org.apache.logging.log4j.plugins.Node;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginAliases;
+import org.apache.logging.log4j.plugins.PluginBuilderAttribute;
+import org.apache.logging.log4j.plugins.PluginFactory;
+import org.apache.logging.log4j.plugins.validation.constraints.Required;
+
+import javax.jms.JMSException;
+import java.io.Serializable;
+import java.util.Properties;
+import java.util.concurrent.TimeUnit;
/**
* Generic JMS Appender plugin for both queues and topics. This Appender replaces the previous split ones. However,
@@ -47,15 +46,12 @@
@PluginAliases({ "JMSQueue", "JMSTopic" })
public class JmsAppender extends AbstractAppender {
- public static class Builder implements org.apache.logging.log4j.core.util.Builder<JmsAppender> {
-
+ public static class Builder<B extends Builder<B>> extends AbstractAppender.Builder<B>
+ implements org.apache.logging.log4j.plugins.util.Builder<JmsAppender> {
+
public static final int DEFAULT_RECONNECT_INTERVAL_MILLIS = 5000;
@PluginBuilderAttribute
- @Required(message = "A name for the JmsAppender must be specified")
- private String name;
-
- @PluginBuilderAttribute
private String factoryName;
@PluginBuilderAttribute
@@ -85,16 +81,8 @@
@PluginBuilderAttribute(sensitive = true)
private char[] password;
- @PluginElement("Layout")
- private Layout<? extends Serializable> 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;
@@ -115,126 +103,82 @@
securityPrincipalName, securityCredentials, null);
configuration = new JmsManagerConfiguration(jndiProperties, factoryBindingName, destinationBindingName,
userName, password, false, reconnectIntervalMillis);
- actualJmsManager = AbstractManager.getManager(name, JmsManager.FACTORY, configuration);
+ actualJmsManager = AbstractManager.getManager(getName(), JmsManager.FACTORY, configuration);
}
if (actualJmsManager == null) {
// JmsManagerFactory has already logged an ERROR.
return null;
}
- if (layout == null) {
+ if (getLayout() == null) {
LOGGER.error("No layout provided for JmsAppender");
return null;
}
try {
- return new JmsAppender(name, filter, layout, ignoreExceptions, actualJmsManager);
+ return new JmsAppender(getName(), getFilter(), getLayout(), isIgnoreExceptions(), getPropertyArray(), actualJmsManager);
} catch (final JMSException e) {
// Never happens since the ctor no longer actually throws a JMSException.
throw new IllegalStateException(e);
}
}
- public Builder setDestinationBindingName(final String destinationBindingName) {
+ public B setDestinationBindingName(final String destinationBindingName) {
this.destinationBindingName = destinationBindingName;
- return this;
+ return asBuilder();
}
- public Builder setFactoryBindingName(final String factoryBindingName) {
+ public B setFactoryBindingName(final String factoryBindingName) {
this.factoryBindingName = factoryBindingName;
- return this;
+ return asBuilder();
}
- public Builder setFactoryName(final String factoryName) {
+ public B setFactoryName(final String factoryName) {
this.factoryName = factoryName;
- return this;
+ return asBuilder();
}
- 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) {
+ public B setImmediateFail(final boolean immediateFail) {
this.immediateFail = immediateFail;
- return this;
+ return asBuilder();
}
- public Builder setJmsManager(final JmsManager jmsManager) {
+ public B setJmsManager(final JmsManager jmsManager) {
this.jmsManager = jmsManager;
- return this;
+ return asBuilder();
}
- public Builder setLayout(final Layout<? extends Serializable> layout) {
- this.layout = layout;
- return this;
- }
-
- public Builder setName(final String name) {
- this.name = name;
- return this;
- }
-
- public Builder setPassword(final char[] password) {
+ public B setPassword(final char[] password) {
this.password = password;
- return this;
+ return asBuilder();
}
- /**
- * Sets the Password.
- * @param password The new password.
- * @deprecated Use setPassword(char[])
- * @return the Builder.
- */
- @Deprecated
- public Builder setPassword(final String password) {
- this.password = password == null ? null : password.toCharArray();
- return this;
- }
-
- public Builder setProviderUrl(final String providerUrl) {
+ public B setProviderUrl(final String providerUrl) {
this.providerUrl = providerUrl;
- return this;
+ return asBuilder();
}
- public Builder setReconnectIntervalMillis(final long reconnectIntervalMillis) {
+ public B setReconnectIntervalMillis(final long reconnectIntervalMillis) {
this.reconnectIntervalMillis = reconnectIntervalMillis;
- return this;
+ return asBuilder();
}
- public Builder setSecurityCredentials(final String securityCredentials) {
+ public B setSecurityCredentials(final String securityCredentials) {
this.securityCredentials = securityCredentials;
- return this;
+ return asBuilder();
}
- public Builder setSecurityPrincipalName(final String securityPrincipalName) {
+ public B setSecurityPrincipalName(final String securityPrincipalName) {
this.securityPrincipalName = securityPrincipalName;
- return this;
+ return asBuilder();
}
- public Builder setUrlPkgPrefixes(final String urlPkgPrefixes) {
+ public B setUrlPkgPrefixes(final String urlPkgPrefixes) {
this.urlPkgPrefixes = urlPkgPrefixes;
- return this;
+ return asBuilder();
}
- /**
- * Sets the user name.
- * @param username The user's name.
- * @deprecated Use {@link #setUserName(String)}.
- * @return the Builder.
- */
- @Deprecated
- public Builder setUsername(final String username) {
- this.userName = username;
- return this;
- }
-
- public Builder setUserName(final String userName) {
+ public B setUserName(final String userName) {
this.userName = userName;
- return this;
+ return asBuilder();
}
/**
@@ -242,35 +186,36 @@
*/
@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
+ @PluginFactory
public static Builder newBuilder() {
return new Builder();
}
- private volatile JmsManager manager;
+ private final JmsManager manager;
/**
* @param name The Appender's name.
* @param filter The filter to attach to the Appender, if any.
* @param layout The layout to use to render the event.
* @param ignoreExceptions true if exceptions should be ignore, false otherwise.
+ * @param properties TODO
* @param manager The JMSManager.
* @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<? extends Serializable> layout,
- final boolean ignoreExceptions, final JmsManager manager) throws JMSException {
- super(name, filter, layout, ignoreExceptions);
+ final boolean ignoreExceptions, Property[] properties, final JmsManager manager) throws JMSException {
+ super(name, filter, layout, ignoreExceptions, properties);
this.manager = manager;
}
diff --git a/log4j-jms/src/main/java/org/apache/logging/log4j/jms/appender/JmsManager.java b/log4j-jms/src/main/java/org/apache/logging/log4j/jms/appender/JmsManager.java
index 77b703a..1ed2ba5 100644
--- a/log4j-jms/src/main/java/org/apache/logging/log4j/jms/appender/JmsManager.java
+++ b/log4j-jms/src/main/java/org/apache/logging/log4j/jms/appender/JmsManager.java
@@ -33,7 +33,6 @@
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;
@@ -129,24 +128,24 @@
try {
return new JmsManager(name, data);
} catch (final Exception e) {
- LOGGER.error("Error creating JmsManager using JmsManagerConfiguration [{}]", data, e);
+ logger().error("Error creating JmsManager using JmsManagerConfiguration [{}]", data, e);
return null;
}
}
}
/**
- * Handles reconnecting to a Socket on a Thread.
+ * Handles reconnecting to JMS on a Thread.
*/
private 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 +174,7 @@
reconnector = null;
shutdown = true;
}
- LOGGER.debug("Connection reestablished to {}", configuration);
+ logger().debug("Connection reestablished to {}", configuration);
}
@Override
@@ -185,7 +184,7 @@
sleep(configuration.getReconnectIntervalMillis());
reconnect();
} catch (final InterruptedException | JMSException | NamingException e) {
- LOGGER.debug("Cannot reestablish JMS connection to {}: {}", configuration, e.getLocalizedMessage(),
+ logger().debug("Cannot reestablish JMS connection to {}: {}", configuration, e.getLocalizedMessage(),
e);
} finally {
latch.countDown();
@@ -199,14 +198,13 @@
}
- private static final Logger LOGGER = StatusLogger.getLogger();
-
static final JmsManagerFactory FACTORY = new JmsManagerFactory();
/**
* Gets a JmsManager using the specified configuration parameters.
*
* @param name The name to use for this JmsManager.
+ * @param jndiProperties JNDI properties.
* @param connectionFactoryName The binding name for the {@link javax.jms.ConnectionFactory}.
* @param destinationName The binding name for the {@link javax.jms.Destination}.
* @param userName The userName to connect with or {@code null} for no authentication.
@@ -332,10 +330,6 @@
* message will be serialized to a String.
* </p>
* <p>
- * When using a layout such as {@link org.apache.logging.log4j.core.layout.SerializedLayout}, the LogEvent message
- * will be serialized as a Java object.
- * </p>
- * <p>
* When using a layout such as {@link org.apache.logging.log4j.core.layout.MessageLayout} and the LogEvent message
* is a Log4j MapMessage, the message will be serialized as a JMS MapMessage.
* </p>
@@ -444,10 +438,10 @@
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) {
@@ -460,17 +454,17 @@
closeJndiManager();
reconnector.reconnect();
} catch (NamingException | JMSException reconnEx) {
- LOGGER.debug("Cannot reestablish JMS connection to {}: {}; starting reconnector thread {}",
+ 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(),
+ String.format("Error sending to %s after reestablishing JMS connection for %s", getName(),
configuration),
causeEx);
}
diff --git a/log4j-jms/src/site/manual/index.md b/log4j-jms/src/site/manual/index.md
index 8ef1b47..295c2d5 100644
--- a/log4j-jms/src/site/manual/index.md
+++ b/log4j-jms/src/site/manual/index.md
@@ -18,7 +18,7 @@
# Apache Log4j Java Message Service (JMS) module
-As of Log4j 2.11.0, JMS support has moved from the existing module logj-core to the new module log4j-jms.
+As of Log4j 2.11.0, JMS support has moved from the existing module log4j-core to the new module log4j-jms.
## Requirements
diff --git a/log4j-jms/src/test/java/org/apache/logging/log4j/jms/appender/JmsAppenderTest.java b/log4j-jms/src/test/java/org/apache/logging/log4j/jms/appender/JmsAppenderTest.java
index 4921719..dce981b 100644
--- a/log4j-jms/src/test/java/org/apache/logging/log4j/jms/appender/JmsAppenderTest.java
+++ b/log4j-jms/src/test/java/org/apache/logging/log4j/jms/appender/JmsAppenderTest.java
@@ -33,6 +33,7 @@
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;
@@ -44,7 +45,9 @@
import org.apache.logging.log4j.core.impl.Log4jLogEvent;
import org.apache.logging.log4j.junit.JndiRule;
import org.apache.logging.log4j.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.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -58,6 +61,7 @@
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!";
@@ -66,9 +70,12 @@
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");
@@ -76,10 +83,23 @@
@Rule
public RuleChain rules = RuleChain.outerRule(jndiRule).around(ctx);
+ 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<String, Object> createBindings() {
final ConcurrentHashMap<String, Object> 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);
@@ -87,20 +107,30 @@
return map;
}
- 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.createTextMessage(anyString())).willReturn(textMessage);
- given(session.createObjectMessage(isA(Serializable.class))).willReturn(objectMessage);
+ 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 3 appenders all connecting to the same ConnectionFactory
- then(connection).should(times(3)).start();
+ // we have 4 appenders all connecting to the same ConnectionFactory
+ then(connection).should(times(4)).start();
}
@Test
@@ -117,13 +147,26 @@
}
@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);
+ then(session).should().createTextMessage(eq(expected.getMessage().getFormat()));
+ then(textMessage).should().setJMSTimestamp(anyLong());
+ then(messageProducer).should().send(textMessage);
appender.stop();
then(session).should().close();
then(connection).should().close();
@@ -134,21 +177,12 @@
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);
+ then(session).should().createTextMessage(eq(expected.getMessage().getFormat()));
+ then(textMessage).should().setJMSTimestamp(anyLong());
+ then(messageProducer).should().send(textMessage);
appender.stop();
then(session).should().close();
then(connection).should().close();
}
- private static Log4jLogEvent createLogEvent() {
- return Log4jLogEvent.newBuilder()
- .setLoggerName(JmsAppenderTest.class.getName())
- .setLoggerFqcn(JmsAppenderTest.class.getName())
- .setLevel(Level.INFO)
- .setMessage(new SimpleMessage(LOG_MESSAGE))
- .build();
- }
-
-}
\ No newline at end of file
+}
diff --git a/log4j-jmx-gui/pom.xml b/log4j-jmx-gui/pom.xml
index 9e24276..a9177d8 100644
--- a/log4j-jmx-gui/pom.xml
+++ b/log4j-jmx-gui/pom.xml
@@ -129,6 +129,7 @@
project -->
<detectOfflineLinks>false</detectOfflineLinks>
<linksource>true</linksource>
+ <source>8</source>
</configuration>
<reportSets>
<reportSet>
diff --git a/log4j-jpa/pom.xml b/log4j-jpa/pom.xml
index 2521032..07192ca 100644
--- a/log4j-jpa/pom.xml
+++ b/log4j-jpa/pom.xml
@@ -135,6 +135,7 @@
<!-- module link generation is completely broken in the javadoc plugin for a multi-module non-aggregating project -->
<detectOfflineLinks>false</detectOfflineLinks>
<linksource>true</linksource>
+ <source>8</source>
</configuration>
<reportSets>
<reportSet>
diff --git a/log4j-jpa/revapi.json b/log4j-jpa/revapi.json
new file mode 100644
index 0000000..04f119a
--- /dev/null
+++ b/log4j-jpa/revapi.json
@@ -0,0 +1,108 @@
+[
+ {
+ "extension": "revapi.ignore",
+ "configuration": [
+ {
+ "code": "java.method.removed",
+ "old": "method void org.apache.logging.log4j.core.appender.db.jpa.JpaDatabaseManager::writeInternal(org.apache.logging.log4j.core.LogEvent)",
+ "justification": "Method not needed"
+ },
+ {
+ "code": "java.class.removed",
+ "old": "class org.apache.logging.log4j.core.appender.db.jpa.AbstractLogEventWrapperEntity",
+ "justification": "Package was renamed to conform to the Java 9 module system"
+
+ },
+ {
+ "code": "java.class.removed",
+ "old": "class org.apache.logging.log4j.core.appender.db.jpa.BasicLogEventEntity",
+ "justification": "Package was renamed to conform to the Java 9 module system"
+
+ },
+ {
+ "code": "java.class.removed",
+ "old": "class org.apache.logging.log4j.core.appender.db.jpa.JpaAppender",
+ "justification": "Package was renamed to conform to the Java 9 module system"
+
+ },
+ {
+ "code": "java.class.removed",
+ "old": "class org.apache.logging.log4j.core.appender.db.jpa.JpaDatabaseManager",
+ "justification": "Package was renamed to conform to the Java 9 module system"
+
+ },
+ {
+ "code": "java.class.removed",
+ "old": "class org.apache.logging.log4j.core.appender.db.jpa.converter.ContextDataAttributeConverter",
+ "justification": "Package was renamed to conform to the Java 9 module system"
+
+ },
+ {
+ "code": "java.class.removed",
+ "old": "class org.apache.logging.log4j.core.appender.db.jpa.converter.ContextDataJsonAttributeConverter",
+ "justification": "Package was renamed to conform to the Java 9 module system"
+
+ },
+ {
+ "code": "java.class.removed",
+ "old": "class org.apache.logging.log4j.core.appender.db.jpa.converter.ContextMapAttributeConverter",
+ "justification": "Package was renamed to conform to the Java 9 module system"
+
+ },
+ {
+ "code": "java.class.removed",
+ "old": "class org.apache.logging.log4j.core.appender.db.jpa.converter.ContextMapJsonAttributeConverter",
+ "justification": "Package was renamed to conform to the Java 9 module system"
+
+ },
+ {
+ "code": "java.class.removed",
+ "old": "class org.apache.logging.log4j.core.appender.db.jpa.converter.ContextStackAttributeConverter",
+ "justification": "Package was renamed to conform to the Java 9 module system"
+
+ },
+ {
+ "code": "java.class.removed",
+ "old": "class org.apache.logging.log4j.core.appender.db.jpa.converter.ContextStackJsonAttributeConverter",
+ "justification": "Package was renamed to conform to the Java 9 module system"
+
+ },
+ {
+ "code": "java.class.removed",
+ "old": "class org.apache.logging.log4j.core.appender.db.jpa.converter.InstantAttributeConverter",
+ "justification": "Package was renamed to conform to the Java 9 module system"
+
+ },
+ {
+ "code": "java.class.removed",
+ "old": "class org.apache.logging.log4j.core.appender.db.jpa.converter.LevelAttributeConverter",
+ "justification": "Package was renamed to conform to the Java 9 module system"
+
+ },
+ {
+ "code": "java.class.removed",
+ "old": "class org.apache.logging.log4j.core.appender.db.jpa.converter.MarkerAttributeConverter",
+ "justification": "Package was renamed to conform to the Java 9 module system"
+
+ },
+ {
+ "code": "java.class.removed",
+ "old": "class org.apache.logging.log4j.core.appender.db.jpa.converter.MessageAttributeConverter",
+ "justification": "Package was renamed to conform to the Java 9 module system"
+
+ },
+ {
+ "code": "java.class.removed",
+ "old": "class org.apache.logging.log4j.core.appender.db.jpa.converter.StackTraceElementAttributeConverter",
+ "justification": "Package was renamed to conform to the Java 9 module system"
+
+ },
+ {
+ "code": "java.class.removed",
+ "old": "class org.apache.logging.log4j.core.appender.db.jpa.converter.ThrowableAttributeConverter",
+ "justification": "Package was renamed to conform to the Java 9 module system"
+
+ }
+ ]
+ }
+]
diff --git a/log4j-jpa/src/main/java/org/apache/logging/log4j/jpa/appender/BasicLogEventEntity.java b/log4j-jpa/src/main/java/org/apache/logging/log4j/jpa/appender/BasicLogEventEntity.java
index f48fd46..366ced6 100644
--- a/log4j-jpa/src/main/java/org/apache/logging/log4j/jpa/appender/BasicLogEventEntity.java
+++ b/log4j-jpa/src/main/java/org/apache/logging/log4j/jpa/appender/BasicLogEventEntity.java
@@ -45,7 +45,7 @@
* columns, override the necessary accessor methods defined in this class with the same annotations plus the
* {@link javax.persistence.Column @Column} annotation to specify the column name.<br>
* <br>
- * The {@link #getContextMap()} and {@link #getContextStack()} attributes in this entity use the
+ * The #getContextMap() and {@link #getContextStack()} attributes in this entity use the
* {@link ContextMapAttributeConverter} and {@link ContextStackAttributeConverter}, respectively. These convert the
* properties to simple strings that cannot be converted back to the properties. If you wish to instead convert these to
* a reversible JSON string, override these attributes with the same annotations but use the
@@ -227,20 +227,6 @@
}
/**
- * Gets the context map. Annotated with {@code @Convert(converter = ContextMapAttributeConverter.class)}.
- *
- * @return the context map.
- * @see ContextMapAttributeConverter
- * @see org.apache.logging.log4j.jpa.converter.ContextMapJsonAttributeConverter
- */
- @SuppressWarnings("deprecation")
- @Override
- @Convert(converter = ContextMapAttributeConverter.class)
- public Map<String, String> getContextMap() {
- return this.getWrappedEvent().getContextMap();
- }
-
- /**
* Gets the context stack. Annotated with {@code @Convert(converter = ContextStackAttributeConverter.class)}.
*
* @return the context stack.
diff --git a/log4j-jpa/src/main/java/org/apache/logging/log4j/jpa/appender/JpaAppender.java b/log4j-jpa/src/main/java/org/apache/logging/log4j/jpa/appender/JpaAppender.java
index 46fc66c..602f6e1 100644
--- a/log4j-jpa/src/main/java/org/apache/logging/log4j/jpa/appender/JpaAppender.java
+++ b/log4j-jpa/src/main/java/org/apache/logging/log4j/jpa/appender/JpaAppender.java
@@ -24,10 +24,11 @@
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.appender.AbstractAppender;
import org.apache.logging.log4j.core.appender.db.AbstractDatabaseAppender;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.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.Property;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginAttribute;
+import org.apache.logging.log4j.plugins.PluginElement;
+import org.apache.logging.log4j.plugins.PluginFactory;
import org.apache.logging.log4j.core.util.Booleans;
import org.apache.logging.log4j.util.LoaderUtil;
import org.apache.logging.log4j.util.Strings;
@@ -45,8 +46,8 @@
private final String description;
private JpaAppender(final String name, final Filter filter, final boolean ignoreExceptions,
- final JpaDatabaseManager manager) {
- super(name, filter, ignoreExceptions, manager);
+ Property[] properties, final JpaDatabaseManager manager) {
+ super(name, filter, null, ignoreExceptions, properties, manager);
this.description = this.getName() + "{ manager=" + this.getManager() + " }";
}
@@ -71,12 +72,12 @@
*/
@PluginFactory
public static JpaAppender createAppender(
- @PluginAttribute("name") final String name,
+ @PluginAttribute final String name,
@PluginAttribute("ignoreExceptions") final String ignore,
- @PluginElement("Filter") final Filter filter,
- @PluginAttribute("bufferSize") final String bufferSize,
- @PluginAttribute("entityClassName") final String entityClassName,
- @PluginAttribute("persistenceUnitName") final String persistenceUnitName) {
+ @PluginElement final Filter filter,
+ @PluginAttribute final String bufferSize,
+ @PluginAttribute final String entityClassName,
+ @PluginAttribute final String persistenceUnitName) {
if (Strings.isEmpty(entityClassName) || Strings.isEmpty(persistenceUnitName)) {
LOGGER.error("Attributes entityClassName and persistenceUnitName are required for JPA Appender.");
return null;
@@ -110,7 +111,7 @@
return null;
}
- return new JpaAppender(name, filter, ignoreExceptions, manager);
+ return new JpaAppender(name, filter, ignoreExceptions, null, manager);
} catch (final ClassNotFoundException e) {
LOGGER.error("Could not load entity class [{}].", entityClassName, e);
return null;
diff --git a/log4j-jpa/src/main/java/org/apache/logging/log4j/jpa/appender/JpaDatabaseManager.java b/log4j-jpa/src/main/java/org/apache/logging/log4j/jpa/appender/JpaDatabaseManager.java
index fda26af..bc07491 100644
--- a/log4j-jpa/src/main/java/org/apache/logging/log4j/jpa/appender/JpaDatabaseManager.java
+++ b/log4j-jpa/src/main/java/org/apache/logging/log4j/jpa/appender/JpaDatabaseManager.java
@@ -84,12 +84,6 @@
}
}
- @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.entityManagerFactory == null || this.entityManager == null
diff --git a/log4j-jpa/src/main/java/org/apache/logging/log4j/jpa/converter/StackTraceElementAttributeConverter.java b/log4j-jpa/src/main/java/org/apache/logging/log4j/jpa/converter/StackTraceElementAttributeConverter.java
index a6ffe31..dfbf7e7 100644
--- a/log4j-jpa/src/main/java/org/apache/logging/log4j/jpa/converter/StackTraceElementAttributeConverter.java
+++ b/log4j-jpa/src/main/java/org/apache/logging/log4j/jpa/converter/StackTraceElementAttributeConverter.java
@@ -72,7 +72,7 @@
// we don't care
}
} else {
- fileName = parenthesisContents.substring(0);
+ fileName = parenthesisContents;
}
}
diff --git a/log4j-jpa/src/site/manual/index.md b/log4j-jpa/src/site/manual/index.md
index 235dfb8..6b1b12e 100644
--- a/log4j-jpa/src/site/manual/index.md
+++ b/log4j-jpa/src/site/manual/index.md
@@ -18,7 +18,7 @@
# Log4j Java Persistence API module
-As of Log4j 2.11.0, JPA support has moved from the existing module logj-core to the new module log4j-jpa.
+As of Log4j 2.11.0, JPA support has moved from the existing module log4j-core to the new module log4j-jpa.
## Requirements
diff --git a/log4j-jpa/src/test/java/org/apache/logging/log4j/jpa/appender/LogEventEntityTest.java b/log4j-jpa/src/test/java/org/apache/logging/log4j/jpa/appender/LogEventEntityTest.java
index 502b4fa..dc83f8f 100644
--- a/log4j-jpa/src/test/java/org/apache/logging/log4j/jpa/appender/LogEventEntityTest.java
+++ b/log4j-jpa/src/test/java/org/apache/logging/log4j/jpa/appender/LogEventEntityTest.java
@@ -17,8 +17,6 @@
package org.apache.logging.log4j.jpa.appender;
-import java.util.Map;
-
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.Marker;
import org.apache.logging.log4j.ThreadContext.ContextStack;
@@ -26,7 +24,6 @@
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.jpa.appender.AbstractLogEventWrapperEntity;
import org.apache.logging.log4j.message.Message;
import org.junit.Assert;
import org.junit.Test;
@@ -40,11 +37,6 @@
private static final long serialVersionUID = 2L;
@Override
- public Map<String, String> getContextMap() {
- return null;
- }
-
- @Override
public ContextStack getContextStack() {
return null;
}
diff --git a/log4j-jpa/src/test/java/org/apache/logging/log4j/jpa/appender/TestBaseEntity.java b/log4j-jpa/src/test/java/org/apache/logging/log4j/jpa/appender/TestBaseEntity.java
index 522372a..ae85df1 100644
--- a/log4j-jpa/src/test/java/org/apache/logging/log4j/jpa/appender/TestBaseEntity.java
+++ b/log4j-jpa/src/test/java/org/apache/logging/log4j/jpa/appender/TestBaseEntity.java
@@ -164,13 +164,6 @@
return this.getWrappedEvent().getThrownProxy();
}
- @SuppressWarnings("deprecation")
- @Override
- @Transient
- public Map<String, String> getContextMap() {
- return this.getWrappedEvent().getContextMap();
- }
-
@Override
@Transient
public ReadOnlyStringMap getContextData() {
diff --git a/log4j-jpa/src/test/java/org/apache/logging/log4j/jpa/appender/TestBasicEntity.java b/log4j-jpa/src/test/java/org/apache/logging/log4j/jpa/appender/TestBasicEntity.java
index b98d525..12a38c4 100644
--- a/log4j-jpa/src/test/java/org/apache/logging/log4j/jpa/appender/TestBasicEntity.java
+++ b/log4j-jpa/src/test/java/org/apache/logging/log4j/jpa/appender/TestBasicEntity.java
@@ -16,10 +16,7 @@
*/
package org.apache.logging.log4j.jpa.appender;
-import java.util.Map;
-
import javax.persistence.Column;
-import javax.persistence.Convert;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
@@ -27,8 +24,6 @@
import javax.persistence.Table;
import org.apache.logging.log4j.core.LogEvent;
-import org.apache.logging.log4j.jpa.appender.BasicLogEventEntity;
-import org.apache.logging.log4j.jpa.converter.ContextMapJsonAttributeConverter;
@Entity
@Table(name = "jpaBasicLogEntry")
@@ -57,10 +52,4 @@
this.id = id;
}
- @Override
- @Convert(converter = ContextMapJsonAttributeConverter.class)
- @Column(name = "contextMapJson")
- public Map<String, String> getContextMap() {
- return super.getContextMap();
- }
}
diff --git a/log4j-jpl/pom.xml b/log4j-jpl/pom.xml
new file mode 100644
index 0000000..d0cfaca
--- /dev/null
+++ b/log4j-jpl/pom.xml
@@ -0,0 +1,288 @@
+<?xml version="1.0" encoding="UTF-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 "License"); you may not use this file except in compliance with
+ ~ the License. You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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 xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <artifactId>log4j</artifactId>
+ <groupId>org.apache.logging.log4j</groupId>
+ <version>3.0.0-SNAPSHOT</version>
+ <relativePath>../</relativePath>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <properties>
+ <log4jParentDir>${basedir}/..</log4jParentDir>
+ <module.name>org.apache.logging.log4j.jpl</module.name>
+ </properties>
+
+ <artifactId>log4j-jpl</artifactId>
+ <name>Apache Log4j JDK Platform Logging Adapter</name>
+ <description>The Apache Log4j implementation of java.lang.System.LoggerFinder</description>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-core</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-core</artifactId>
+ <type>test-jar</type>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.hamcrest</groupId>
+ <artifactId>hamcrest-all</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <!-- Include the standard NOTICE and LICENSE -->
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-remote-resources-plugin</artifactId>
+ <executions>
+ <execution>
+ <goals>
+ <goal>process</goal>
+ </goals>
+ <configuration>
+ <skip>false</skip>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <configuration>
+ <instructions>
+ <Fragment-Host>org.apache.logging.log4j.core</Fragment-Host>
+ <Export-Package>org.apache.logging.log4j.jpl</Export-Package>
+ </instructions>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-toolchains-plugin</artifactId>
+ <version>1.1</version>
+ <executions>
+ <execution>
+ <goals>
+ <goal>toolchain</goal>
+ </goals>
+ </execution>
+ </executions>
+ <configuration>
+ <toolchains>
+ <jdk>
+ <version>[11, )</version>
+ </jdk>
+ </toolchains>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>default-compile</id>
+ <phase>compile</phase>
+ <goals>
+ <goal>compile</goal>
+ </goals>
+ </execution>
+ <execution>
+ <id>default-testCompile</id>
+ <phase>test-compile</phase>
+ <goals>
+ <goal>testCompile</goal>
+ </goals>
+ </execution>
+ </executions>
+ <configuration>
+ <source>11</source>
+ <target>11</target>
+ <release>11</release>
+ <proc>none</proc>
+ <!-- disable errorprone -->
+ <compilerId>javac</compilerId>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <!-- Do not upgrade until https://issues.apache.org/jira/browse/SUREFIRE-720 is fixed -->
+ <version>2.13</version>
+ <configuration>
+ <excludes combine.self="override" />
+ </configuration>
+ <executions>
+ <execution>
+ <id>test</id>
+ <phase>test</phase>
+ <goals>
+ <goal>test</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+ <reporting>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-changes-plugin</artifactId>
+ <version>${changes.plugin.version}</version>
+ <reportSets>
+ <reportSet>
+ <reports>
+ <report>changes-report</report>
+ </reports>
+ </reportSet>
+ </reportSets>
+ <configuration>
+ <issueLinkTemplate>%URL%/show_bug.cgi?id=%ISSUE%</issueLinkTemplate>
+ <useJql>true</useJql>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-checkstyle-plugin</artifactId>
+ <version>${checkstyle.plugin.version}</version>
+ <configuration>
+ <configLocation>${log4jParentDir}/checkstyle.xml</configLocation>
+ <suppressionsLocation>${log4jParentDir}/checkstyle-suppressions.xml</suppressionsLocation>
+ <enableRulesSummary>false</enableRulesSummary>
+ <propertyExpansion>basedir=${basedir}</propertyExpansion>
+ <propertyExpansion>licensedir=${log4jParentDir}/checkstyle-header.txt</propertyExpansion>
+ <excludes>**/module-info.java</excludes>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-javadoc-plugin</artifactId>
+ <version>${javadoc.plugin.version}</version>
+ <configuration>
+ <bottom><![CDATA[<p align="center">Copyright © {inceptionYear}-{currentYear} {organizationName}. All Rights Reserved.<br />
+ Apache Logging, Apache Log4j, Log4j, Apache, the Apache feather logo, the Apache Logging project logo,
+ and the Apache Log4j logo are trademarks of The Apache Software Foundation.</p>]]></bottom>
+ <!-- module link generation is completely broken in the javadoc plugin for a multi-module non-aggregating
+ project -->
+ <detectOfflineLinks>false</detectOfflineLinks>
+ <linksource>true</linksource>
+ <failOnError>false</failOnError>
+ </configuration>
+ <reportSets>
+ <reportSet>
+ <id>non-aggregate</id>
+ <reports>
+ <report>javadoc</report>
+ </reports>
+ </reportSet>
+ </reportSets>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jxr-plugin</artifactId>
+ <version>${jxr.plugin.version}</version>
+ <reportSets>
+ <reportSet>
+ <id>non-aggregate</id>
+ <reports>
+ <report>jxr</report>
+ </reports>
+ </reportSet>
+ <reportSet>
+ <id>aggregate</id>
+ <reports>
+ <report>aggregate</report>
+ </reports>
+ </reportSet>
+ </reportSets>
+ </plugin>
+ </plugins>
+ </reporting>
+ <profiles>
+ <profile>
+ <id>java11-module</id>
+ <activation>
+ <jdk>[11,)</jdk>
+ </activation>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <!-- Use Maven Surefire 2.21 which brings module support -->
+ <version>${surefire.plugin.version}</version>
+ <!-- Disable forked VM as 2.21 yields https://issues.apache.org/jira/browse/SUREFIRE-720 -->
+ <configuration combine.self="override"/>
+ <executions>
+ <execution>
+ <id>test</id>
+ <phase>test</phase>
+ <goals>
+ <goal>test</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+ <reporting>
+ <plugins>
+ <plugin>
+ <!-- spotbugs is not compatible with toolchain and needs same JDK than one use to compile -->
+ <groupId>com.github.spotbugs</groupId>
+ <artifactId>spotbugs-maven-plugin</artifactId>
+ <configuration>
+ <fork>true</fork>
+ <jvmArgs>-Duser.language=en</jvmArgs>
+ <threshold>Normal</threshold>
+ <effort>Default</effort>
+ <excludeFilterFile>${log4jParentDir}/spotbugs-exclude-filter.xml</excludeFilterFile>
+ </configuration>
+ </plugin>
+ <plugin>
+ <!-- pmd is not compatible with toolchain and needs same JDK than one use to compile -->
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-pmd-plugin</artifactId>
+ <version>${pmd.plugin.version}</version>
+ <configuration>
+ <targetJdk>${maven.compiler.target}</targetJdk>
+ </configuration>
+ </plugin>
+ </plugins>
+ </reporting>
+ </profile>
+ </profiles>
+</project>
diff --git a/log4j-jpl/src/main/java/module-info.java b/log4j-jpl/src/main/java/module-info.java
new file mode 100644
index 0000000..2788318
--- /dev/null
+++ b/log4j-jpl/src/main/java/module-info.java
@@ -0,0 +1,21 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+module org.apache.logging.log4j.jpl {
+ requires org.apache.logging.log4j;
+
+ provides java.lang.System.LoggerFinder with org.apache.logging.log4j.jpl.Log4jSystemLoggerFinder;
+}
diff --git a/log4j-jpl/src/main/java/org/apache/logging/log4j/jpl/Log4jSystemLogger.java b/log4j-jpl/src/main/java/org/apache/logging/log4j/jpl/Log4jSystemLogger.java
new file mode 100644
index 0000000..925ca71
--- /dev/null
+++ b/log4j-jpl/src/main/java/org/apache/logging/log4j/jpl/Log4jSystemLogger.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.jpl;
+
+import java.lang.System.Logger;
+import java.util.MissingResourceException;
+import java.util.Objects;
+import java.util.ResourceBundle;
+import java.util.function.Supplier;
+
+import org.apache.logging.log4j.spi.ExtendedLogger;
+
+/**
+ * JPL {@link Logger logger} implementation that uses Log4j.
+ * Implement all default {@link Logger} methods to ensure proper class resolution
+ *
+ * @since 2.14
+ */
+public class Log4jSystemLogger implements Logger {
+
+ private final ExtendedLogger logger;
+
+ private static final String FQCN = Log4jSystemLogger.class.getName();
+
+ public Log4jSystemLogger(final ExtendedLogger logger) {
+ this.logger = logger;
+ }
+
+ @Override
+ public String getName() {
+ return logger.getName();
+ }
+
+ @Override
+ public boolean isLoggable(final Level level) {
+ return logger.isEnabled(getLevel(level));
+ }
+
+ @Override
+ public void log(Level level, String msg) {
+ log(level, (ResourceBundle) null, msg, (Object[]) null);
+ }
+
+ @Override
+ public void log(Level level, Supplier<String> msgSupplier) {
+ Objects.requireNonNull(msgSupplier);
+ if (isLoggable(Objects.requireNonNull(level))) {
+ log(level, (ResourceBundle) null, msgSupplier.get(), (Object[]) null);
+ }
+ }
+
+ @Override
+ public void log(Level level, Object obj) {
+ Objects.requireNonNull(obj);
+ if (isLoggable(Objects.requireNonNull(level))) {
+ log(level, (ResourceBundle) null, obj.toString(), (Object[]) null);
+ }
+ }
+
+ @Override
+ public void log(Level level, String msg, Throwable thrown) {
+ log(level, null, msg, thrown);
+ }
+
+ @Override
+ public void log(Level level, Supplier<String> msgSupplier, Throwable thrown) {
+ Objects.requireNonNull(msgSupplier);
+ if (isLoggable(Objects.requireNonNull(level))) {
+ log(level, null, msgSupplier.get(), thrown);
+ }
+ }
+
+ @Override
+ public void log(Level level, String format, Object... params) {
+ log(level, null, format, params);
+ }
+
+ @Override
+ public void log(final Level level, final ResourceBundle bundle, final String msg, final Throwable thrown) {
+ logger.logIfEnabled(FQCN, getLevel(level), null, getResource(bundle, msg), thrown);
+ }
+
+ @Override
+ public void log(final Level level, final ResourceBundle bundle, final String format, final Object... params) {
+ logger.logIfEnabled(FQCN, getLevel(level), null, getResource(bundle, format), params);
+ }
+
+ private static org.apache.logging.log4j.Level getLevel(final Level level) {
+ switch (level) {
+ case OFF:
+ return org.apache.logging.log4j.Level.OFF;
+ case ERROR:
+ return org.apache.logging.log4j.Level.ERROR;
+ case WARNING:
+ return org.apache.logging.log4j.Level.WARN;
+ case INFO:
+ return org.apache.logging.log4j.Level.INFO;
+ case DEBUG:
+ return org.apache.logging.log4j.Level.DEBUG;
+ case TRACE:
+ return org.apache.logging.log4j.Level.TRACE;
+ case ALL:
+ return org.apache.logging.log4j.Level.ALL;
+ }
+ return org.apache.logging.log4j.Level.ERROR;
+ }
+
+ private static String getResource(ResourceBundle bundle, String msg) {
+ if (bundle == null || msg == null) {
+ return msg;
+ }
+ try {
+ return bundle.getString(msg);
+ } catch (MissingResourceException e) {
+ // ignore
+ return msg;
+ } catch (ClassCastException ex) {
+ return bundle.getObject(msg).toString();
+ }
+ }
+}
diff --git a/log4j-jpl/src/main/java/org/apache/logging/log4j/jpl/Log4jSystemLoggerAdapter.java b/log4j-jpl/src/main/java/org/apache/logging/log4j/jpl/Log4jSystemLoggerAdapter.java
new file mode 100644
index 0000000..3db9b52
--- /dev/null
+++ b/log4j-jpl/src/main/java/org/apache/logging/log4j/jpl/Log4jSystemLoggerAdapter.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.jpl;
+
+import java.lang.System.Logger;
+import java.lang.System.LoggerFinder;
+
+import org.apache.logging.log4j.spi.AbstractLoggerAdapter;
+import org.apache.logging.log4j.spi.LoggerContext;
+import org.apache.logging.log4j.util.StackLocatorUtil;
+
+/**
+ * {@link Logger} registry implementation using just log4j-api.
+ *
+ * @since 2.14
+ */
+public class Log4jSystemLoggerAdapter extends AbstractLoggerAdapter<Logger> {
+
+ @Override
+ protected Logger newLogger(String name, LoggerContext context) {
+ return new Log4jSystemLogger(context.getLogger(name));
+ }
+
+ @Override
+ protected LoggerContext getContext() {
+ return getContext(StackLocatorUtil.getCallerClass(LoggerFinder.class));
+ }
+}
diff --git a/log4j-jpl/src/main/java/org/apache/logging/log4j/jpl/Log4jSystemLoggerFinder.java b/log4j-jpl/src/main/java/org/apache/logging/log4j/jpl/Log4jSystemLoggerFinder.java
new file mode 100644
index 0000000..cb8670d
--- /dev/null
+++ b/log4j-jpl/src/main/java/org/apache/logging/log4j/jpl/Log4jSystemLoggerFinder.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.jpl;
+
+import java.lang.System.Logger;
+
+/**
+ * @since 2.14
+ */
+public class Log4jSystemLoggerFinder extends System.LoggerFinder {
+
+ private final Log4jSystemLoggerAdapter loggerAdapter = new Log4jSystemLoggerAdapter();
+
+ @Override
+ public Logger getLogger(String name, Module module) {
+ return loggerAdapter.getLogger(name);
+ }
+}
diff --git a/log4j-jpl/src/main/resources/META-INF/services/java.lang.System$LoggerFinder b/log4j-jpl/src/main/resources/META-INF/services/java.lang.System$LoggerFinder
new file mode 100644
index 0000000..b959e7e
--- /dev/null
+++ b/log4j-jpl/src/main/resources/META-INF/services/java.lang.System$LoggerFinder
@@ -0,0 +1 @@
+org.apache.logging.log4j.jpl.Log4jSystemLoggerFinder
\ No newline at end of file
diff --git a/log4j-jpl/src/site/markdown/index.md b/log4j-jpl/src/site/markdown/index.md
new file mode 100644
index 0000000..1e5da79
--- /dev/null
+++ b/log4j-jpl/src/site/markdown/index.md
@@ -0,0 +1,30 @@
+<!-- vim: set syn=markdown : -->
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+# Log4j 2 JDK Platform Logging Adapter
+
+The Log4j 2 JDK Logging Adapter allow to route all System.Logger events to Log4j 2 APIs.
+
+## Requirements
+
+The JDK Platform Logging Adapter is dependent on the Log4j API as well as Java 11.
+
+## Usage
+
+Simply include the Log4j 2 JDK Platform Logging Adapter jar along with the Log4j2 jars to cause all System.Logger
+logging to be handled by Log4j 2.
diff --git a/log4j-jpl/src/site/site.xml b/log4j-jpl/src/site/site.xml
new file mode 100644
index 0000000..fccc81f
--- /dev/null
+++ b/log4j-jpl/src/site/site.xml
@@ -0,0 +1,52 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+-->
+<project name="Log4j JDK Platform Logging Adapter"
+ xmlns="http://maven.apache.org/DECORATION/1.4.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/DECORATION/1.4.0 http://maven.apache.org/xsd/decoration-1.4.0.xsd">
+ <body>
+ <links>
+ <item name="Apache" href="http://www.apache.org/" />
+ <item name="Logging Services" href="http://logging.apache.org/"/>
+ <item name="Log4j" href="../index.html"/>
+ </links>
+
+ <!-- Component-specific reports -->
+ <menu ref="reports"/>
+
+ <!-- Overall Project Info -->
+ <menu name="Log4j Project Information" img="icon-info-sign">
+ <item name="Dependencies" href="../dependencies.html" />
+ <item name="Dependency Convergence" href="../dependency-convergence.html" />
+ <item name="Dependency Management" href="../dependency-management.html" />
+ <item name="Project Team" href="../team-list.html" />
+ <item name="Mailing Lists" href="../mail-lists.html" />
+ <item name="Issue Tracking" href="../issue-tracking.html" />
+ <item name="Project License" href="../license.html" />
+ <item name="Source Repository" href="../source-repository.html" />
+ <item name="Project Summary" href="../project-summary.html" />
+ </menu>
+
+ <menu name="Log4j Project Reports" img="icon-cog">
+ <item name="Changes Report" href="../changes-report.html" />
+ <item name="JIRA Report" href="../jira-report.html" />
+ <item name="Surefire Report" href="../surefire-report.html" />
+ <item name="RAT Report" href="../rat-report.html" />
+ </menu>
+ </body>
+</project>
diff --git a/log4j-jpl/src/test/java/org/apache/logging/log4j/jpl/Log4jSystemLoggerTest.java b/log4j-jpl/src/test/java/org/apache/logging/log4j/jpl/Log4jSystemLoggerTest.java
new file mode 100644
index 0000000..ccc4b3c
--- /dev/null
+++ b/log4j-jpl/src/test/java/org/apache/logging/log4j/jpl/Log4jSystemLoggerTest.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.jpl;
+
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.hasSize;
+import static org.hamcrest.Matchers.instanceOf;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
+
+import java.lang.System.Logger;
+import java.util.List;
+
+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.test.appender.ListAppender;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public class Log4jSystemLoggerTest {
+
+ public static final String LOGGER_NAME = "Test";
+ protected Logger logger;
+ protected ListAppender eventAppender;
+ protected ListAppender stringAppender;
+
+ @Before
+ public void setUp() throws Exception {
+ logger = System.getLogger(LOGGER_NAME);
+ assertThat(logger, instanceOf(Log4jSystemLogger.class));
+ eventAppender = ListAppender.getListAppender("TestAppender");
+ stringAppender = ListAppender.getListAppender("StringAppender");
+ assertNotNull(eventAppender);
+ assertNotNull(stringAppender);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ if (eventAppender != null) {
+ eventAppender.clear();
+ }
+ if (stringAppender != null) {
+ stringAppender.clear();
+ }
+ }
+
+ @Test
+ public void testGetName() throws Exception {
+ assertThat(logger.getName(), equalTo(LOGGER_NAME));
+ }
+
+ @Test
+ public void testIsLoggable() throws Exception {
+ assertThat(logger.isLoggable(Logger.Level.ERROR), equalTo(true));
+ }
+
+ @Test
+ public void testLog() throws Exception {
+ logger.log(Logger.Level.INFO, "Informative message here.");
+ final List<LogEvent> events = eventAppender.getEvents();
+ assertThat(events, hasSize(1));
+ final LogEvent event = events.get(0);
+ assertThat(event, instanceOf(Log4jLogEvent.class));
+ assertEquals(Level.INFO, event.getLevel());
+ assertEquals(LOGGER_NAME, event.getLoggerName());
+ assertEquals("Informative message here.", event.getMessage().getFormattedMessage());
+ assertEquals(Log4jSystemLogger.class.getName(), event.getLoggerFqcn());
+ }
+
+ @Test
+ public void testLogWithCallingClass() throws Exception {
+ final Logger log = System.getLogger("Test.CallerClass");
+ log.log(Logger.Level.INFO, "Calling from LoggerTest");
+ final List<String> messages = stringAppender.getMessages();
+ assertThat(messages, hasSize(1));
+ final String message = messages.get(0);
+ assertEquals(Log4jSystemLoggerTest.class.getName(), message);
+ }
+
+ @Test
+ public void testCurlyBraces() {
+ testMessage("{message}");
+ }
+
+ @Test
+ public void testPercent() {
+ testMessage("message%s");
+ }
+
+ @Test
+ public void testPercentAndCurlyBraces() {
+ testMessage("message{%s}");
+ }
+
+ private void testMessage(final String string) {
+ logger.log(Logger.Level.INFO, "Test info " + string);
+ final List<LogEvent> events = eventAppender.getEvents();
+ assertThat(events, hasSize(1));
+ for (final LogEvent event : events) {
+ final String message = event.getMessage().getFormattedMessage();
+ assertThat(message, equalTo("Test info " + string));
+ }
+ }
+}
diff --git a/log4j-jpl/src/test/resources/log4j2-test.xml b/log4j-jpl/src/test/resources/log4j2-test.xml
new file mode 100644
index 0000000..27cef64
--- /dev/null
+++ b/log4j-jpl/src/test/resources/log4j2-test.xml
@@ -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.
+ -->
+
+<Configuration name="LoggerTest" status="OFF">
+ <Appenders>
+ <List name="TestAppender"/>
+ <List name="StringAppender">
+ <PatternLayout pattern="%class"/>
+ </List>
+ <Console name="Console" target="SYSTEM_ERR">
+ <PatternLayout pattern="%highlight{%p - %m%n}"/>
+ </Console>
+ </Appenders>
+ <Loggers>
+ <Logger name="Test" level="DEBUG" additivity="false">
+ <AppenderRef ref="TestAppender"/>
+ </Logger>
+ <Logger name="Test.CallerClass" level="DEBUG" additivity="false">
+ <AppenderRef ref="StringAppender"/>
+ </Logger>
+ <Root level="ERROR">
+ <AppenderRef ref="Console"/>
+ </Root>
+ </Loggers>
+</Configuration>
diff --git a/log4j-jul/pom.xml b/log4j-jul/pom.xml
index d0c3399..64d7040 100644
--- a/log4j-jul/pom.xml
+++ b/log4j-jul/pom.xml
@@ -60,6 +60,12 @@
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
+ <!-- Required for AsyncLogger testing -->
+ <dependency>
+ <groupId>com.lmax</groupId>
+ <artifactId>disruptor</artifactId>
+ <scope>test</scope>
+ </dependency>
</dependencies>
<build>
@@ -89,23 +95,52 @@
</instructions>
</configuration>
</plugin>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-surefire-plugin</artifactId>
- <version>${surefire.plugin.version}</version>
- <configuration>
- <systemPropertyVariables>
- <java.awt.headless>true</java.awt.headless>
- </systemPropertyVariables>
- <argLine>-Xms256m -Xmx1024m</argLine>
- <forkCount>1</forkCount>
- <reuseForks>false</reuseForks>
- <excludes>
- <exclude>${log4j.skip.test1}</exclude>
- <exclude>${log4j.skip.test2}</exclude>
- </excludes>
- </configuration>
- </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <version>${surefire.plugin.version}</version>
+ <configuration>
+ <systemPropertyVariables>
+ <java.awt.headless>true</java.awt.headless>
+ </systemPropertyVariables>
+ <argLine>-Xms256m -Xmx1024m</argLine>
+ <forkCount>1</forkCount>
+ <reuseForks>false</reuseForks>
+ </configuration>
+ <executions>
+ <execution>
+ <id>default-test</id>
+ <phase>test</phase>
+ <goals>
+ <goal>test</goal>
+ </goals>
+ <configuration>
+ <excludes>
+ <exclude>${log4j.skip.test1}</exclude>
+ <exclude>${log4j.skip.test2}</exclude>
+ <exclude>Log4jBridgeHandlerTest.java</exclude>
+ </excludes>
+ </configuration>
+ </execution>
+ <execution>
+ <!-- this test needs some special configuration, thus its own run -->
+ <id>bridgeHandler-test</id>
+ <phase>test</phase>
+ <goals>
+ <goal>test</goal>
+ </goals>
+ <configuration>
+ <includes>
+ <include>Log4jBridgeHandlerTest.java</include>
+ </includes>
+ <systemPropertyVariables>
+ <java.util.logging.config.file>src/test/resources/logging-test.properties</java.util.logging.config.file>
+ <log4j2.configurationFile>log4j2-julBridge-test.xml</log4j2.configurationFile>
+ </systemPropertyVariables>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
</plugins>
</build>
<reporting>
@@ -151,6 +186,7 @@
project -->
<detectOfflineLinks>false</detectOfflineLinks>
<linksource>true</linksource>
+ <source>8</source>
</configuration>
<reportSets>
<reportSet>
diff --git a/log4j-jul/src/main/java/org/apache/logging/log4j/jul/ApiLogger.java b/log4j-jul/src/main/java/org/apache/logging/log4j/jul/ApiLogger.java
index a906183..96512e6 100644
--- a/log4j-jul/src/main/java/org/apache/logging/log4j/jul/ApiLogger.java
+++ b/log4j-jul/src/main/java/org/apache/logging/log4j/jul/ApiLogger.java
@@ -119,22 +119,38 @@
@Override
public void log(final Level level, final String msg) {
- logger.log(LevelTranslator.toLevel(level), msg);
+ if (getFilter() == null) {
+ logger.log(LevelTranslator.toLevel(level), msg);
+ } else {
+ super.log(level, msg);
+ }
}
@Override
public void log(final Level level, final String msg, final Object param1) {
- logger.log(LevelTranslator.toLevel(level), msg, param1);
+ if (getFilter() == null) {
+ logger.log(LevelTranslator.toLevel(level), msg, param1);
+ } else {
+ super.log(level, msg, param1);
+ }
}
@Override
public void log(final Level level, final String msg, final Object[] params) {
- logger.log(LevelTranslator.toLevel(level), msg, params);
+ if (getFilter() == null) {
+ logger.log(LevelTranslator.toLevel(level), msg, params);
+ } else {
+ super.log(level, msg, params);
+ }
}
@Override
public void log(final Level level, final String msg, final Throwable thrown) {
- logger.log(LevelTranslator.toLevel(level), msg, thrown);
+ if (getFilter() == null) {
+ logger.log(LevelTranslator.toLevel(level), msg, thrown);
+ } else {
+ super.log(level, msg, thrown);
+ }
}
@Override
@@ -186,27 +202,27 @@
@Override
public void entering(final String sourceClass, final String sourceMethod) {
- logger.entry();
+ logger.traceEntry();
}
@Override
public void entering(final String sourceClass, final String sourceMethod, final Object param1) {
- logger.entry(param1);
+ logger.traceEntry(null, param1);
}
@Override
public void entering(final String sourceClass, final String sourceMethod, final Object[] params) {
- logger.entry(params);
+ logger.traceEntry(null, params);
}
@Override
public void exiting(final String sourceClass, final String sourceMethod) {
- logger.exit();
+ logger.traceExit();
}
@Override
public void exiting(final String sourceClass, final String sourceMethod, final Object result) {
- logger.exit(result);
+ logger.traceExit(result);
}
@Override
@@ -216,36 +232,64 @@
@Override
public void severe(final String msg) {
- logger.logIfEnabled(FQCN, org.apache.logging.log4j.Level.ERROR, null, msg);
+ if (getFilter() == null) {
+ logger.logIfEnabled(FQCN, org.apache.logging.log4j.Level.ERROR, null, msg);
+ } else {
+ super.severe(msg);
+ }
}
@Override
public void warning(final String msg) {
- logger.logIfEnabled(FQCN, org.apache.logging.log4j.Level.WARN, null, msg);
+ if (getFilter() == null) {
+ logger.logIfEnabled(FQCN, org.apache.logging.log4j.Level.WARN, null, msg);
+ } else {
+ super.warning(msg);
+ }
}
@Override
public void info(final String msg) {
- logger.logIfEnabled(FQCN, org.apache.logging.log4j.Level.INFO, null, msg);
+ if (getFilter() == null) {
+ logger.logIfEnabled(FQCN, org.apache.logging.log4j.Level.INFO, null, msg);
+ } else {
+ super.info(msg);
+ }
}
@Override
public void config(final String msg) {
- logger.logIfEnabled(FQCN, LevelTranslator.CONFIG, null, msg);
+ if (getFilter() == null) {
+ logger.logIfEnabled(FQCN, LevelTranslator.CONFIG, null, msg);
+ } else {
+ super.config(msg);
+ }
}
@Override
public void fine(final String msg) {
- logger.logIfEnabled(FQCN, org.apache.logging.log4j.Level.DEBUG, null, msg);
+ if (getFilter() == null) {
+ logger.logIfEnabled(FQCN, org.apache.logging.log4j.Level.DEBUG, null, msg);
+ } else {
+ super.fine(msg);
+ }
}
@Override
public void finer(final String msg) {
- logger.logIfEnabled(FQCN, org.apache.logging.log4j.Level.TRACE, null, msg);
+ if (getFilter() == null) {
+ logger.logIfEnabled(FQCN, org.apache.logging.log4j.Level.TRACE, null, msg);
+ } else {
+ super.finer(msg);
+ }
}
@Override
public void finest(final String msg) {
- logger.logIfEnabled(FQCN, LevelTranslator.FINEST, null, msg);
+ if (getFilter() == null) {
+ logger.logIfEnabled(FQCN, LevelTranslator.FINEST, null, msg);
+ } else {
+ super.finest(msg);
+ }
}
}
diff --git a/log4j-jul/src/main/java/org/apache/logging/log4j/jul/Log4jBridgeHandler.java b/log4j-jul/src/main/java/org/apache/logging/log4j/jul/Log4jBridgeHandler.java
new file mode 100644
index 0000000..aea7ad0
--- /dev/null
+++ b/log4j-jul/src/main/java/org/apache/logging/log4j/jul/Log4jBridgeHandler.java
@@ -0,0 +1,319 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.logging.log4j.jul;
+
+//note: NO import of Logger, Level, LogManager to prevent conflicts JUL/log4j
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.logging.LogRecord;
+
+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.spi.ExtendedLogger;
+import org.apache.logging.log4j.status.StatusLogger;
+
+
+/**
+ * Bridge from JUL to log4j2.<br>
+ * This is an alternative to log4j.jul.LogManager (running as complete JUL replacement),
+ * especially useful for webapps running on a container for which the LogManager cannot or
+ * should not be used.<br><br>
+ *
+ * Installation/usage:<ul>
+ * <li> Declaratively inside JUL's <code>logging.properties</code>:<br>
+ * <code>handlers = org.apache.logging.log4j.jul.Log4jBridgeHandler</code><br>
+ * (and typically also: <code>org.apache.logging.log4j.jul.Log4jBridgeHandler.propagateLevels = true</code> )<br>
+ * Note: in a webapp running on Tomcat, you may create a <code>WEB-INF/classes/logging.properties</code>
+ * file to configure JUL for this webapp only: configured handlers and log levels affect your webapp only!
+ * This file is then the <i>complete</i> JUL configuration, so JUL's defaults (e.g. log level INFO) apply
+ * for stuff not explicitly defined therein.
+ * <li> Programmatically by calling <code>install()</code> method,
+ * e.g. inside ServletContextListener static-class-init. or contextInitialized()
+ * </ul>
+ * Configuration (in JUL's <code>logging.properties</code>):<ul>
+ * <li> Log4jBridgeHandler.<code>suffixToAppend</code><br>
+ * String, suffix to append to JUL logger names, to easily recognize bridged log messages.
+ * A dot "." is automatically prepended, so configuration for the basis logger is used<br>
+ * Example: <code>Log4jBridgeHandler.suffixToAppend = _JUL</code><br>
+ * Useful, for example, if you use JSF because it logs exceptions and throws them afterwards;
+ * you can easily recognize the duplicates with this (or concentrate on the non-JUL-logs).
+ * <li> Log4jBridgeHandler.<code>propagateLevels</code> boolean, "true" to automatically propagate log4j log levels to JUL.
+ * <li> Log4jBridgeHandler.<code>sysoutDebug</code> boolean, perform some (developer) debug output to sysout
+ * </ul>
+ *
+ * Log levels are translated with {@link LevelTranslator}, see also
+ * <a href="https://logging.apache.org/log4j/2.x/log4j-jul/index.html#Default_Level_Conversions">log4j doc</a>.<br><br>
+ *
+ * Restrictions:<ul>
+ * <li> Manually given source/location info in JUL (e.g. entering(), exiting(), throwing(), logp(), logrb() )
+ * will NOT be considered, i.e. gets lost in log4j logging.
+ * <li> Log levels of JUL have to be adjusted according to log4j log levels:
+ * Either by using "propagateLevels" (preferred), or manually by specifying them explicitly,
+ * i.e. logging.properties and log4j2.xml have some redundancies.
+ * <li> Only JUL log events that are allowed according to the JUL log level get to this handler and thus to log4j.
+ * This is only relevant and important if you NOT use "propagateLevels".
+ * If you set <code>.level = SEVERE</code> only error logs will be seen by this handler and thus log4j
+ * - even if the corresponding log4j log level is ALL.<br>
+ * On the other side, you should NOT set <code>.level = FINER or FINEST</code> if the log4j level is higher.
+ * In this case a lot of JUL log events would be generated, sent via this bridge to log4j and thrown away by the latter.<br>
+ * Note: JUL's default log level (i.e. none specified in logger.properties) is INFO.
+ * </ul>
+ *
+ * (Credits: idea and concept originate from org.slf4j.bridge.SLF4JBridgeHandler;
+ * level propagation idea originates from logback/LevelChangePropagator;
+ * but no source code has been copied)
+ */
+public class Log4jBridgeHandler extends java.util.logging.Handler implements PropertyChangeListener {
+ private static final org.apache.logging.log4j.Logger SLOGGER = StatusLogger.getLogger();
+
+ // the caller of the logging is java.util.logging.Logger (for location info)
+ private static final String FQCN = java.util.logging.Logger.class.getName();
+ private static final String UNKNOWN_LOGGER_NAME = "unknown.jul.logger";
+ private static final java.util.logging.Formatter julFormatter = new java.util.logging.SimpleFormatter();
+
+ private boolean doDebugOutput = false;
+ private String julSuffixToAppend = null;
+ //not needed: private boolean installAsLevelPropagator = false;
+
+
+ /**
+ * Adds a new Log4jBridgeHandler instance to JUL's root logger.
+ * This is a programmatic alternative to specify
+ * <code>handlers = org.apache.logging.log4j.jul.Log4jBridgeHandler</code>
+ * and its configuration in logging.properties.<br>
+ * @param removeHandlersForRootLogger true to remove all other installed handlers on JUL root level
+ */
+ public static void install(boolean removeHandlersForRootLogger, String suffixToAppend, boolean propagateLevels) {
+ java.util.logging.Logger rootLogger = getJulRootLogger();
+ if (removeHandlersForRootLogger) {
+ for (java.util.logging.Handler hdl : rootLogger.getHandlers()) {
+ rootLogger.removeHandler(hdl);
+ }
+ }
+ rootLogger.addHandler(new Log4jBridgeHandler(false, suffixToAppend, propagateLevels));
+ // note: filter-level of Handler defaults to ALL, so nothing to do here
+ }
+
+ private static java.util.logging.Logger getJulRootLogger() {
+ return java.util.logging.LogManager.getLogManager().getLogger("");
+ }
+
+
+ /** Initialize this handler by reading out JUL configuration. */
+ public Log4jBridgeHandler() {
+ final java.util.logging.LogManager julLogMgr = java.util.logging.LogManager.getLogManager();
+ final String className = this.getClass().getName();
+ init(Boolean.parseBoolean(julLogMgr.getProperty(className + ".sysoutDebug")),
+ julLogMgr.getProperty(className + ".appendSuffix"),
+ Boolean.parseBoolean(julLogMgr.getProperty(className + ".propagateLevels")) );
+
+ }
+
+ /** Initialize this handler with given configuration. */
+ public Log4jBridgeHandler(boolean debugOutput, String suffixToAppend, boolean propagateLevels) {
+ init(debugOutput, suffixToAppend, propagateLevels);
+ }
+
+
+ /** Perform init. of this handler with given configuration (typical use is for constructor). */
+ protected void init(boolean debugOutput, String suffixToAppend, boolean propagateLevels) {
+ this.doDebugOutput = debugOutput;
+ if (debugOutput) {
+ new Exception("DIAGNOSTIC ONLY (sysout): Log4jBridgeHandler instance created (" + this + ")")
+ .printStackTrace(System.out); // is no error thus no syserr
+ }
+
+ if (suffixToAppend != null) {
+ suffixToAppend = suffixToAppend.trim(); // remove spaces
+ if (suffixToAppend.isEmpty()) {
+ suffixToAppend = null;
+ } else if (suffixToAppend.charAt(0) != '.') { // always make it a sub-logger
+ suffixToAppend = '.' + suffixToAppend;
+ }
+ }
+ this.julSuffixToAppend = suffixToAppend;
+
+ //not needed: this.installAsLevelPropagator = propagateLevels;
+ if (propagateLevels) {
+ @SuppressWarnings("resource") // no need to close the AutoCloseable ctx here
+ LoggerContext context = LoggerContext.getContext(false);
+ context.addPropertyChangeListener(this);
+ propagateLogLevels(context.getConfiguration());
+ // note: java.util.logging.LogManager.addPropertyChangeListener() could also
+ // be set here, but a call of JUL.readConfiguration() will be done on purpose
+ }
+
+ SLOGGER.debug("Log4jBridgeHandler init. with: suffix='{}', lvlProp={}, instance={}",
+ suffixToAppend, propagateLevels, this);
+ }
+
+
+ @Override
+ public void close() {
+ // cleanup and remove listener and JUL logger references
+ julLoggerRefs = null;
+ LoggerContext.getContext(false).removePropertyChangeListener(this);
+ if (doDebugOutput) {
+ System.out.println("sysout: Log4jBridgeHandler close(): " + this);
+ }
+ }
+
+
+ @Override
+ public void publish(LogRecord record) {
+ if (record == null) { // silently ignore null records
+ return;
+ }
+
+ org.apache.logging.log4j.Logger log4jLogger = getLog4jLogger(record);
+ String msg = julFormatter.formatMessage(record); // use JUL's implementation to get real msg
+ /* log4j allows nulls:
+ if (msg == null) {
+ // JUL allows nulls, but other log system may not
+ msg = "<null log msg>";
+ } */
+ org.apache.logging.log4j.Level log4jLevel = LevelTranslator.toLevel(record.getLevel());
+ Throwable thrown = record.getThrown();
+ if (log4jLogger instanceof ExtendedLogger) {
+ // relevant for location information
+ try {
+ ((ExtendedLogger) log4jLogger).logIfEnabled(FQCN, log4jLevel, null, msg, thrown);
+ } catch (NoClassDefFoundError e) {
+ // sometimes there are problems with log4j.ExtendedStackTraceElement, so try a workaround
+ log4jLogger.warn("Log4jBridgeHandler: ignored exception when calling 'ExtendedLogger': {}", e.toString());
+ log4jLogger.log(log4jLevel, msg, thrown);
+ }
+ } else {
+ log4jLogger.log(log4jLevel, msg, thrown);
+ }
+ }
+
+
+ @Override
+ public void flush() {
+ // nothing to do
+ }
+
+
+ /**
+ * Return the log4j-Logger instance that will be used for logging.
+ * Handles null name case and appends configured suffix.
+ */
+ private org.apache.logging.log4j.Logger getLog4jLogger(LogRecord record) {
+ String name = record.getLoggerName();
+ if (name == null) {
+ name = UNKNOWN_LOGGER_NAME;
+ } else if (julSuffixToAppend != null) {
+ name += julSuffixToAppend;
+ }
+ return org.apache.logging.log4j.LogManager.getLogger(name);
+ }
+
+
+///// log level propagation code
+
+
+ @Override
+ // impl. for PropertyChangeListener
+ public void propertyChange(PropertyChangeEvent evt) {
+ SLOGGER.debug("Log4jBridgeHandler.propertyChange(): {}", evt);
+ if (LoggerContext.PROPERTY_CONFIG.equals(evt.getPropertyName()) && evt.getNewValue() instanceof Configuration) {
+ propagateLogLevels((Configuration) evt.getNewValue());
+ }
+ }
+
+
+ /** Save "hard" references to configured JUL loggers. (is lazy init.) */
+ private Set<java.util.logging.Logger> julLoggerRefs;
+ /** Perform developer tests? (Should be unused/outcommented for real code) */
+ //private static final boolean DEVTEST = false;
+
+
+ private void propagateLogLevels(Configuration config) {
+ SLOGGER.debug("Log4jBridgeHandler.propagateLogLevels(): {}", config);
+ // clear or init. saved JUL logger references
+ // JUL loggers have to be explicitly referenced because JUL internally uses
+ // weak references so not instantiated loggers may be garbage collected
+ // and their level config gets lost then.
+ if (julLoggerRefs == null) {
+ julLoggerRefs = new HashSet<>();
+ } else {
+ julLoggerRefs.clear();
+ }
+
+ //if (DEVTEST) debugPrintJulLoggers("Start of propagation");
+ // walk through all log4j configured loggers and set JUL level accordingly
+ final Map<String, LoggerConfig> log4jLoggers = config.getLoggers();
+ //java.util.List<String> outTxt = new java.util.ArrayList<>(); // DEVTEST / DEV-DEBUG ONLY
+ for (LoggerConfig lcfg : log4jLoggers.values()) {
+ java.util.logging.Logger julLog = java.util.logging.Logger.getLogger(lcfg.getName()); // this also fits for root = ""
+ java.util.logging.Level julLevel = LevelTranslator.toJavaLevel(lcfg.getLevel()); // lcfg.getLevel() never returns null
+ julLog.setLevel(julLevel);
+ julLoggerRefs.add(julLog); // save an explicit reference to prevent GC
+ //if (DEVTEST) outTxt.add("propagating '" + lcfg.getName() + "' / " + lcfg.getLevel() + " -> " + julLevel);
+ } // for
+ //if (DEVTEST) java.util.Collections.sort(outTxt, String.CASE_INSENSITIVE_ORDER);
+ //if (DEVTEST) for (String s : outTxt) System.out.println("+ " + s);
+ //if (DEVTEST) debugPrintJulLoggers("After propagation");
+
+ // cleanup JUL: reset all log levels not explicitly given by log4j
+ // This has to happen after propagation because JUL creates and inits. the loggers lazily
+ // so a nested logger might be created during the propagation-for-loop above and gets
+ // its JUL-configured level not until then.
+ final java.util.logging.LogManager julMgr = java.util.logging.LogManager.getLogManager();
+ for (Enumeration<String> en = julMgr.getLoggerNames(); en.hasMoreElements(); ) {
+ java.util.logging.Logger julLog = julMgr.getLogger(en.nextElement());
+ if (julLog != null && julLog.getLevel() != null && !"".equals(julLog.getName())
+ && !log4jLoggers.containsKey(julLog.getName()) ) {
+ julLog.setLevel(null);
+ }
+ } // for
+ //if (DEVTEST) debugPrintJulLoggers("After JUL cleanup");
+ }
+
+
+ /* DEV-DEBUG ONLY (comment out for release) *xx/
+ private void debugPrintJulLoggers(String infoStr) {
+ if (!DEVTEST) return;
+ java.util.logging.LogManager julMgr = java.util.logging.LogManager.getLogManager();
+ System.out.println("sysout: " + infoStr + " - for " + julMgr);
+ java.util.List<String> txt = new java.util.ArrayList<>();
+ int n = 1;
+ for (Enumeration<String> en = julMgr.getLoggerNames(); en.hasMoreElements(); ) {
+ String ln = en.nextElement();
+ java.util.logging.Logger lg = julMgr.getLogger(ln);
+ if (lg == null) {
+ txt.add("(!null-Logger '" + ln + "') #" + n);
+ } else if (lg.getLevel() == null) {
+ txt.add("(null-Level Logger '" + ln + "') #" + n);
+ } else {
+ txt.add("Logger '" + ln + "', lvl = " + lg.getLevel() + " #" + n);
+ }
+ n++;
+ } // for
+ java.util.Collections.sort(txt, String.CASE_INSENSITIVE_ORDER);
+ for (String s : txt) {
+ System.out.println(" - " + s);
+ }
+ } /**/
+
+}
diff --git a/log4j-jul/src/main/java/org/apache/logging/log4j/jul/LogManager.java b/log4j-jul/src/main/java/org/apache/logging/log4j/jul/LogManager.java
index 10f8fdb..6225b4c 100644
--- a/log4j-jul/src/main/java/org/apache/logging/log4j/jul/LogManager.java
+++ b/log4j-jul/src/main/java/org/apache/logging/log4j/jul/LogManager.java
@@ -18,6 +18,8 @@
import java.util.Collections;
import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.Set;
import java.util.logging.Logger;
import org.apache.logging.log4j.LoggingException;
@@ -41,6 +43,8 @@
private static final org.apache.logging.log4j.Logger LOGGER = StatusLogger.getLogger();
private final AbstractLoggerAdapter loggerAdapter;
+ // Contains the set of logger names that are actively being requested using getLogger.
+ private final ThreadLocal<Set<String>> recursive = ThreadLocal.withInitial(HashSet::new);
public LogManager() {
super();
@@ -86,7 +90,17 @@
@Override
public Logger getLogger(final String name) {
LOGGER.trace("Call to LogManager.getLogger({})", name);
- return loggerAdapter.getLogger(name);
+ Set<String> activeRequests = recursive.get();
+ if (activeRequests.add(name)) {
+ try {
+ return loggerAdapter.getLogger(name);
+ } finally {
+ activeRequests.remove(name);
+ }
+ } else {
+ LOGGER.warn("Recursive call to getLogger for {} ignored.", name);
+ return new NoOpLogger(name);
+ }
}
@Override
diff --git a/log4j-jul/src/main/java/org/apache/logging/log4j/jul/NoOpLogger.java b/log4j-jul/src/main/java/org/apache/logging/log4j/jul/NoOpLogger.java
new file mode 100644
index 0000000..3c3b158
--- /dev/null
+++ b/log4j-jul/src/main/java/org/apache/logging/log4j/jul/NoOpLogger.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.logging.log4j.jul;
+
+import java.util.ResourceBundle;
+import java.util.function.Supplier;
+import java.util.logging.Level;
+import java.util.logging.LogRecord;
+import java.util.logging.Logger;
+
+/**
+ * Dummy version of a java.util.Logger.
+ */
+public class NoOpLogger extends Logger {
+
+ protected NoOpLogger(String name) {
+ super(name, null);
+ }
+
+ @Override
+ public void log(LogRecord record) {
+ }
+
+ @Override
+ public void log(Level level, String msg) {
+ }
+
+ @Override
+ public void log(Level level, Supplier<String> msgSupplier) {
+ }
+
+ @Override
+ public void log(Level level, String msg, Object param1) {
+ }
+
+ @Override
+ public void log(Level level, String msg, Object[] params) {
+ }
+
+ @Override
+ public void log(Level level, String msg, Throwable thrown) {
+ }
+
+ @Override
+ public void log(Level level, Throwable thrown, Supplier<String> msgSupplier) {
+ }
+
+ @Override
+ public void logp(Level level, String sourceClass, String sourceMethod, String msg) {
+ }
+
+ @Override
+ public void logp(Level level, String sourceClass, String sourceMethod, Supplier<String> msgSupplier) {
+ }
+
+ @Override
+ public void logp(Level level, String sourceClass, String sourceMethod, String msg, Object param1) {
+ }
+
+ @Override
+ public void logp(Level level, String sourceClass, String sourceMethod, String msg, Object[] params) {
+ }
+
+ @Override
+ public void logp(Level level, String sourceClass, String sourceMethod, String msg, Throwable thrown) {
+ }
+
+ @Override
+ public void logp(Level level, String sourceClass, String sourceMethod, Throwable thrown,
+ Supplier<String> msgSupplier) {
+ }
+
+ @Override
+ public void logrb(Level level, String sourceClass, String sourceMethod, String bundleName, String msg) {
+ }
+
+ @Override
+ public void logrb(Level level, String sourceClass, String sourceMethod, String bundleName, String msg,
+ Object param1) {
+ }
+
+ @Override
+ public void logrb(Level level, String sourceClass, String sourceMethod, String bundleName, String msg,
+ Object[] params) {
+ }
+
+ @Override
+ public void logrb(Level level, String sourceClass, String sourceMethod, ResourceBundle bundle, String msg,
+ Object... params) {
+ }
+
+ @Override
+ public void logrb(Level level, String sourceClass, String sourceMethod, String bundleName, String msg,
+ Throwable thrown) {
+ }
+
+ @Override
+ public void logrb(Level level, String sourceClass, String sourceMethod, ResourceBundle bundle, String msg,
+ Throwable thrown) {
+ }
+
+ @Override
+ public void entering(String sourceClass, String sourceMethod) {
+ }
+
+ @Override
+ public void entering(String sourceClass, String sourceMethod, Object param1) {
+ }
+
+ @Override
+ public void entering(String sourceClass, String sourceMethod, Object[] params) {
+ }
+
+ @Override
+ public void exiting(String sourceClass, String sourceMethod) {
+ }
+
+ @Override
+ public void exiting(String sourceClass, String sourceMethod, Object result) {
+ }
+
+ @Override
+ public void throwing(String sourceClass, String sourceMethod, Throwable thrown) {
+ }
+
+ @Override
+ public void severe(String msg) {
+ }
+
+ @Override
+ public void warning(String msg) {
+ }
+
+ @Override
+ public void info(String msg) {
+ }
+
+ @Override
+ public void config(String msg) {
+ }
+
+ @Override
+ public void fine(String msg) {
+ }
+
+ @Override
+ public void finer(String msg) {
+ }
+
+ @Override
+ public void finest(String msg) {
+ }
+
+ @Override
+ public void severe(Supplier<String> msgSupplier) {
+ }
+
+ @Override
+ public void warning(Supplier<String> msgSupplier) {
+ }
+
+ @Override
+ public void info(Supplier<String> msgSupplier) {
+ }
+
+ @Override
+ public void config(Supplier<String> msgSupplier) {
+ }
+
+ @Override
+ public void fine(Supplier<String> msgSupplier) {
+ }
+
+ @Override
+ public void finer(Supplier<String> msgSupplier) {
+ }
+
+ @Override
+ public void finest(Supplier<String> msgSupplier) {
+ }
+
+ @Override
+ public void setLevel(Level newLevel) throws SecurityException {
+ }
+
+ @Override
+ public Level getLevel() {
+ return Level.OFF;
+ }
+
+ @Override
+ public boolean isLoggable(Level level) {
+ return false;
+ }
+}
diff --git a/log4j-jul/src/main/java/org/apache/logging/log4j/jul/WrappedLogger.java b/log4j-jul/src/main/java/org/apache/logging/log4j/jul/WrappedLogger.java
index 8e54185..e7e3969 100644
--- a/log4j-jul/src/main/java/org/apache/logging/log4j/jul/WrappedLogger.java
+++ b/log4j-jul/src/main/java/org/apache/logging/log4j/jul/WrappedLogger.java
@@ -18,6 +18,7 @@
package org.apache.logging.log4j.jul;
import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.message.EntryMessage;
import org.apache.logging.log4j.spi.ExtendedLogger;
import org.apache.logging.log4j.spi.ExtendedLoggerWrapper;
@@ -51,23 +52,13 @@
}
@Override
- public void entry() {
- entry(FQCN);
+ public EntryMessage traceEntry() {
+ return enter(FQCN, null, (Object[]) null);
}
@Override
- public void entry(final Object... params) {
- entry(FQCN, params);
- }
-
- @Override
- public void exit() {
- exit(FQCN, null);
- }
-
- @Override
- public <R> R exit(final R result) {
- return exit(FQCN, result);
+ public EntryMessage traceEntry(final String message, final Object... params) {
+ return enter(FQCN, message, params);
}
@Override
diff --git a/log4j-jul/src/site/markdown/index.md b/log4j-jul/src/site/markdown/index.md
index 80dd6c2..683d139 100644
--- a/log4j-jul/src/site/markdown/index.md
+++ b/log4j-jul/src/site/markdown/index.md
@@ -16,6 +16,11 @@
limitations under the License.
-->
+There are two possibilities:
+- Logging Adapter as complete replacement (preferred, but requires JVM start option)
+- Bridge Handler, transferring JDK output to log4j, e.g. useful for webapps
+
+
# Log4j JDK Logging Adapter
The JDK Logging Adapter is a custom implementation of
@@ -74,3 +79,41 @@
[`FINER`](http://docs.oracle.com/javase/6/docs/api/java/util/logging/Level.html#FINER) | `TRACE`
[`FINEST`](http://docs.oracle.com/javase/6/docs/api/java/util/logging/Level.html#FINEST) | [`FINEST`](apidocs/org/apache/logging/log4j/jul/LevelTranslator.html#FINEST)
[`ALL`](http://docs.oracle.com/javase/6/docs/api/java/util/logging/Level.html#ALL) | `ALL`
+
+
+# Log4j JDK Logging Bridge Handler
+
+The LogManager is not always usable because you have to set a JVM wide effective system
+property - e.g. in web servers this is not possible if you are not the administrator.
+
+The [`Log4jBridgeHandler`](apidocs/org/apache/logging/log4j/jul/Log4jBridgeHandler.html) is an
+alternative that can be declaratively used via `logging.properties`.
+
+It is less performant than the LogManager but still okay to use: the LogManager replaces the JDK
+implementation, so your logging code (using JDK syntax) effectively directly uses log4j.
+When using the BridgeHandler the original JDK implementation along with its configuration
+(e.g. log levels) is still fully working but the log events are "written" via this handler to log4j
+as if you would have called log4j.Logger.debug() etc.; it is like a FileHandler but instead of
+writing to a file, it "writes" to log4j Loggers - thus there is some overhead compared to using
+LogManager.
+
+## Usage
+
+The JUL configuration file `logging.properties` needs the line<br/>
+`handlers = org.apache.logging.log4j.jul.Log4jBridgeHandler`<br/>
+and JUL logs go to log4j2. Additionally, you typically want to use to following:<br/>
+`org.apache.logging.log4j.jul.Log4jBridgeHandler.propagateLevels = true`
+
+In a webapp on Tomcat (and maybe other servers, too), you may simply create a
+`WEB-INF/classes/logging.properties` file with above content.
+The bridge and the log levels defined in this file are only valid for your webapp and
+do *not* have any impact on the other webapps on the same Tomcat instance.
+
+Alternatively you may call `Log4jBridgeHandler.install()` inside your webapp's initialization code,
+e.g. inside `ServletContextListener` or a `ServletFilter` static-class-init. or `contextInitialized()`.
+
+**Important:** Log levels of JDK should match the ones of log4j. You may do this manually or use the
+automatic level propagation via `Log4jBridgeHandler.propagateLevels = true`.
+
+Please, read the [JavaDoc](apidocs/org/apache/logging/log4j/jul/Log4jBridgeHandler.html) for detailed
+configuration and limitation information!
diff --git a/log4j-jul/src/test/java/org/apache/logging/log4j/jul/AbstractLoggerTest.java b/log4j-jul/src/test/java/org/apache/logging/log4j/jul/AbstractLoggerTest.java
index f81d3fa..ef69728 100644
--- a/log4j-jul/src/test/java/org/apache/logging/log4j/jul/AbstractLoggerTest.java
+++ b/log4j-jul/src/test/java/org/apache/logging/log4j/jul/AbstractLoggerTest.java
@@ -85,6 +85,34 @@
}
@Test
+ public void testLogFilter() throws Exception {
+ logger.setFilter(record -> false);
+ logger.severe("Informative message here.");
+ logger.warning("Informative message here.");
+ logger.info("Informative message here.");
+ logger.config("Informative message here.");
+ logger.fine("Informative message here.");
+ logger.finer("Informative message here.");
+ logger.finest("Informative message here.");
+ final List<LogEvent> events = eventAppender.getEvents();
+ assertThat(events, hasSize(0));
+ }
+
+ @Test
+ public void testAlteringLogFilter() throws Exception {
+ logger.setFilter(record -> { record.setMessage("This is not the message you are looking for."); return true; });
+ logger.info("Informative message here.");
+ final List<LogEvent> events = eventAppender.getEvents();
+ assertThat(events, hasSize(1));
+ final LogEvent event = events.get(0);
+ assertThat(event, instanceOf(Log4jLogEvent.class));
+ assertEquals(Level.INFO, event.getLevel());
+ assertEquals(LOGGER_NAME, event.getLoggerName());
+ assertEquals("This is not the message you are looking for.", event.getMessage().getFormattedMessage());
+ assertEquals(ApiLogger.class.getName(), event.getLoggerFqcn());
+ }
+
+ @Test
public void testLogParamMarkers() {
final Logger flowLogger = Logger.getLogger("TestFlow");
flowLogger.logp(java.util.logging.Level.FINER, "sourceClass", "sourceMethod", "ENTER {0}", "params");
diff --git a/log4j-jul/src/test/java/org/apache/logging/log4j/jul/ApiLoggerTest.java b/log4j-jul/src/test/java/org/apache/logging/log4j/jul/ApiLoggerTest.java
index 6e08b3a..0f2c830 100644
--- a/log4j-jul/src/test/java/org/apache/logging/log4j/jul/ApiLoggerTest.java
+++ b/log4j-jul/src/test/java/org/apache/logging/log4j/jul/ApiLoggerTest.java
@@ -48,6 +48,7 @@
@Before
public void setUp() throws Exception {
logger = Logger.getLogger(LOGGER_NAME);
+ logger.setFilter(null);
assertThat(logger.getLevel(), equalTo(java.util.logging.Level.FINE));
eventAppender = ListAppender.getListAppender("TestAppender");
flowAppender = ListAppender.getListAppender("FlowAppender");
diff --git a/log4j-jul/src/test/java/org/apache/logging/log4j/jul/AsyncLoggerThreadsTest.java b/log4j-jul/src/test/java/org/apache/logging/log4j/jul/AsyncLoggerThreadsTest.java
new file mode 100644
index 0000000..3ca72e6
--- /dev/null
+++ b/log4j-jul/src/test/java/org/apache/logging/log4j/jul/AsyncLoggerThreadsTest.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.jul;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.categories.AsyncLoggers;
+import org.apache.logging.log4j.core.CoreLoggerContexts;
+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.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+@Category(AsyncLoggers.class)
+public class AsyncLoggerThreadsTest {
+
+ @BeforeClass
+ public static void beforeClass() {
+ System.setProperty(Constants.LOG4J_CONTEXT_SELECTOR,
+ AsyncLoggerContextSelector.class.getName());
+ System.setProperty("java.util.logging.manager", org.apache.logging.log4j.jul.LogManager.class.getName());
+ }
+
+ @AfterClass
+ public static void afterClass() {
+ System.clearProperty(Constants.LOG4J_CONTEXT_SELECTOR);
+ System.clearProperty("java.util.logging.manager");
+ }
+
+ @Test
+ public void testAsyncLoggerThreads() {
+ LogManager.getLogger("com.foo.Bar").info("log");
+ List<Thread> asyncLoggerThreads = Thread.getAllStackTraces().keySet().stream()
+ .filter(thread -> thread.getName().matches("Log4j2-TF.*AsyncLogger.*"))
+ .collect(Collectors.toList());
+ assertEquals(asyncLoggerThreads.toString(), 1, asyncLoggerThreads.size());
+ }
+}
diff --git a/log4j-jul/src/test/java/org/apache/logging/log4j/jul/CoreLoggerTest.java b/log4j-jul/src/test/java/org/apache/logging/log4j/jul/CoreLoggerTest.java
index fbc17b2..ae91022 100644
--- a/log4j-jul/src/test/java/org/apache/logging/log4j/jul/CoreLoggerTest.java
+++ b/log4j-jul/src/test/java/org/apache/logging/log4j/jul/CoreLoggerTest.java
@@ -48,6 +48,7 @@
@Before
public void setUp() throws Exception {
logger = Logger.getLogger(LOGGER_NAME);
+ logger.setFilter(null);
assertThat(logger.getLevel(), equalTo(Level.FINE));
eventAppender = ListAppender.getListAppender("TestAppender");
flowAppender = ListAppender.getListAppender("FlowAppender");
@@ -118,4 +119,4 @@
assertThat(childLogger.isLoggable(Level.FINE), is(false));
}
-}
\ No newline at end of file
+}
diff --git a/log4j-jul/src/test/java/org/apache/logging/log4j/jul/JavaLevelTranslatorTest.java b/log4j-jul/src/test/java/org/apache/logging/log4j/jul/JavaLevelTranslatorTest.java
index b912257..5ccc9c2 100644
--- a/log4j-jul/src/test/java/org/apache/logging/log4j/jul/JavaLevelTranslatorTest.java
+++ b/log4j-jul/src/test/java/org/apache/logging/log4j/jul/JavaLevelTranslatorTest.java
@@ -70,4 +70,4 @@
final java.util.logging.Level actualLevel = LevelTranslator.toJavaLevel(log4jLevel);
assertEquals(javaLevel, actualLevel);
}
-}
\ No newline at end of file
+}
diff --git a/log4j-jul/src/test/java/org/apache/logging/log4j/jul/Log4jBridgeHandlerTest.java b/log4j-jul/src/test/java/org/apache/logging/log4j/jul/Log4jBridgeHandlerTest.java
new file mode 100644
index 0000000..93093f8
--- /dev/null
+++ b/log4j-jul/src/test/java/org/apache/logging/log4j/jul/Log4jBridgeHandlerTest.java
@@ -0,0 +1,459 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.logging.log4j.jul;
+
+//note: NO import of Logger, Level, LogManager to prevent conflicts JUL/log4j
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.beans.PropertyChangeEvent;
+import java.beans.PropertyChangeListener;
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.util.Arrays;
+import java.util.Enumeration;
+import java.util.Map.Entry;
+
+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.Configurator;
+import org.apache.logging.log4j.core.config.LoggerConfig;
+import org.apache.logging.log4j.core.config.Reconfigurable;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.FixMethodOrder;
+import org.junit.Test;
+
+
+/**
+ * Test the Log4jBridgeHandler.
+ * Requires some configurations in the log-config-files, for format/layout
+ * see also jul() and log4j():
+ * - JUL-config ("logging-test.properties", must be set on JVM-start via "-D..."):
+ * + handlers = org.apache.logging.log4j.jul.Log4jBridgeHandler, java.util.logging.ConsoleHandler
+ * + org.apache.logging.log4j.jul.Log4jBridgeHandler.appendSuffix = _JUL
+ * + java.util.logging.ConsoleHandler.level = ALL
+ * + java.util.logging.SimpleFormatter.format = JUL: %1$tT.%1$tL %4$s [%3$s: %2$s] - %5$s%6$s%n
+ * + .level = FINER
+ * - log4j2-config ("log4j2-test.xml"):
+ * + <Root level="TRACE">
+ * + <Appenders> <Console> with target="SYSTEM_ERR", follow="true",
+ * <PatternLayout> "log4j2: %d{HH:mm:ss.SSS} %5level - [%thread][%logger: %class/%method/%line] - %message%n"
+ *
+ * This test needs to capture syserr because it uses java.util.logging.ConsoleHandler.
+ * Also, it performs some outputs to console (sysout and syserr); see also field OUTPUT_CAPTURED.
+ *
+ * The code also contains evaluation/test code for development time. It is not used for the unit tests
+ * but kept here for reference and info. See field DEVTEST.
+ */
+@FixMethodOrder(org.junit.runners.MethodSorters.NAME_ASCENDING) // is nicer for manually checking console output
+public class Log4jBridgeHandlerTest {
+ /** Perform developer tests? */
+ private static final boolean DEVTEST = false;
+
+ /** Do output the captured logger-output to stdout? */
+ private static final boolean OUTPUT_CAPTURED = !DEVTEST && Boolean.parseBoolean(
+ System.getProperty("log4j.Log4jBridgeHandlerTest.outputCaptured"));
+
+ /** This classes' simple name = relevant part of logger name. */
+ private static final String CSNAME = Log4jBridgeHandlerTest.class.getSimpleName();
+ // loggers used in many tests
+ private static final java.util.logging.Logger julLog = java.util.logging.Logger.getLogger(Log4jBridgeHandlerTest.class.getName());
+ private static final org.apache.logging.log4j.Logger log4jLog = org.apache.logging.log4j.LogManager.getLogger();
+
+ // capture sysout/syserr
+ //@Rule public final SystemErrRule systemOutRule = new SystemErrRule().enableLog();
+ private static final ByteArrayOutputStream sysoutBytes = new ByteArrayOutputStream(1024);
+ private static PrintStream prevSysErrStream;
+
+
+ @BeforeClass
+ public static void beforeClass() {
+ // debug output to easily recognize misconfig.:
+ //System.out.println("sys-props:\n" + System.getProperties());
+ System.out.println("sysout: logging-cfg-file: " + System.getProperty("java.util.logging.config.file"));
+ if (DEVTEST) devTestBeforeClass(); // call before stderr capturing
+
+ // JUL does not like setting stderr inbetween, so set it once and reset collecting stream
+ // for each method; (thus com.github.stefanbirkner:system-rules:SystemErrRule cannot be used)
+ System.err.println("vvv--- BEGIN capturing output to stderr ---vvv"
+ + " (do output of captured text to orig. stderr: " + OUTPUT_CAPTURED + ")");
+ prevSysErrStream = System.err;
+ System.setErr(new PrintStream(sysoutBytes, true));
+ }
+
+ @AfterClass
+ public static void afterClass() {
+ // reset sysout/err to original value
+ System.setErr(prevSysErrStream);
+ System.err.println("^^^--- END capturing output of stderr ---^^^");
+ }
+
+
+ @Before
+ public void beforeTest() {
+ // reset sysout collector
+ sysoutBytes.reset();
+ }
+
+
+
+ /** Assert that captured sysout matches given regexp (any text may follow afterwards). */
+ private void assertSysoutMatches(String regex) {
+ //String logOutput = systemOutRule.getLogWithNormalizedLineSeparator();
+ String logOutput = sysoutBytes.toString();
+ if (OUTPUT_CAPTURED) prevSysErrStream.print(logOutput);
+ logOutput = logOutput.replace("\r\n", "\n");
+ regex = regex + "(.|\\n)*"; // allow any text with NL afterwards
+ assertTrue("Unmatching output:\n" + logOutput + "\n-- vs: --\n" + regex + "\n----", logOutput.matches(regex));
+ }
+
+ /** Get regex for a JUL console output. Must match JUL-Console-Formatter! */
+ private String jul(java.util.logging.Level lvl, String locationPartRE,
+ String msgPartRE, String exceptionClassAndMsgRE) {
+ return "JUL:.*" + lvl.getLocalizedName() + ".*" + CSNAME
+ + ".*" + locationPartRE + ".*" + msgPartRE + ".*\n" // use real \n at end here for better error output
+ + (exceptionClassAndMsgRE == null ? ""
+ : ".*" + exceptionClassAndMsgRE + ".*\\n(\tat .*\\n)*\\n?");
+ }
+
+ /** Get regex for a log4j console output. Must match log4j2-Console-Layout! */
+ private String log4j(org.apache.logging.log4j.Level lvl, boolean julBridged,
+ String methodPartRE, String msgPartRE, String exceptionClassAndMsgRE) {
+ return "log4j2:.*" + lvl.name() + ".*" + CSNAME + (julBridged ? "\\._JUL" : "")
+ + ".*" + CSNAME + "/\\w*" + methodPartRE + "\\w*/.*"
+ + msgPartRE + ".*\n" // use real \n at end here for better error output
+ + (exceptionClassAndMsgRE == null ? ""
+ : ".*" + exceptionClassAndMsgRE + ".*\\n(\tat .*\\n)*\\n?");
+ }
+
+
+
+ @Test
+ public void test1SimpleLoggings1Jul() {
+ julLog.info("Test-'Info'-Log with JUL");
+ julLog.fine("Test-'Fine'-Log with JUL");
+ julLog.finest("Test-'Finest'-Log with JUL"); // should not be logged because JUL-level is FINER
+ julLog.warning("Test-'Warn'-Log with JUL"); // thus add another log afterwards to allow checking
+ String methodRE = "SimpleLoggings1Jul";
+ assertSysoutMatches(
+ log4j(org.apache.logging.log4j.Level.INFO, true, methodRE, "'Info'-Log with JUL", null)
+ + jul(java.util.logging.Level.INFO, methodRE, "'Info'-Log with JUL", null)
+ + log4j(org.apache.logging.log4j.Level.DEBUG, true, methodRE, "'Fine'-Log with JUL", null)
+ + jul(java.util.logging.Level.FINE, methodRE, "'Fine'-Log with JUL", null)
+ // no finest/trace
+ + log4j(org.apache.logging.log4j.Level.WARN, true, methodRE, "'Warn'-Log with JUL", null)
+ + jul(java.util.logging.Level.WARNING, methodRE, "'Warn'-Log with JUL", null)
+ );
+ }
+
+ @Test
+ public void test1SimpleLoggings2Log4jDirect() {
+ log4jLog.info("Test-'Info'-Log with log4j2");
+ log4jLog.debug("Test-'Debug'-Log with log4j2");
+ log4jLog.trace("Test-'Trace'-Log with log4j2");
+ String methodRE = "SimpleLoggings2Log4jDirect";
+ assertSysoutMatches(
+ log4j(org.apache.logging.log4j.Level.INFO, false, methodRE, "'Info'-Log with log4j2", null)
+ + log4j(org.apache.logging.log4j.Level.DEBUG, false, methodRE, "'Debug'-Log with log4j2", null)
+ + log4j(org.apache.logging.log4j.Level.TRACE, false, methodRE, "'Trace'-Log with log4j2", null)
+ );
+ }
+
+
+ @Test
+ public void test2SubMethod() {
+ subMethodWithLogs(); // location info is sub method now
+ String methodRE = "subMethodWithLogs";
+ assertSysoutMatches(
+ log4j(org.apache.logging.log4j.Level.DEBUG, true, methodRE, "'Fine'-Log with JUL in subMethod", null)
+ + jul(java.util.logging.Level.FINE, methodRE, "'Fine'-Log with JUL in subMethod", null)
+ + log4j(org.apache.logging.log4j.Level.INFO, false, methodRE, "'Info'-Log with log4j2 in subMethod", null)
+ );
+ }
+ private void subMethodWithLogs() {
+ julLog.fine("Test-'Fine'-Log with JUL in subMethod");
+ log4jLog.info("Test-'Info'-Log with log4j2 in subMethod");
+ }
+
+
+ @Test
+ public void test3JulFlow1() {
+ // note: manually given source information get lost in log4j!
+ julLog.entering("enteringExampleClassParam", "enteringExampleMethodParam");
+ String methodRE = "JulFlow";
+ assertSysoutMatches(
+ log4j(org.apache.logging.log4j.Level.TRACE, true, methodRE, "ENTRY", null)
+ + jul(java.util.logging.Level.FINER, "enteringExampleClassParam enteringExampleMethodParam", "ENTRY", null)
+ );
+ }
+
+ @Test
+ public void test3JulFlow2() {
+ // note: manually given source information get lost in log4j!
+ julLog.entering("enteringExampleClassParam", "enteringExampleMethodParam_withParams",
+ new Object[] {"with some", "parameters", 42} );
+ String methodRE = "JulFlow";
+ assertSysoutMatches(
+ log4j(org.apache.logging.log4j.Level.TRACE, true, methodRE,
+ "ENTRY.*with some.*param.*42", null)
+ + jul(java.util.logging.Level.FINER, "enteringExampleClassParam enteringExampleMethodParam_withParams",
+ "ENTRY.*with some.*param.*42", null)
+ );
+ }
+
+ @Test
+ public void test3JulFlow3() {
+ // note: manually given source information get lost in log4j!
+ julLog.exiting("exitingExampleClassParam", "exitingExampleMethodParam",
+ Arrays.asList("array of Strings", "that are the exit", "result"));
+ String methodRE = "JulFlow";
+ assertSysoutMatches(
+ log4j(org.apache.logging.log4j.Level.TRACE, true, methodRE,
+ "RETURN.*array of Str.*that are.*result", null)
+ + jul(java.util.logging.Level.FINER, "exitingExampleClassParam exitingExampleMethodParam",
+ "RETURN.*array of Str.*that are.*result", null)
+ );
+ }
+
+ @Test
+ public void test3JulFlow4() {
+ // note: manually given source information get lost in log4j!
+ julLog.throwing("throwingExampleClassParam", "throwingExampleMethodParam",
+ new IllegalStateException("ONLY TEST for JUL-throwing()"));
+ String methodRE = "JulFlow";
+ assertSysoutMatches(
+ log4j(org.apache.logging.log4j.Level.TRACE, true, methodRE,
+ "THROW", "IllegalStateException.*ONLY TEST for JUL-throwing")
+ + jul(java.util.logging.Level.FINER, "throwingExampleClassParam throwingExampleMethodParam",
+ "THROW", "IllegalStateException.*ONLY TEST for JUL-throwing")
+ );
+ }
+
+
+
+ @Test
+ public void test4JulSpecials1() {
+ julLog.log(java.util.logging.Level.WARNING, "JUL-Test via log() as warning with exception",
+ new java.util.zip.DataFormatException("ONLY TEST for JUL.log()"));
+ String methodRE = "JulSpecials";
+ assertSysoutMatches(
+ log4j(org.apache.logging.log4j.Level.WARN, true, methodRE,
+ "JUL-Test via log\\(\\) as warning", "DataFormatException.*ONLY TEST for JUL")
+ + jul(java.util.logging.Level.WARNING, methodRE, "JUL-Test via log\\(\\) as warning",
+ "DataFormatException.*ONLY TEST for JUL")
+ );
+ }
+
+ @Test
+ public void test4JulSpecials2() {
+ // test with MessageFormat
+ julLog.log(java.util.logging.Level.INFO, "JUL-Test via log() with parameters (0={0}, 1={1}, 2={2,number,##000.0})",
+ new Object[] {"a", "b", 42} );
+ String methodRE = "JulSpecials";
+ assertSysoutMatches(
+ log4j(org.apache.logging.log4j.Level.INFO, true, methodRE,
+ "JUL-Test via log\\(\\) with parameters \\(0=a, 1=b, 2=042.0\\)", null)
+ + jul(java.util.logging.Level.INFO, methodRE,
+ "JUL-Test via log\\(\\) with parameters \\(0=a, 1=b, 2=042.0\\)", null)
+ );
+ }
+
+ // no test for logrb(ResourceBundle)-case as this is very specific and seldom used (in
+ // my opinion); and it does not add any real thing to test here
+
+
+ private void assertLogLevel(String loggerName, java.util.logging.Level julLevel) {
+ java.util.logging.Logger lg = java.util.logging.LogManager.getLogManager().getLogger(loggerName);
+ assertEquals("Logger '" + loggerName + "'", julLevel, (lg == null ? null : lg.getLevel()));
+ }
+
+ @Test
+ public void test5LevelPropFromConfigFile() {
+ // JUL levels are set from config files and the initial propagation
+ assertLogLevel("", java.util.logging.Level.FINE);
+ assertLogLevel("log4j.Log4jBridgeHandlerTest.propagate1", java.util.logging.Level.FINE);
+ assertLogLevel("log4j.Log4jBridgeHandlerTest.propagate1.nested1", java.util.logging.Level.FINER);
+ assertLogLevel("log4j.Log4jBridgeHandlerTest.propagate1.nested2.deeplyNested", java.util.logging.Level.WARNING);
+ assertLogLevel("log4j.Log4jBridgeHandlerTest.propagate2", java.util.logging.Level.ALL);
+ assertLogLevel("log4j.Log4jBridgeHandlerTest.propagate2.nested.deeplyNested", java.util.logging.Level.INFO);
+ // these are set in logging.properties but not in log4j2.xml:
+ assertLogLevel("log4j.Log4jBridgeHandlerTest.propagate2.nested", null);
+ assertLogLevel("javax.mail", null);
+ // these should not exist:
+ assertLogLevel("log4j.Log4jBridgeHandlerTest", null);
+ assertLogLevel("log4j.Log4jBridgeHandlerTest.propagate1.nested", null);
+ assertLogLevel("log4j.Log4jBridgeHandlerTest.propagate1.nested1.deeplyNested", null);
+ }
+
+
+ @Test
+ public void test5LevelPropSetLevel() {
+ String name = "log4j.test.new_logger_level_set";
+ Configurator.setLevel(name, org.apache.logging.log4j.Level.DEBUG);
+ assertLogLevel(name, java.util.logging.Level.FINE);
+ test5LevelPropFromConfigFile(); // the rest should be untouched!
+
+ name = "log4j.Log4jBridgeHandlerTest.propagate1.nested1";
+ Configurator.setLevel(name, org.apache.logging.log4j.Level.WARN);
+ assertLogLevel(name, java.util.logging.Level.WARNING);
+ // the others around should be untouched
+ assertLogLevel("log4j.Log4jBridgeHandlerTest.propagate1", java.util.logging.Level.FINE);
+ assertLogLevel("log4j.Log4jBridgeHandlerTest.propagate1.nested2.deeplyNested", java.util.logging.Level.WARNING);
+
+ // note: no need to check for the other set[Root]Level() methods, because they all call
+ // loggerContext.updateLoggers() which calls firePropertyChangeEvent()
+ }
+
+
+ @Test
+ public void test5LevelPropGC() {
+ // this test will fail if you comment out "julLoggerRefs.add(julLog);" in propagateLogLevels()
+ test5LevelPropFromConfigFile(); // at start, all should be fine
+ java.util.logging.Logger julLogRef = java.util.logging.Logger
+ .getLogger("log4j.Log4jBridgeHandlerTest.propagate1.nested1");
+ System.gc(); // a single call is sufficient
+ System.out.println("sysout: test5LevelPropGC() still has reference to JUL-logger: "
+ + julLogRef.getName() + " / " + julLogRef);
+ try {
+ test5LevelPropFromConfigFile(); // even after GC the not referenced loggers should still be there
+ } catch (Throwable t) {
+ debugPrintJulLoggers("After GC");
+ // => JUL root logger, above explicitly referenced logger and its parent ("...propagate1")
+ // and the global referenced julLog ("...jul.Log4jBridgeHandlerTest") are still there, the
+ // others are null-references now
+ throw t;
+ }
+ }
+
+
+ /** Print all available JUL loggers to stdout. */
+ private static void debugPrintJulLoggers(String infoStr) {
+ java.util.logging.LogManager julMgr = java.util.logging.LogManager.getLogManager();
+ System.out.println("sysout: " + infoStr + " - for " + julMgr);
+ java.util.List<String> txt = new java.util.ArrayList<>();
+ int n = 1;
+ for (Enumeration<String> en = julMgr.getLoggerNames(); en.hasMoreElements(); ) {
+ String ln = en.nextElement();
+ java.util.logging.Logger lg = julMgr.getLogger(ln);
+ if (lg == null) {
+ txt.add("(!null-Logger '" + ln + "') #" + n);
+ } else if (lg.getLevel() == null) {
+ txt.add("(null-Level Logger '" + ln + "') #" + n);
+ } else {
+ txt.add("Logger '" + ln + "', lvl = " + lg.getLevel() + " #" + n);
+ }
+ n++;
+ } // for
+ java.util.Collections.sort(txt, String.CASE_INSENSITIVE_ORDER);
+ for (String s : txt) {
+ System.out.println(" - " + s);
+ }
+ }
+
+
+
+
+////////////////
+//////////////// INTERNAL DEVELOPER TESTS follow
+//////////////// (these are NOT neccessary for unit test but source is kept here for reference and info)
+
+
+ static {
+ if (DEVTEST) {
+ System.out.println("sysout: static init. BEGIN");
+
+ // get log4j context impl. (requires log4j-core!)
+ // note: "LogManager.getContext();" does not work, it returns another instance!?!
+ LoggerContext context = LoggerContext.getContext(false); // this matches Configurator.setLevel() impl.
+ Configuration cfg = context.getConfiguration();
+ // print real loggers (=> is empty when using LogManager.getContext()!?! only contains already instantiated loggers)
+ System.out.println("LogCtx " + context + " '" + context.getName() + "', loc = "
+ + context.getConfigLocation() + ", cfg = " + cfg + " = " + System.identityHashCode(cfg));
+ for (org.apache.logging.log4j.Logger lg : context.getLoggers()) {
+ System.out.println("- Logger '" + lg.getName() + "', lvl = " + lg.getLevel());
+ }
+ // print logger configurations (=> all loggers with level are present here)
+ System.out.println("Loggers in Cfg:");
+ for (Entry<String, LoggerConfig> entry : cfg.getLoggers().entrySet()) {
+ LoggerConfig lcfg = entry.getValue();
+ System.out.println("- '" + entry.getKey() + "' = '" + lcfg.getName() + "' / "
+ + lcfg.getLevel() + "; " + lcfg);
+ }
+
+ // print JUL loggers (=> is completely init. here, even if first JUL log and BridgeHandler-creation happens later)
+ debugPrintJulLoggers("in static-class-init");
+ /* java.util.logging.LogManager julMgr = java.util.logging.LogManager.getLogManager();
+ System.out.println("\nJUL-Loggers for " + julMgr);
+ for (Enumeration<String> en = julMgr.getLoggerNames(); en.hasMoreElements(); ) {
+ String ln = en.nextElement();
+ java.util.logging.Logger lg = julMgr.getLogger(ln);
+ if (lg.getLevel() == null) {
+ System.out.println("- (null-Level Logger '" + ln + "')");
+ } else {
+ System.out.println("- Logger '" + ln + "', lvl = " + lg.getLevel());
+ }
+ } */
+
+ // changing of log4j config. is to be done via log4j.core.config.Configurator,
+ // e.g. setLevel(loggerName, newLevel)
+ // Note: the (internal) log4j.core.Logger has a setLevel() but this should not be used.
+ CfgListener listener = new CfgListener();
+ cfg.addListener(listener); // => onChange() is never called: not on start, not on setLevel
+ context.addPropertyChangeListener(listener);
+
+ System.out.println("sysout: static init. END");
+ } // if
+ }
+
+
+ private static void devTestBeforeClass() {
+ log4jLog.info("Dummy-Start-Log in beforeClass()"); // force init. of log4j (needed?? does not harm)
+ @SuppressWarnings("resource")
+ LoggerContext context = LoggerContext.getContext(false); // this matches Configurator.setLevel() impl. (instead of "LogManager.getContext();")
+ System.out.println("beforeClass(): LogCtx " + context + " '" + context.getName() + "', loc = " + context.getConfigLocation()
+ + ", cfg = " + context.getConfiguration());
+ for (org.apache.logging.log4j.Logger lg : context.getLoggers()) {
+ System.out.println("- Logger '" + lg.getName() + "', lvl = " + lg.getLevel());
+ }
+
+ // force level change
+ System.out.println("sysout: now calling log4j-setLevel()");
+ Configurator.setLevel("log4jTest.Dummy_set_in_devTestBeforeClass", org.apache.logging.log4j.Level.DEBUG);
+ }
+
+
+ private static class CfgListener implements ConfigurationListener, PropertyChangeListener {
+ public CfgListener() {
+ System.out.println("sysout: CfgListener created: " + this);
+ }
+
+ @Override
+ public void onChange(Reconfigurable reconfigurable) { // from ConfigurationListener
+ System.out.println("sysout: CfgListener.CfgLi-onChange(): " + reconfigurable
+ + " = " + System.identityHashCode(reconfigurable));
+ }
+
+ @Override
+ public void propertyChange(PropertyChangeEvent evt) { // from PropertyChangeListener
+ System.out.println("sysout: CfgListener.PropChLi-propertyChange(): " + evt);
+ }
+ }
+
+}
diff --git a/log4j-jul/src/test/java/org/apache/logging/log4j/jul/Log4jLevelTranslatorTest.java b/log4j-jul/src/test/java/org/apache/logging/log4j/jul/Log4jLevelTranslatorTest.java
index e38159a..dd65105 100644
--- a/log4j-jul/src/test/java/org/apache/logging/log4j/jul/Log4jLevelTranslatorTest.java
+++ b/log4j-jul/src/test/java/org/apache/logging/log4j/jul/Log4jLevelTranslatorTest.java
@@ -64,4 +64,4 @@
assertEquals(javaLevel, actualLevel);
}
-}
\ No newline at end of file
+}
diff --git a/log4j-jul/src/test/resources/log4j2-julBridge-test.xml b/log4j-jul/src/test/resources/log4j2-julBridge-test.xml
new file mode 100644
index 0000000..edc0cab
--- /dev/null
+++ b/log4j-jul/src/test/resources/log4j2-julBridge-test.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Log4jBridgeHandlerTest TEST execution config -->
+<Configuration status="info">
+ <Appenders>
+ <Console name="STDOUT" target="SYSTEM_ERR" follow="true"> <!-- syserr + follow !! -->
+ <PatternLayout pattern="log4j2: %d{HH:mm:ss.SSS} %5level - [%thread][%logger: %class/%method/%line] - %message%n" />
+ </Console>
+ </Appenders>
+
+ <Loggers>
+ <Root level="DEBUG">
+ <AppenderRef ref="STDOUT" />
+ </Root>
+
+ <!-- needs to be set to a lower level: -->
+ <Logger name="org.apache.logging.log4j.jul.Log4jBridgeHandlerTest" level="TRACE" />
+ <!-- some test configs: -->
+ <Logger name="log4j.Log4jBridgeHandlerTest.propagate1" level="DEBUG" />
+ <Logger name="log4j.Log4jBridgeHandlerTest.propagate1.nested1" level="TRACE" />
+ <Logger name="log4j.Log4jBridgeHandlerTest.propagate1.nested2.deeplyNested" level="WARN" />
+ <Logger name="log4j.Log4jBridgeHandlerTest.propagate2" level="ALL" />
+ <Logger name="log4j.Log4jBridgeHandlerTest.propagate2.nested.deeplyNested" level="INFO" />
+ </Loggers>
+</Configuration>
diff --git a/log4j-jul/src/test/resources/logging-test.properties b/log4j-jul/src/test/resources/logging-test.properties
new file mode 100644
index 0000000..a7d6d1d
--- /dev/null
+++ b/log4j-jul/src/test/resources/logging-test.properties
@@ -0,0 +1,31 @@
+### JUL configuration for Log4jBridgeHandler test
+# JVM must be started with to use this file: -Djava.util.logging.config.file=path_to_this_file
+
+
+# install bridge but also output JUL-logs to console (order of handler matters!):
+handlers = org.apache.logging.log4j.jul.Log4jBridgeHandler, java.util.logging.ConsoleHandler
+
+#org.apache.logging.log4j.jul.Log4jBridgeHandler.sysoutDebug = true
+# append given suffix to logger names (e.g. "_JUL"); a dot is prepended automatically
+org.apache.logging.log4j.jul.Log4jBridgeHandler.appendSuffix = _JUL
+org.apache.logging.log4j.jul.Log4jBridgeHandler.propagateLevels = true
+
+# ConsoleHandler defaults to INFO filtering, but we need all here
+java.util.logging.ConsoleHandler.level = ALL
+java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter
+java.util.logging.SimpleFormatter.format = JUL: %1$tT.%1$tL %4$s [%3$s: %2$s] - %5$s%6$s%n
+
+
+# note: JUL levels are SEVERE, WARNING, INFO, FINE, FINER, FINEST, ALL
+
+# set default JUL logging level (FINER is for entering, exiting etc.)
+# out-comment to use default of "INFO" - will be set by level propagation to DEBUG=FINE again
+#.level = FINE
+org.apache.logging.log4j.jul.Log4jBridgeHandlerTest.level = FINER
+# do not log mail-init. (is done on INFO-level) because this would init. JUL before setErr() happens
+javax.mail.level = WARNING
+
+# configure (initial) JUL levels differently to log4j-config (and use high levels here)
+log4j.Log4jBridgeHandlerTest.propagate1.nested1.level = SEVERE
+# this is a logger not directly available in log4j, but the level above and below is defined in log4j:
+log4j.Log4jBridgeHandlerTest.propagate2.nested.level = WARNING
diff --git a/log4j-kafka/pom.xml b/log4j-kafka/pom.xml
index 2291085..abca119 100644
--- a/log4j-kafka/pom.xml
+++ b/log4j-kafka/pom.xml
@@ -116,6 +116,7 @@
<!-- module link generation is completely broken in the javadoc plugin for a multi-module non-aggregating project -->
<detectOfflineLinks>false</detectOfflineLinks>
<linksource>true</linksource>
+ <source>8</source>
</configuration>
<reportSets>
<reportSet>
diff --git a/log4j-kafka/src/main/java/org/apache/logging/log4j/kafka/appender/KafkaAppender.java b/log4j-kafka/src/main/java/org/apache/logging/log4j/kafka/appender/KafkaAppender.java
index 687a9e7..ec5e512 100644
--- a/log4j-kafka/src/main/java/org/apache/logging/log4j/kafka/appender/KafkaAppender.java
+++ b/log4j-kafka/src/main/java/org/apache/logging/log4j/kafka/appender/KafkaAppender.java
@@ -17,26 +17,23 @@
package org.apache.logging.log4j.kafka.appender;
-import java.io.Serializable;
-import java.util.Objects;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-
import org.apache.logging.log4j.core.AbstractLifeCycle;
import org.apache.logging.log4j.core.Appender;
import org.apache.logging.log4j.core.Filter;
import org.apache.logging.log4j.core.Layout;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.appender.AbstractAppender;
-import org.apache.logging.log4j.core.config.Configuration;
-import org.apache.logging.log4j.core.config.Node;
import org.apache.logging.log4j.core.config.Property;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
-import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
-import org.apache.logging.log4j.core.config.plugins.PluginElement;
-import org.apache.logging.log4j.core.layout.SerializedLayout;
+import org.apache.logging.log4j.plugins.Node;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginAttribute;
+import org.apache.logging.log4j.plugins.PluginFactory;
+
+import java.io.Serializable;
+import java.util.Objects;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
/**
* Sends log events to an Apache Kafka topic.
@@ -46,22 +43,27 @@
/**
* Builds KafkaAppender instances.
- * @param <B> The type to build
+ *
+ * @param <B>
+ * The type to build
*/
public static class Builder<B extends Builder<B>> extends AbstractAppender.Builder<B>
- implements org.apache.logging.log4j.core.util.Builder<KafkaAppender> {
+ implements org.apache.logging.log4j.plugins.util.Builder<KafkaAppender> {
- @PluginAttribute("topic")
+ @PluginAttribute
+ private String retryCount;
+
+ @PluginAttribute
private String topic;
- @PluginAttribute("key")
+ @PluginAttribute
private String key;
-
- @PluginAttribute(value = "syncSend", defaultBoolean = true)
+
+ @PluginAttribute(defaultBoolean = true)
private boolean syncSend;
- @PluginElement("Properties")
- private Property[] properties;
+ @PluginAttribute(value = "eventTimestamp", defaultBoolean = true)
+ private boolean sendEventTimestamp;
@SuppressWarnings("resource")
@Override
@@ -71,9 +73,9 @@
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, retryCount);
+ return new KafkaAppender(getName(), layout, getFilter(), isIgnoreExceptions(), getPropertyArray(), kafkaManager);
}
public String getTopic() {
@@ -84,8 +86,8 @@
return syncSend;
}
- public Property[] getProperties() {
- return properties;
+ public boolean isSendEventTimestamp() {
+ return sendEventTimestamp;
}
public B setTopic(final String topic) {
@@ -93,42 +95,43 @@
return asBuilder();
}
+ public B setKey(final String key) {
+ this.key = key;
+ return asBuilder();
+ }
+
public B setSyncSend(final boolean syncSend) {
this.syncSend = syncSend;
return asBuilder();
}
- public B setProperties(final Property[] properties) {
- this.properties = properties;
+ public B setSendEventTimestamp(boolean sendEventTimestamp) {
+ this.sendEventTimestamp = sendEventTimestamp;
return asBuilder();
}
- }
-
- @Deprecated
- public static KafkaAppender createAppender(
- final Layout<? extends Serializable> layout,
- final Filter filter,
- final String name,
- final boolean ignoreExceptions,
- final String topic,
- final Property[] properties,
- final Configuration configuration,
- final String key) {
- if (layout == null) {
- AbstractLifeCycle.LOGGER.error("No layout provided for KafkaAppender");
- return null;
+ public Integer getRetryCount() {
+ Integer intRetryCount = null;
+ try {
+ intRetryCount = Integer.valueOf(retryCount);
+ } catch (NumberFormatException e) {
+
+ }
+ return intRetryCount;
}
- final KafkaManager kafkaManager =
- new KafkaManager(configuration.getLoggerContext(), name, topic, true, properties, key);
- return new KafkaAppender(name, layout, filter, ignoreExceptions, kafkaManager);
+
+ public B setRetryCount(final String retryCount) {
+ this.retryCount = retryCount;
+ return asBuilder();
+ }
}
/**
* Creates a builder for a KafkaAppender.
+ *
* @return a builder for a KafkaAppender.
*/
- @PluginBuilderFactory
+ @PluginFactory
public static <B extends Builder<B>> B newBuilder() {
return new Builder<B>().asBuilder();
}
@@ -136,8 +139,8 @@
private final KafkaManager manager;
private KafkaAppender(final String name, final Layout<? extends Serializable> layout, final Filter filter,
- final boolean ignoreExceptions, final KafkaManager manager) {
- super(name, filter, layout, ignoreExceptions);
+ final boolean ignoreExceptions, Property[] properties, final KafkaManager manager) {
+ super(name, filter, layout, ignoreExceptions, properties);
this.manager = Objects.requireNonNull(manager, "manager");
}
@@ -157,16 +160,11 @@
private void tryAppend(final LogEvent event) throws ExecutionException, InterruptedException, TimeoutException {
final Layout<? extends Serializable> 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);
+ Long eventTimestamp;
+
+ data = layout.toByteArray(event);
+ eventTimestamp = event.getTimeMillis();
+ manager.send(data, eventTimestamp);
}
@Override
@@ -186,10 +184,6 @@
@Override
public String toString() {
- return "KafkaAppender{" +
- "name=" + getName() +
- ", state=" + getState() +
- ", topic=" + manager.getTopic() +
- '}';
+ return "KafkaAppender{" + "name=" + getName() + ", state=" + getState() + ", topic=" + manager.getTopic() + '}';
}
}
diff --git a/log4j-kafka/src/main/java/org/apache/logging/log4j/kafka/appender/KafkaManager.java b/log4j-kafka/src/main/java/org/apache/logging/log4j/kafka/appender/KafkaManager.java
index dfb1d13..a4e4ddb 100644
--- a/log4j-kafka/src/main/java/org/apache/logging/log4j/kafka/appender/KafkaManager.java
+++ b/log4j-kafka/src/main/java/org/apache/logging/log4j/kafka/appender/KafkaManager.java
@@ -31,6 +31,7 @@
import org.apache.kafka.clients.producer.RecordMetadata;
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.appender.AbstractManager;
+import org.apache.logging.log4j.core.appender.ManagerFactory;
import org.apache.logging.log4j.core.config.Property;
import org.apache.logging.log4j.core.util.Log4jThread;
@@ -50,15 +51,34 @@
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;
+ */
+ private KafkaManager(final LoggerContext loggerContext, final String name, final String topic, final boolean syncSend,
+ final boolean sendTimestamp, final Property[] properties, final String key, final String retryCount) {
super(loggerContext, name);
this.topic = Objects.requireNonNull(topic, "topic");
this.syncSend = syncSend;
+ this.sendTimestamp = sendTimestamp;
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");
+
+ if(retryCount!=null) {
+ try {
+ Integer.parseInt(retryCount);
+ config.setProperty("retries", retryCount);
+ }catch(NumberFormatException numberFormatException) {
+
+ }
+
+
+ }
+
for (final Property property : properties) {
config.setProperty(property.getName(), property.getValue());
}
@@ -100,9 +120,10 @@
}
}
- public void send(final byte[] msg) throws ExecutionException, InterruptedException, TimeoutException {
+ public void send(final byte[] msg, final Long eventTimestamp) throws ExecutionException, InterruptedException, TimeoutException {
if (producer != null) {
byte[] newKey = null;
+ Long timestamp = null;
if(key != null && key.contains("${")) {
newKey = getLoggerContext().getConfiguration().getStrSubstitutor().replace(key).getBytes(StandardCharsets.UTF_8);
@@ -110,7 +131,11 @@
newKey = key.getBytes(StandardCharsets.UTF_8);
}
- final ProducerRecord<byte[], byte[]> newRecord = new ProducerRecord<>(topic, newKey, msg);
+ if(sendTimestamp) {
+ timestamp = eventTimestamp;
+ }
+
+ final ProducerRecord<byte[], byte[]> newRecord = new ProducerRecord<>(topic, null, timestamp, newKey, msg);
if (syncSend) {
final Future<RecordMetadata> response = producer.send(newRecord);
response.get(timeoutMillis, TimeUnit.MILLISECONDS);
@@ -135,4 +160,45 @@
return topic;
}
+ public 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 String retryCount) {
+ StringBuilder sb = new StringBuilder(name);
+ 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, retryCount));
+ }
+
+ 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;
+ private final String retryCount;
+
+ public FactoryData(final LoggerContext loggerContext, final String topic, final boolean syncSend,
+ final boolean sendTimestamp, final Property[] properties, final String key, final String retryCount) {
+ this.loggerContext = loggerContext;
+ this.topic = topic;
+ this.syncSend = syncSend;
+ this.sendTimestamp = sendTimestamp;
+ this.properties = properties;
+ this.key = key;
+ this.retryCount = retryCount;
+ }
+
+ }
+
+ private static class KafkaManagerFactory implements ManagerFactory<KafkaManager, FactoryData> {
+ @Override
+ public KafkaManager createManager(String name, FactoryData data) {
+ return new KafkaManager(data.loggerContext, name, data.topic, data.syncSend, data.sendTimestamp,
+ data.properties, data.key, data.retryCount);
+ }
+ }
+
}
diff --git a/log4j-kafka/src/main/java/org/apache/logging/log4j/kafka/appender/KafkaProducerFactory.java b/log4j-kafka/src/main/java/org/apache/logging/log4j/kafka/appender/KafkaProducerFactory.java
index 2194492..6956323 100644
--- a/log4j-kafka/src/main/java/org/apache/logging/log4j/kafka/appender/KafkaProducerFactory.java
+++ b/log4j-kafka/src/main/java/org/apache/logging/log4j/kafka/appender/KafkaProducerFactory.java
@@ -36,4 +36,4 @@
*/
Producer<byte[], byte[]> newKafkaProducer(Properties config);
-}
\ No newline at end of file
+}
diff --git a/log4j-kafka/src/site/manual/index.md b/log4j-kafka/src/site/manual/index.md
index d70256a..1438ee1 100644
--- a/log4j-kafka/src/site/manual/index.md
+++ b/log4j-kafka/src/site/manual/index.md
@@ -18,7 +18,7 @@
# Apache Log4j Kafka module
-As of Log4j 2.11.0, Kafka support has moved from the existing module logj-core to the new module log4j-kafka.
+As of Log4j 2.11.0, Kafka support has moved from the existing module log4j-core to the new module log4j-kafka.
## Requirements
diff --git a/log4j-kafka/src/test/java/org/apache/logging/log4j/kafka/appender/KafkaAppenderCloseTimeoutTest.java b/log4j-kafka/src/test/java/org/apache/logging/log4j/kafka/appender/KafkaAppenderCloseTimeoutTest.java
index 25cdd60..77966fc 100644
--- a/log4j-kafka/src/test/java/org/apache/logging/log4j/kafka/appender/KafkaAppenderCloseTimeoutTest.java
+++ b/log4j-kafka/src/test/java/org/apache/logging/log4j/kafka/appender/KafkaAppenderCloseTimeoutTest.java
@@ -77,4 +77,4 @@
final Appender appender = ctx.getRequiredAppender("KafkaAppender");
appender.stop();
}
-}
\ No newline at end of file
+}
diff --git a/log4j-kafka/src/test/java/org/apache/logging/log4j/kafka/appender/KafkaAppenderTest.java b/log4j-kafka/src/test/java/org/apache/logging/log4j/kafka/appender/KafkaAppenderTest.java
index 8df3057..f726fd4 100644
--- a/log4j-kafka/src/test/java/org/apache/logging/log4j/kafka/appender/KafkaAppenderTest.java
+++ b/log4j-kafka/src/test/java/org/apache/logging/log4j/kafka/appender/KafkaAppenderTest.java
@@ -103,20 +103,6 @@
}
@Test
- public void testAppendWithSerializedLayout() throws Exception {
- final Appender appender = ctx.getRequiredAppender("KafkaAppenderWithSerializedLayout");
- final LogEvent logEvent = createLogEvent();
- appender.append(logEvent);
- final List<ProducerRecord<byte[], byte[]>> history = kafka.history();
- assertEquals(1, history.size());
- final ProducerRecord<byte[], byte[]> item = history.get(0);
- assertNotNull(item);
- assertEquals(TOPIC_NAME, item.topic());
- assertNull(item.key());
- assertEquals(LOG_MESSAGE, deserializeLogEvent(item.value()).getMessage().getFormattedMessage());
- }
-
- @Test
public void testAsyncAppend() throws Exception {
final Appender appender = ctx.getRequiredAppender("AsyncKafkaAppender");
appender.append(createLogEvent());
@@ -140,6 +126,7 @@
assertNotNull(item);
assertEquals(TOPIC_NAME, item.topic());
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));
}
@@ -157,11 +144,28 @@
assertNotNull(item);
assertEquals(TOPIC_NAME, item.topic());
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 testAppenderNoEventTimestamp() throws Exception {
+ final Appender appender = ctx.getRequiredAppender("KafkaAppenderNoEventTimestamp");
+ final LogEvent logEvent = createLogEvent();
+ appender.append(logEvent);
+ final List<ProducerRecord<byte[], byte[]>> history = kafka.history();
+ assertEquals(1, history.size());
+ final ProducerRecord<byte[], byte[]> item = history.get(0);
+ assertNotNull(item);
+ assertEquals(TOPIC_NAME, item.topic());
+ 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));
+ }
+
private LogEvent deserializeLogEvent(final byte[] data) throws IOException, ClassNotFoundException {
final ByteArrayInputStream bis = new ByteArrayInputStream(data);
try (ObjectInput ois = new FilteredObjectInputStream(bis)) {
@@ -169,4 +173,4 @@
}
}
-}
\ No newline at end of file
+}
diff --git a/log4j-core/src/test/resources/KafkaAppenderCloseTimeoutTest.xml b/log4j-kafka/src/test/resources/KafkaAppenderCloseTimeoutTest.xml
similarity index 100%
rename from log4j-core/src/test/resources/KafkaAppenderCloseTimeoutTest.xml
rename to log4j-kafka/src/test/resources/KafkaAppenderCloseTimeoutTest.xml
diff --git a/log4j-kafka/src/test/resources/KafkaAppenderTest.xml b/log4j-kafka/src/test/resources/KafkaAppenderTest.xml
new file mode 100644
index 0000000..725fb9b
--- /dev/null
+++ b/log4j-kafka/src/test/resources/KafkaAppenderTest.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="UTF-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 "License"); you may not use this file except in compliance with
+ ~ the License. You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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="KafkaAppenderTest" status="OFF">
+ <Appenders>
+ <Kafka name="KafkaAppenderWithLayout" topic="kafka-topic">
+ <PatternLayout pattern="[%m]"/>
+ <Property name="bootstrap.servers">localhost:9092</Property>
+ <Property name="timeout.ms">1000</Property>
+ </Kafka>
+ <Kafka name="AsyncKafkaAppender" topic="kafka-topic">
+ <PatternLayout pattern="%m"/>
+ <Property name="bootstrap.servers">localhost:9092</Property>
+ <Property name="syncSend">false</Property>
+ </Kafka>
+ <Kafka name="KafkaAppenderWithKey" topic="kafka-topic" key="key">
+ <PatternLayout pattern="%m"/>
+ <Property name="timeout.ms">1000</Property>
+ <Property name="bootstrap.servers">localhost:9092</Property>
+ </Kafka>
+ <Kafka name="KafkaAppenderWithKeyLookup" topic="kafka-topic" key="$${date:dd-MM-yyyy}">
+ <PatternLayout pattern="%m"/>
+ <Property name="timeout.ms">1000</Property>
+ <Property name="bootstrap.servers">localhost:9092</Property>
+ </Kafka>
+ <Kafka name="KafkaAppenderNoEventTimestamp" topic="kafka-topic" key="key" eventTimestamp="false">
+ <PatternLayout pattern="%m"/>
+ <Property name="timeout.ms">1000</Property>
+ <Property name="bootstrap.servers">localhost:9092</Property>
+ </Kafka>
+ </Appenders>
+ <Loggers>
+ <Root level="info">
+ <AppenderRef ref="KafkaAppenderWithLayout"/>
+ <AppenderRef ref="AsyncKafkaAppender"/>
+ <AppenderRef ref="KafkaAppenderWithKey"/>
+ <AppenderRef ref="KafkaAppenderNoEventTimestamp"/>
+ </Root>
+ </Loggers>
+</Configuration>
\ No newline at end of file
diff --git a/log4j-kubernetes/pom.xml b/log4j-kubernetes/pom.xml
new file mode 100644
index 0000000..0283725
--- /dev/null
+++ b/log4j-kubernetes/pom.xml
@@ -0,0 +1,207 @@
+<?xml version="1.0" encoding="UTF-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 "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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 xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j</artifactId>
+ <version>3.0.0-SNAPSHOT</version>
+ <relativePath>../</relativePath>
+ </parent>
+ <artifactId>log4j-kubernetes</artifactId>
+ <packaging>jar</packaging>
+ <name>Apache Log4j Kubernetes Library</name>
+ <description>Apache Log4j Kubernetes Support</description>
+ <properties>
+ <log4jParentDir>${basedir}/..</log4jParentDir>
+ <docLabel>Log4j Kubernetes Library Documentation</docLabel>
+ <projectDir>/kubernetes</projectDir>
+ <maven.compiler.source>1.8</maven.compiler.source>
+ <maven.compiler.target>1.8</maven.compiler.target>
+ <module.name>org.apache.logging.log4j.kubernetes</module.name>
+ </properties>
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-core</artifactId>
+ </dependency>
+ <!-- Kubernetes Client -->
+ <dependency>
+ <groupId>io.fabric8</groupId>
+ <artifactId>kubernetes-client</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-toolchains-plugin</artifactId>
+ <version>1.1</version>
+ <executions>
+ <execution>
+ <goals>
+ <goal>toolchain</goal>
+ </goals>
+ </execution>
+ </executions>
+ <configuration>
+ <toolchains>
+ <jdk>
+ <version>[8, )</version>
+ </jdk>
+ </toolchains>
+ </configuration>
+ </plugin>
+ <!-- Include the standard NOTICE and LICENSE -->
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-remote-resources-plugin</artifactId>
+ <executions>
+ <execution>
+ <goals>
+ <goal>process</goal>
+ </goals>
+ <configuration>
+ <skip>false</skip>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ </plugin>
+ <plugin>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <forkCount>1</forkCount>
+ <reuseForks>false</reuseForks>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ <reporting>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-changes-plugin</artifactId>
+ <version>${changes.plugin.version}</version>
+ <reportSets>
+ <reportSet>
+ <reports>
+ <report>changes-report</report>
+ </reports>
+ </reportSet>
+ </reportSets>
+ <configuration>
+ <issueLinkTemplate>%URL%/show_bug.cgi?id=%ISSUE%</issueLinkTemplate>
+ <useJql>true</useJql>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-checkstyle-plugin</artifactId>
+ <version>${checkstyle.plugin.version}</version>
+ <configuration>
+ <!--<propertiesLocation>${vfs.parent.dir}/checkstyle.properties</propertiesLocation> -->
+ <configLocation>${log4jParentDir}/checkstyle.xml</configLocation>
+ <suppressionsLocation>${log4jParentDir}/checkstyle-suppressions.xml</suppressionsLocation>
+ <enableRulesSummary>false</enableRulesSummary>
+ <propertyExpansion>basedir=${basedir}</propertyExpansion>
+ <propertyExpansion>licensedir=${log4jParentDir}/checkstyle-header.txt</propertyExpansion>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-javadoc-plugin</artifactId>
+ <version>${javadoc.plugin.version}</version>
+ <configuration>
+ <bottom><![CDATA[<p align="center">Copyright © {inceptionYear}-{currentYear} {organizationName}. All Rights Reserved.<br />
+ Apache Logging, Apache Log4j, Log4j, Apache, the Apache feather logo, the Apache Logging project logo,
+ and the Apache Log4j logo are trademarks of The Apache Software Foundation.</p>]]></bottom>
+ <!-- module link generation is completely broken in the javadoc plugin for a multi-module non-aggregating
+ project -->
+ <detectOfflineLinks>false</detectOfflineLinks>
+ <linksource>true</linksource>
+ <source>8</source>
+ </configuration>
+ <reportSets>
+ <reportSet>
+ <id>non-aggregate</id>
+ <reports>
+ <report>javadoc</report>
+ </reports>
+ </reportSet>
+ </reportSets>
+ </plugin>
+ <plugin>
+ <groupId>com.github.spotbugs</groupId>
+ <artifactId>spotbugs-maven-plugin</artifactId>
+ <configuration>
+ <fork>true</fork>
+ <jvmArgs>-Duser.language=en</jvmArgs>
+ <threshold>Normal</threshold>
+ <effort>Default</effort>
+ <excludeFilterFile>${log4jParentDir}/spotbugs-exclude-filter.xml</excludeFilterFile>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jxr-plugin</artifactId>
+ <version>${jxr.plugin.version}</version>
+ <reportSets>
+ <reportSet>
+ <id>non-aggregate</id>
+ <reports>
+ <report>jxr</report>
+ </reports>
+ </reportSet>
+ <reportSet>
+ <id>aggregate</id>
+ <reports>
+ <report>aggregate</report>
+ </reports>
+ </reportSet>
+ </reportSets>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-pmd-plugin</artifactId>
+ <version>${pmd.plugin.version}</version>
+ <configuration>
+ <targetJdk>${maven.compiler.target}</targetJdk>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>net.sourceforge.maven-taglib</groupId>
+ <artifactId>maven-taglib-plugin</artifactId>
+ <version>2.4</version>
+ </plugin>
+ </plugins>
+ </reporting>
+</project>
+
diff --git a/log4j-kubernetes/src/main/java/org/apache/logging/log4j/kubernetes/ContainerUtil.java b/log4j-kubernetes/src/main/java/org/apache/logging/log4j/kubernetes/ContainerUtil.java
new file mode 100644
index 0000000..7944e86
--- /dev/null
+++ b/log4j-kubernetes/src/main/java/org/apache/logging/log4j/kubernetes/ContainerUtil.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.kubernetes;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Objects;
+
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.status.StatusLogger;
+
+/**
+ * Locate the current docker container.
+ */
+public class ContainerUtil {
+ private static final Logger LOGGER = StatusLogger.getLogger();
+ private static final int MAXLENGTH = 65;
+
+/**
+ * Returns the container id when running in a Docker container.
+ *
+ * This inspects /proc/self/cgroup looking for a Kubernetes Control Group. Once it finds one it attempts
+ * to isolate just the docker container id. There doesn't appear to be a standard way to do this, but
+ * it seems to be the only way to determine what the current container is in a multi-container pod. It would have
+ * been much nicer if Kubernetes would just put the container id in a standard environment variable.
+ *
+ * @see <a href="http://stackoverflow.com/a/25729598/12916">Stackoverflow</a> for a discussion on retrieving the containerId.
+ * @see <a href="https://github.com/jenkinsci/docker-workflow-plugin/blob/master/src/main/java/org/jenkinsci/plugins/docker/workflow/client/ControlGroup.java">ControlGroup</a>
+ * for the original version of this. Not much is actually left but it provided good inspiration.
+ * @return The container id.
+ */
+ public static String getContainerId() {
+ try {
+ File file = new File("/proc/self/cgroup");
+ if (file.exists()) {
+ Path path = file.toPath();
+ String id = Files.lines(path).map(ContainerUtil::getContainerId).filter(Objects::nonNull)
+ .findFirst().orElse(null);
+ LOGGER.debug("Found container id {}", id);
+ return id;
+ } else {
+ LOGGER.warn("Unable to access container information");
+ }
+ } catch (IOException ioe) {
+ LOGGER.warn("Error obtaining container id: {}", ioe.getMessage());
+ }
+ return null;
+ }
+
+ private static String getContainerId(String line) {
+ // Every control group in Kubernetes will use
+ if (line.contains("/kubepods")) {
+ // Strip off everything up to the last slash.
+ int i = line.lastIndexOf('/');
+ if (i < 0) {
+ return null;
+ }
+ // If the remainder has a period then take everything up to it.
+ line = line.substring(i + 1);
+ i = line.lastIndexOf('.');
+ if (i > 0) {
+ line = line.substring(0, i);
+ }
+ // Everything ending with a '/' has already been stripped but the remainder might start with "docker-"
+ if (line.contains("docker-")) {
+ // 8:cpuset:/kubepods.slice/kubepods-pod9c26dfb6_b9c9_11e7_bfb9_02c6c1fc4861.slice/docker-3dd988081e7149463c043b5d9c57d7309e079c5e9290f91feba1cc45a04d6a5b.scope
+ i = line.lastIndexOf("docker-");
+ line = line.substring(i + 7);
+ }
+ return line.length() <= MAXLENGTH ? line : line.substring(0, MAXLENGTH);
+ }
+
+ return null;
+ }
+}
diff --git a/log4j-kubernetes/src/main/java/org/apache/logging/log4j/kubernetes/KubernetesClientBuilder.java b/log4j-kubernetes/src/main/java/org/apache/logging/log4j/kubernetes/KubernetesClientBuilder.java
new file mode 100644
index 0000000..79c942d
--- /dev/null
+++ b/log4j-kubernetes/src/main/java/org/apache/logging/log4j/kubernetes/KubernetesClientBuilder.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.kubernetes;
+
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.status.StatusLogger;
+
+import io.fabric8.kubernetes.client.Config;
+import io.fabric8.kubernetes.client.ConfigBuilder;
+import io.fabric8.kubernetes.client.DefaultKubernetesClient;
+import io.fabric8.kubernetes.client.KubernetesClient;
+
+/**
+ * Builds a Kubernetes Client.
+ */
+public class KubernetesClientBuilder {
+
+ private static final Logger LOGGER = StatusLogger.getLogger();
+
+ public KubernetesClient createClient() {
+ Config config = kubernetesClientConfig();
+ return config != null ? new DefaultKubernetesClient(config) : null;
+ }
+
+ private Config kubernetesClientConfig() {
+ Config base = null;
+ try {
+ base = Config.autoConfigure(null);
+ } catch (Exception ex) {
+ if (ex instanceof NullPointerException) {
+ return null;
+ }
+ }
+ KubernetesClientProperties props = new KubernetesClientProperties(base);
+ Config properties = new ConfigBuilder(base)
+ .withApiVersion(props.getApiVersion())
+ .withCaCertData(props.getCaCertData())
+ .withCaCertFile(props.getCaCertFile())
+ .withClientCertData(props.getClientCertData())
+ .withClientCertFile(props.getClientCertFile())
+ .withClientKeyAlgo(props.getClientKeyAlgo())
+ .withClientKeyData(props.getClientKeyData())
+ .withClientKeyFile(props.getClientKeyFile())
+ .withClientKeyPassphrase(props.getClientKeyPassphrase())
+ .withConnectionTimeout(props.getConnectionTimeout())
+ .withHttpProxy(props.getHttpProxy())
+ .withHttpsProxy(props.getHttpsProxy())
+ .withMasterUrl(props.getMasterUrl())
+ .withNamespace(props.getNamespace())
+ .withNoProxy(props.getNoProxy())
+ .withPassword(props.getPassword())
+ .withProxyPassword(props.getProxyPassword())
+ .withProxyUsername(props.getProxyUsername())
+ .withRequestTimeout(props.getRequestTimeout())
+ .withRollingTimeout(props.getRollingTimeout())
+ .withTrustCerts(props.isTrustCerts())
+ .withUsername(props.getUsername())
+ .build();
+ return properties;
+ }
+}
diff --git a/log4j-kubernetes/src/main/java/org/apache/logging/log4j/kubernetes/KubernetesClientProperties.java b/log4j-kubernetes/src/main/java/org/apache/logging/log4j/kubernetes/KubernetesClientProperties.java
new file mode 100644
index 0000000..9382c6e
--- /dev/null
+++ b/log4j-kubernetes/src/main/java/org/apache/logging/log4j/kubernetes/KubernetesClientProperties.java
@@ -0,0 +1,191 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.logging.log4j.kubernetes;
+
+import java.time.Duration;
+
+import org.apache.logging.log4j.util.PropertiesUtil;
+
+import io.fabric8.kubernetes.client.Config;
+
+/**
+ * Obtains properties used to configure the Kubernetes client.
+ */
+public class KubernetesClientProperties {
+
+ private static final String[] PREFIXES = {"log4j2.kubernetes.client.", "spring.cloud.kubernetes.client."};
+ private static final String API_VERSION = "apiVersion";
+ private static final String CA_CERT_FILE = "caCertFile";
+ private static final String CA_CERT_DATA = "caCertData";
+ private static final String CLIENT_CERT_FILE = "clientCertFile";
+ private static final String CLIENT_CERT_DATA = "clientCertData";
+ private static final String CLIENT_KEY_FILE = "clientKeyFile";
+ private static final String CLIENT_KEY_DATA = "cientKeyData";
+ private static final String CLIENT_KEY_ALGO = "clientKeyAlgo";
+ private static final String CLIENT_KEY_PASSPHRASE = "clientKeyPassphrase";
+ private static final String CONNECTION_TIMEOUT = "connectionTimeout";
+ private static final String HTTP_PROXY = "httpProxy";
+ private static final String HTTPS_PROXY = "httpsProxy";
+ private static final String LOGGING_INTERVAL = "loggingInterval";
+ private static final String MASTER_URL = "masterUrl";
+ private static final String NAMESPACE = "namespace";
+ private static final String NO_PROXY = "noProxy";
+ private static final String PASSWORD = "password";
+ private static final String PROXY_USERNAME = "proxyUsername";
+ private static final String PROXY_PASSWORD = "proxyPassword";
+ private static final String REQUEST_TIMEOUT = "requestTimeout";
+ private static final String ROLLING_TIMEOUT = "rollingTimeout";
+ private static final String TRUST_CERTS = "trustCerts";
+ private static final String USERNAME = "username";
+ private static final String WATCH_RECONNECT_INTERVAL = "watchReconnectInterval";
+ private static final String WATCH_RECONNECT_LIMIT = "watchReconnectLimit";
+
+ private final PropertiesUtil props = PropertiesUtil.getProperties();
+ private final Config base;
+
+ public KubernetesClientProperties(Config base) {
+ this.base = base;
+ }
+
+
+ public String getApiVersion() {
+ return props.getStringProperty(PREFIXES, API_VERSION, base::getApiVersion);
+ }
+ public String getCaCertFile() {
+ return props.getStringProperty(PREFIXES, CA_CERT_FILE, base::getCaCertFile);
+ }
+
+ public String getCaCertData() {
+ return props.getStringProperty(PREFIXES, CA_CERT_DATA, base::getCaCertData);
+ }
+
+ public String getClientCertFile() {
+ return props.getStringProperty(PREFIXES, CLIENT_CERT_FILE, base::getClientCertFile);
+ }
+
+ public String getClientCertData() {
+ return props.getStringProperty(PREFIXES, CLIENT_CERT_DATA, base::getClientCertData);
+ }
+
+ public String getClientKeyFile() {
+ return props.getStringProperty(PREFIXES, CLIENT_KEY_FILE, base::getClientKeyFile);
+ }
+
+ public String getClientKeyData() {
+ return props.getStringProperty(PREFIXES, CLIENT_KEY_DATA, base::getClientKeyData);
+ }
+
+ public String getClientKeyAlgo() {
+ return props.getStringProperty(PREFIXES, CLIENT_KEY_ALGO, base::getClientKeyAlgo);
+ }
+
+ public String getClientKeyPassphrase() {
+ return props.getStringProperty(PREFIXES, CLIENT_KEY_PASSPHRASE, base::getClientKeyPassphrase);
+ }
+
+ public int getConnectionTimeout() {
+ Duration timeout = props.getDurationProperty(PREFIXES, CONNECTION_TIMEOUT, null);
+ if (timeout != null) {
+ return (int) timeout.toMillis();
+ }
+ return base.getConnectionTimeout();
+ }
+
+ public String getHttpProxy() {
+ return props.getStringProperty(PREFIXES, HTTP_PROXY, base::getHttpProxy);
+ }
+
+ public String getHttpsProxy() {
+ return props.getStringProperty(PREFIXES, HTTPS_PROXY, base::getHttpsProxy);
+ }
+
+ public int getLoggingInterval() {
+ Duration interval = props.getDurationProperty(PREFIXES, LOGGING_INTERVAL, null);
+ if (interval != null) {
+ return (int) interval.toMillis();
+ }
+ return base.getLoggingInterval();
+ }
+
+ public String getMasterUrl() {
+ return props.getStringProperty(PREFIXES, MASTER_URL, base::getMasterUrl);
+ }
+
+ public String getNamespace() {
+ return props.getStringProperty(PREFIXES, NAMESPACE, base::getNamespace);
+ }
+
+ public String[] getNoProxy() {
+ String result = props.getStringProperty(PREFIXES, NO_PROXY, null);
+ if (result != null) {
+ return result.replace("\\s", "").split(",");
+ }
+ return base.getNoProxy();
+ }
+
+ public String getPassword() {
+ return props.getStringProperty(PREFIXES, PASSWORD, base::getPassword);
+ }
+
+ public String getProxyUsername() {
+ return props.getStringProperty(PREFIXES, PROXY_USERNAME, base::getProxyUsername);
+ }
+
+ public String getProxyPassword() {
+ return props.getStringProperty(PREFIXES, PROXY_PASSWORD, base::getProxyPassword);
+ }
+
+ public int getRequestTimeout() {
+ Duration interval = props.getDurationProperty(PREFIXES, REQUEST_TIMEOUT, null);
+ if (interval != null) {
+ return (int) interval.toMillis();
+ }
+ return base.getRequestTimeout();
+ }
+
+ public long getRollingTimeout() {
+ Duration interval = props.getDurationProperty(PREFIXES, ROLLING_TIMEOUT, null);
+ if (interval != null) {
+ return interval.toMillis();
+ }
+ return base.getRollingTimeout();
+ }
+
+ public Boolean isTrustCerts() {
+ return props.getBooleanProperty(PREFIXES, TRUST_CERTS, base::isTrustCerts);
+ }
+
+ public String getUsername() {
+ return props.getStringProperty(PREFIXES, USERNAME, base::getUsername);
+ }
+
+ public int getWatchReconnectInterval() {
+ Duration interval = props.getDurationProperty(PREFIXES, WATCH_RECONNECT_INTERVAL, null);
+ if (interval != null) {
+ return (int) interval.toMillis();
+ }
+ return base.getWatchReconnectInterval();
+ }
+
+ public int getWatchReconnectLimit() {
+ Duration interval = props.getDurationProperty(PREFIXES, WATCH_RECONNECT_LIMIT, null);
+ if (interval != null) {
+ return (int) interval.toMillis();
+ }
+ return base.getWatchReconnectLimit();
+ }
+}
diff --git a/log4j-kubernetes/src/main/java/org/apache/logging/log4j/kubernetes/KubernetesLookup.java b/log4j-kubernetes/src/main/java/org/apache/logging/log4j/kubernetes/KubernetesLookup.java
new file mode 100644
index 0000000..2a16a9e
--- /dev/null
+++ b/log4j-kubernetes/src/main/java/org/apache/logging/log4j/kubernetes/KubernetesLookup.java
@@ -0,0 +1,290 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.logging.log4j.kubernetes;
+
+import java.net.URL;
+import java.nio.file.Paths;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.lookup.AbstractLookup;
+import org.apache.logging.log4j.core.lookup.StrLookup;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.status.StatusLogger;
+import org.apache.logging.log4j.util.LoaderUtil;
+import org.apache.logging.log4j.util.Strings;
+
+import io.fabric8.kubernetes.api.model.Container;
+import io.fabric8.kubernetes.api.model.ContainerStatus;
+import io.fabric8.kubernetes.api.model.Namespace;
+import io.fabric8.kubernetes.api.model.Pod;
+import io.fabric8.kubernetes.client.Config;
+import io.fabric8.kubernetes.client.KubernetesClient;
+
+
+/**
+ * Retrieve various Kubernetes attributes. Supported keys are:
+ * accountName, containerId, containerName, clusterName, host, hostIp, labels, labels.app,
+ * labels.podTemplateHash, masterUrl, namespaceId, namespaceName, podId, podIp, podName,
+ * imageId, imageName.
+ */
+@Plugin(name = "k8s", category = StrLookup.CATEGORY)
+public class KubernetesLookup extends AbstractLookup {
+
+ private static final Logger LOGGER = StatusLogger.getLogger();
+ private static final String HOSTNAME = "HOSTNAME";
+ private static final String SPRING_ENVIRONMENT_KEY = "SpringEnvironment";
+
+ private static volatile KubernetesInfo kubernetesInfo;
+ private static final Lock initLock = new ReentrantLock();
+ private static final boolean isSpringIncluded =
+ LoaderUtil.isClassAvailable("org.apache.logging.log4j.spring.cloud.config.client.SpringEnvironmentHolder");
+ private Pod pod;
+ private Namespace namespace;
+ private URL masterUrl;
+
+ public KubernetesLookup() {
+ this.pod = null;
+ this.namespace = null;
+ this.masterUrl = null;
+ initialize();
+ }
+
+ KubernetesLookup(Pod pod, Namespace namespace, URL masterUrl) {
+ this.pod = pod;
+ this.namespace = namespace;
+ this.masterUrl = masterUrl;
+ initialize();
+ }
+ private boolean initialize() {
+ if (kubernetesInfo == null || (isSpringIncluded && !kubernetesInfo.isSpringActive)) {
+ initLock.lock();
+ boolean isSpringActive = isSpringActive();
+ if (kubernetesInfo == null || (!kubernetesInfo.isSpringActive && isSpringActive)) {
+ try {
+ KubernetesInfo info = new KubernetesInfo();
+ KubernetesClient client = null;
+ info.isSpringActive = isSpringActive;
+ if (pod == null) {
+ client = new KubernetesClientBuilder().createClient();
+ if (client != null) {
+ pod = getCurrentPod(System.getenv(HOSTNAME), client);
+ info.masterUrl = client.getMasterUrl();
+ if (pod != null) {
+ info.namespace = pod.getMetadata().getNamespace();
+ namespace = namespace = client.namespaces().withName(info.namespace).get();
+ }
+ } else {
+ LOGGER.warn("Kubernetes is not available for access");
+ }
+ } else {
+ info.masterUrl = masterUrl;
+ }
+ if (pod != null) {
+ if (namespace != null) {
+ info.namespaceId = namespace.getMetadata().getUid();
+ info.namespaceAnnotations = namespace.getMetadata().getAnnotations();
+ info.namespaceLabels = namespace.getMetadata().getLabels();
+ }
+ info.app = pod.getMetadata().getLabels().get("app");
+ info.hostName = pod.getSpec().getNodeName();
+ info.annotations = pod.getMetadata().getAnnotations();
+ final String app = info.app != null ? info.app : "";
+ info.podTemplateHash = pod.getMetadata().getLabels().get("pod-template-hash");
+ info.accountName = pod.getSpec().getServiceAccountName();
+ info.clusterName = pod.getMetadata().getClusterName();
+ info.hostIp = pod.getStatus().getHostIP();
+ info.labels = pod.getMetadata().getLabels();
+ info.podId = pod.getMetadata().getUid();
+ info.podIp = pod.getStatus().getPodIP();
+ info.podName = pod.getMetadata().getName();
+ ContainerStatus containerStatus = null;
+ List<ContainerStatus> statuses = pod.getStatus().getContainerStatuses();
+ if (statuses.size() == 1) {
+ containerStatus = statuses.get(0);
+ } else if (statuses.size() > 1) {
+ String containerId = ContainerUtil.getContainerId();
+ if (containerId != null) {
+ containerStatus = statuses.stream()
+ .filter(cs -> cs.getContainerID().contains(containerId))
+ .findFirst().orElse(null);
+ }
+ }
+ final String containerName;
+ if (containerStatus != null) {
+ info.containerId = containerStatus.getContainerID();
+ info.imageId = containerStatus.getImageID();
+ containerName = containerStatus.getName();
+ } else {
+ containerName = null;
+ }
+ Container container = null;
+ List<Container> containers = pod.getSpec().getContainers();
+ if (containers.size() == 1) {
+ container = containers.get(0);
+ } else if (containers.size() > 1 && containerName != null) {
+ container = containers.stream().filter(c -> c.getName().equals(containerName))
+ .findFirst().orElse(null);
+ }
+ if (container != null) {
+ info.containerName = container.getName();
+ info.imageName = container.getImage();
+ }
+
+ kubernetesInfo = info;
+ }
+ } finally {
+ initLock.unlock();
+ }
+ }
+ }
+ return kubernetesInfo != null;
+ }
+
+ @Override
+ public String lookup(LogEvent event, String key) {
+ if (kubernetesInfo == null) {
+ return null;
+ }
+ switch (key) {
+ case "accountName": {
+ return kubernetesInfo.accountName;
+ }
+ case "annotations": {
+ return kubernetesInfo.annotations.toString();
+ }
+ case "containerId": {
+ return kubernetesInfo.containerId;
+ }
+ case "containerName": {
+ return kubernetesInfo.containerName;
+ }
+ case "clusterName": {
+ return kubernetesInfo.clusterName;
+ }
+ case "host": {
+ return kubernetesInfo.hostName;
+ }
+ case "hostIp": {
+ return kubernetesInfo.hostIp;
+ }
+ case "labels": {
+ return kubernetesInfo.labels.toString();
+ }
+ case "labels.app": {
+ return kubernetesInfo.app;
+ }
+ case "labels.podTemplateHash": {
+ return kubernetesInfo.podTemplateHash;
+ }
+ case "masterUrl": {
+ return kubernetesInfo.masterUrl.toString();
+ }
+ case "namespaceAnnotations": {
+ return kubernetesInfo.namespaceAnnotations.toString();
+ }
+ case "namespaceId": {
+ return kubernetesInfo.namespaceId;
+ }
+ case "namespaceLabels": {
+ return kubernetesInfo.namespaceLabels.toString();
+ }
+ case "namespaceName": {
+ return kubernetesInfo.namespace;
+ }
+ case "podId": {
+ return kubernetesInfo.podId;
+ }
+ case "podIp": {
+ return kubernetesInfo.podIp;
+ }
+ case "podName": {
+ return kubernetesInfo.podName;
+ }
+ case "imageId": {
+ return kubernetesInfo.imageId;
+ }
+ case "imageName": {
+ return kubernetesInfo.imageName;
+ }
+ default:
+ return null;
+ }
+ }
+
+ /**
+ * For unit testing only.
+ */
+ void clearInfo() {
+ kubernetesInfo = null;
+ }
+
+ private String getHostname() {
+ return System.getenv(HOSTNAME);
+ }
+
+ private Pod getCurrentPod(String hostName, KubernetesClient kubernetesClient) {
+ try {
+ if (isServiceAccount() && Strings.isNotBlank(hostName)) {
+ return kubernetesClient.pods().withName(hostName).get();
+ }
+ } catch (Throwable t) {
+ LOGGER.debug("Unable to locate pod with name {}.", hostName);
+ }
+ return null;
+ }
+
+ private boolean isServiceAccount() {
+ return Paths.get(Config.KUBERNETES_SERVICE_ACCOUNT_TOKEN_PATH).toFile().exists()
+ && Paths.get(Config.KUBERNETES_SERVICE_ACCOUNT_CA_CRT_PATH).toFile().exists();
+ }
+
+ private boolean isSpringActive() {
+ return isSpringIncluded && LogManager.getFactory() != null
+ && LogManager.getFactory().hasContext(KubernetesLookup.class.getName(), null, false)
+ && LogManager.getContext(false).getObject(SPRING_ENVIRONMENT_KEY) != null;
+ }
+
+ private static class KubernetesInfo {
+ boolean isSpringActive;
+ String accountName;
+ Map<String, String> annotations;
+ String app;
+ String clusterName;
+ String containerId;
+ String containerName;
+ String hostName;
+ String hostIp;
+ String imageId;
+ String imageName;
+ Map<String, String> labels;
+ URL masterUrl;
+ String namespace;
+ Map<String, String> namespaceAnnotations;
+ String namespaceId;
+ Map<String, String> namespaceLabels;
+ String podId;
+ String podIp;
+ String podName;
+ String podTemplateHash;
+ }
+}
diff --git a/log4j-kubernetes/src/site/markdown/index.md.vm b/log4j-kubernetes/src/site/markdown/index.md.vm
new file mode 100644
index 0000000..d51e757
--- /dev/null
+++ b/log4j-kubernetes/src/site/markdown/index.md.vm
@@ -0,0 +1,104 @@
+<!-- vim: set syn=markdown : -->
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+#set($dollar = '$')
+#set($h1='#')
+#set($h2='##')
+
+$h1 Log4j Kubernetes Support
+
+Log4j supports Kubernetes by providing a Lookup to retrieve container information.
+
+$h2 Accessing Kubernetes
+
+The Log4j Kubernetes support requires access to the Docker REST interface. In many cases the REST service
+can be accessed automatically. If needed the Kubernetes client can be configured any of the standard
+Log4j configuration locations or via the Spring Boot configuration. Note, however, that since Spring Boot
+causes logging to initialize 3 times and since the Spring environment is only available during the last
+Log4j initialization Spring properties will only be available to Log4j in the last initialization.
+
+$h2 Lookup Attributes
+
+Log4j Kubernetes provides access to the following container attributes:
+
+* accountName - The service account name.
+* clusterName - The name of the cluster the application is running in.
+* containerId - The full id assigned to the container.
+* containerName - The name assigned to the container.
+* host - The name of the host.
+* hostIp - The host's ip address.
+* imageId - The id assigned to the image.
+* imageName - The name assigned to the image.
+* labels - All labels formatted in a list.
+* labels.app - The application name.
+* labels.podTemplateHash - The pod's template hash value.
+* masterUrl - The url needed to access the API server.
+* namespaceId - The id of the namespace the various kubernetes components are located within.
+* namespaceName - The namespace the various kubernetes components are located within.
+* podId - The pod's id number.
+* podIp - The pod's ip address.
+* podName - The name of the pod.
+
+#set( $D = '${' )
+#set( $container = 'k8s:containerId}')
+Attributes may be accessed by adding
+```
+$D$container
+```
+to the configuration. Note that kubernetes variables are only resolved once during logging initialization so they
+shouldn't be referenced with more than one '$' character.
+
+$h2 Configuration
+
+Much of the configuration needed to access the Kubernetes API server is provided automatically by Kubernetes.
+However, it is not uncommon to need to provide the url required to access the Kubernetes API server or the
+namespace the application is assigned to. The properties below may either be configured using the Log4j
+variable names and located by Log4j's normal property resolution mechansim or Log4j will resolve the
+spring properties when the application is running in Spring Boot and the Spring Environment has been created.
+Note that Spring Boot initializes logging 3 times and only the last will have a Spring Environment present.
+
+| Log4j Property Name | Spring Property Name | Default | Description |
+|------------------------ |----------------------:|----------:|------------:|
+| log4j2.kubernetes.client.apiVersion | spring.cloud.kubernetes.client.apiVersion | v1 | Kubernetes API Version |
+| log4j2.kubernetes.client.caCertData | spring.cloud.kubernetes.client.caCertData | | Kubernetes API CACertData |
+| log4j2.kubernetes.client.caCertFile | spring.cloud.kubernetes.client.caCertFile | | Kubernetes API CACertFile |
+| log4j2.kubernetes.client.clientCertData | spring.cloud.kubernetes.client.clientCertData | | Kubernetes API ClientCertData |
+| log4j2.kubernetes.client.clientCertFile | spring.cloud.kubernetes.client.clientCertFile | | Kubernetes API ClientCertFile |
+| log4j2.kubernetes.client.clientKeyAlgo | spring.cloud.kubernetes.client.clientKeyAlgo | RSA | Kubernetes API ClientKeyAlgo |
+| log4j2.kubernetes.client.clientKeyData | spring.cloud.kubernetes.client.clientKeyData | | Kubernetes API ClientKeyData |
+| log4j2.kubernetes.client.clientKeyFile | spring.cloud.kubernetes.client.clientKeyFile | | Kubernetes API ClientKeyFile |
+| log4j2.kubernetes.client.clientKeyPassPhrase | spring.cloud.kubernetes.client.clientKeyPassphrase | changeit | Kubernetes API ClientKeyPassphrase |
+| log4j2.kubernetes.client.connectionTimeout | spring.cloud.kubernetes.client.connectionTimeout | 10s | Connection timeout |
+| log4j2.kubernetes.client.httpProxy | spring.cloud.kubernetes.client.http-proxy | | |
+| log4j2.kubernetes.client.httpsProxy | spring.cloud.kubernetes.client.https-proxy | | |
+| log4j2.kubernetes.client.loggingInberval | spring.cloud.kubernetes.client.loggingInterval | 20s | Logging interval |
+| log4j2.kubernetes.client.masterUrl | spring.cloud.kubernetes.client.masterUrl | kubernetes.default.svc | Kubernetes API Master Node URL |
+| log4j2.kubernetes.client.namespacce | spring.cloud.kubernetes.client.namespace | default | Kubernetes Namespace |
+| log4j2.kubernetes.client.noProxy | spring.cloud.kubernetes.client.noProxy | | |
+| log4j2.kubernetes.client.password | spring.cloud.kubernetes.client.password | | Kubernetes API Password |
+| log4j2.kubernetes.client.proxyPassword | spring.cloud.kubernetes.client.proxyPassword | | |
+| log4j2.kubernetes.client.proxyUsername | spring.cloud.kubernetes.client.proxyUsername | | |
+| log4j2.kubernetes.client.requestTimeout | spring.cloud.kubernetes.client.requestTimeout | 10s | Request timeout |
+| log4j2.kubernetes.client.rollingTimeout | spring.cloud.kubernetes.client.rollingTimeout | 900s | Rolling timeout |
+| log4j2.kubernetes.client.trustCerts | spring.cloud.kubernetes.client.trustCerts | false | Kubernetes API Trust Certificates |
+| log4j2.kubernetes.client.username | spring.cloud.kubernetes.client.username | | Kubernetes API Username |
+| log4j2.kubernetes.client.watchReconnectInterval | spring.cloud.kubernetes.client.watchReconnectInterval | 1s | Reconnect Interval |
+| log4j2.kubernetes.client.watchReconnectLimit | spring.cloud.kubernetes.client.watchReconnectLimit | -1 | Reconnect Interval limit retries |
+
+$h2 Requirements
+Log4j Kubernetes requires Log4j Core, Log4j API and a minimum of Java 8.
+For more information, see [Runtime Dependencies](../runtime-dependencies.html).
diff --git a/log4j-kubernetes/src/site/site.xml b/log4j-kubernetes/src/site/site.xml
new file mode 100644
index 0000000..7322f3b
--- /dev/null
+++ b/log4j-kubernetes/src/site/site.xml
@@ -0,0 +1,52 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+-->
+<project name="Log4j Docker Support"
+ xmlns="http://maven.apache.org/DECORATION/1.4.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/DECORATION/1.4.0 http://maven.apache.org/xsd/decoration-1.4.0.xsd">
+ <body>
+ <links>
+ <item name="Apache" href="http://www.apache.org/" />
+ <item name="Logging Services" href="http://logging.apache.org/"/>
+ <item name="Log4j" href="../index.html"/>
+ </links>
+
+ <!-- Component-specific reports -->
+ <menu ref="reports"/>
+
+ <!-- Overall Project Info -->
+ <menu name="Log4j Project Information" img="icon-info-sign">
+ <item name="Dependencies" href="../dependencies.html" />
+ <item name="Dependency Convergence" href="../dependency-convergence.html" />
+ <item name="Dependency Management" href="../dependency-management.html" />
+ <item name="Project Team" href="../team-list.html" />
+ <item name="Mailing Lists" href="../mail-lists.html" />
+ <item name="Issue Tracking" href="../issue-tracking.html" />
+ <item name="Project License" href="../license.html" />
+ <item name="Source Repository" href="../source-repository.html" />
+ <item name="Project Summary" href="../project-summary.html" />
+ </menu>
+
+ <menu name="Log4j Project Reports" img="icon-cog">
+ <item name="Changes Report" href="../changes-report.html" />
+ <item name="JIRA Report" href="../jira-report.html" />
+ <item name="Surefire Report" href="../surefire-report.html" />
+ <item name="RAT Report" href="../rat-report.html" />
+ </menu>
+ </body>
+</project>
diff --git a/log4j-kubernetes/src/test/java/org/apache/logging/log4j/kubernetes/KubernetesLookupTest.java b/log4j-kubernetes/src/test/java/org/apache/logging/log4j/kubernetes/KubernetesLookupTest.java
new file mode 100644
index 0000000..7174596
--- /dev/null
+++ b/log4j-kubernetes/src/test/java/org/apache/logging/log4j/kubernetes/KubernetesLookupTest.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.kubernetes;
+
+import java.io.File;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import io.fabric8.kubernetes.api.model.Namespace;
+import io.fabric8.kubernetes.api.model.ObjectMeta;
+import io.fabric8.kubernetes.api.model.Pod;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Validate the Kubernetes Lookup.
+ */
+public class KubernetesLookupTest {
+
+ private static final String localJson = "target/test-classes/localPod.json";
+ private static final String clusterJson = "target/test-classes/clusterPod.json";
+ private static final ObjectMapper objectMapper = new ObjectMapper();
+ public static URL masterUrl;
+
+ @BeforeClass
+ public static void beforeClass() throws Exception {
+ masterUrl = new URL("http://localhost:443/");
+ }
+
+ @Test
+ public void testLocal() throws Exception {
+ Pod pod = objectMapper.readValue(new File(localJson), Pod.class);
+ Namespace namespace = createNamespace();
+ KubernetesLookup lookup = new KubernetesLookup(pod, namespace, masterUrl);
+ try {
+ assertEquals("Incorrect container name", "sampleapp", lookup.lookup("containerName"));
+ assertEquals("Incorrect container id",
+ "docker://818b0098946c67e6ac56cb7c0934b7c2a9f50feb7244b422b2a7f566f7e5d0df",
+ lookup.lookup("containerId"));
+ assertEquals("Incorrect host name", "docker-desktop", lookup.lookup("host"));
+ assertEquals("Incorrect pod name", "sampleapp-584f99476d-mnrp4", lookup.lookup("podName"));
+ } finally {
+ lookup.clearInfo();;
+ }
+ }
+
+ @Test
+ public void testCluster() throws Exception {
+ Pod pod = objectMapper.readValue(new File(clusterJson), Pod.class);
+ Namespace namespace = createNamespace();
+ KubernetesLookup lookup = new KubernetesLookup(pod, namespace, masterUrl);
+ try {
+ assertEquals("Incorrect container name", "platform-forms-service", lookup.lookup("containerName"));
+ assertEquals("Incorrect container id",
+ "docker://2b7c2a93dfb48334aa549e29fdd38039ddd256eec43ba64c145fa4b75a1542f0",
+ lookup.lookup("containerId"));
+ assertEquals("Incorrect host name", "k8s-tmpcrm-worker-s03-04", lookup.lookup("host"));
+ assertEquals("Incorrect pod name", "platform-forms-service-primary-5ddfc4f9b8-kfpzv", lookup.lookup("podName"));
+ } finally {
+ lookup.clearInfo();
+ }
+ }
+
+ private Namespace createNamespace() {
+ Namespace namespace = new Namespace();
+ ObjectMeta meta = new ObjectMeta();
+ Map<String, String> annotations = new HashMap<>();
+ annotations.put("test", "name");
+ meta.setAnnotations(annotations);
+ Map<String, String> labels = new HashMap<>();
+ labels.put("ns", "my-namespace");
+ meta.setLabels(labels);
+ meta.setUid(UUID.randomUUID().toString());
+ namespace.setMetadata(meta);
+ return namespace;
+ }
+}
diff --git a/log4j-kubernetes/src/test/resources/clusterPod.json b/log4j-kubernetes/src/test/resources/clusterPod.json
new file mode 100644
index 0000000..7bae9c3
--- /dev/null
+++ b/log4j-kubernetes/src/test/resources/clusterPod.json
@@ -0,0 +1,177 @@
+{
+ "apiVersion": "v1",
+ "kind": "Pod",
+ "metadata": {
+ "annotations": {
+ "cni.projectcalico.org/podIP": "172.16.55.101/32",
+ "cni.projectcalico.org/podIPs": "172.16.55.101/32",
+ "flagger-id": "94d53b7b-cc06-41b3-bbac-a2d14a16d95d",
+ "prometheus.io/port": "9797",
+ "prometheus.io/scrape": "true"
+ },
+ "creationTimestamp": "2020-06-15T15:44:16Z",
+ "generateName": "platform-forms-service-primary-5ddfc4f9b8-",
+ "labels": {
+ "app": "platform-forms-service-primary",
+ "pod-template-hash": "5ddfc4f9b8"
+ },
+ "name": "platform-forms-service-primary-5ddfc4f9b8-kfpzv",
+ "namespace": "default",
+ "ownerReferences": [
+ {
+ "apiVersion": "apps/v1",
+ "kind": "ReplicaSet",
+ "blockOwnerDeletion": true,
+ "controller": true,
+ "name": "platform-forms-service-primary-5ddfc4f9b8",
+ "uid": "d2e89c56-7623-439e-a9ee-4a67e2f3a81a"
+ }],
+ "resourceVersion": "37382150",
+ "selfLink": "/api/v1/namespaces/default/pods/platform-forms-service-primary-5ddfc4f9b8-kfpzv",
+ "uid": "df8cbac1-129c-4cd3-b5bc-65d72d8ba5f0"
+ },
+ "spec": {
+ "containers": [
+ {
+ "env": [
+ {
+ "name": "APACHE_ENV",
+ "value": "tmpcrm"
+ },
+ {
+ "name": "SPRING_PROFILES_ACTIVE",
+ "value": "tmpcrm"
+ },
+ {
+ "name": "JAVA_OPTS",
+ "value": "-Dlogging.label=crm"
+ }],
+ "image": "docker.apache.xyz/platform-forms-service:0.15.0",
+ "imagePullPolicy": "Always",
+ "livenessProbe": {
+ "failureThreshold": 3,
+ "httpGet": {
+ "path": "/info",
+ "port": "http",
+ "scheme": "HTTP"
+ },
+ "periodSeconds": 10,
+ "successThreshold": 1,
+ "timeoutSeconds": 1
+ },
+ "name": "platform-forms-service",
+ "ports": [
+ {
+ "containerPort": 8080,
+ "name": "http",
+ "protocol": "TCP"
+ }],
+ "readinessProbe": {
+ "failureThreshold": 3,
+ "httpGet": {
+ "path": "/health",
+ "port": "http",
+ "scheme": "HTTP"
+ },
+ "periodSeconds": 10,
+ "successThreshold": 1,
+ "timeoutSeconds": 1
+ },
+ "resources": {
+ },
+ "securityContext": {
+ },
+ "terminationMessagePath": "/dev/termination-log",
+ "terminationMessagePolicy": "File",
+ "volumeMounts": [
+ {
+ "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount",
+ "name": "default-token-2nqlw",
+ "readOnly": true
+ }]
+ }],
+ "dnsPolicy": "ClusterFirst",
+ "enableServiceLinks": true,
+ "nodeName": "k8s-tmpcrm-worker-s03-04",
+ "priority": 0,
+ "restartPolicy": "Always",
+ "schedulerName": "default-scheduler",
+ "securityContext": {
+ },
+ "serviceAccount": "default",
+ "serviceAccountName": "default",
+ "terminationGracePeriodSeconds": 30,
+ "tolerations": [
+ {
+ "effect": "NoExecute",
+ "key": "node.kubernetes.io/not-ready",
+ "operator": "Exists",
+ "tolerationSeconds": 300
+ },
+ {
+ "effect": "NoExecute",
+ "key": "node.kubernetes.io/unreachable",
+ "operator": "Exists",
+ "tolerationSeconds": 300
+ }],
+ "volumes": [
+ {
+ "name": "default-token-2nqlw",
+ "secret": {
+ "defaultMode": 420,
+ "secretName": "default-token-2nqlw"
+ }
+ }]
+ },
+ "status": {
+ "conditions": [
+ {
+ "lastTransitionTime": "2020-06-15T15:44:16Z",
+ "status": "True",
+ "type": "Initialized"
+ },
+ {
+ "lastTransitionTime": "2020-06-15T15:44:46Z",
+ "status": "True",
+ "type": "Ready"
+ },
+ {
+ "lastTransitionTime": "2020-06-15T15:44:46Z",
+ "status": "True",
+ "type": "ContainersReady"
+ },
+ {
+ "lastTransitionTime": "2020-06-15T15:44:16Z",
+ "status": "True",
+ "type": "PodScheduled"
+ }],
+ "containerStatuses": [
+ {
+ "containerID": "docker://2b7c2a93dfb48334aa549e29fdd38039ddd256eec43ba64c145fa4b75a1542f0",
+ "image": "docker.apache.xyz/platform-forms-service:0.15.0",
+ "imageID":
+ "docker-pullable://docker.apache.xyz/platform-forms-service@sha256:45fd19ccd99e218a7685c4cee5bc5b16aeae1cdb8e8773f9c066d4cfb22ee195",
+ "lastState": {
+ },
+ "name": "platform-forms-service",
+ "ready": true,
+ "restartCount": 0,
+ "state": {
+ "running": {
+ "startedAt": "2020-06-15T15:44:21Z"
+ }
+ },
+ "started": true
+ }],
+ "hostIP": "10.103.220.170",
+ "phase": "Running",
+ "podIP": "172.16.55.101",
+ "qosClass": "BestEffort",
+ "startTime": "2020-06-15T15:44:16Z",
+ "podIPs": [
+ {
+ "ip": "172.16.55.101"
+ }]
+ }
+}
+
diff --git a/log4j-kubernetes/src/test/resources/localPod.json b/log4j-kubernetes/src/test/resources/localPod.json
new file mode 100644
index 0000000..3aeef46
--- /dev/null
+++ b/log4j-kubernetes/src/test/resources/localPod.json
@@ -0,0 +1,141 @@
+{
+ "apiVersion": "v1",
+ "kind": "Pod",
+ "metadata": {
+ "creationTimestamp": "2020-06-14T21:50:09Z",
+ "generateName": "sampleapp-584f99476d-",
+ "labels": {
+ "app": "sampleapp",
+ "pod-template-hash": "584f99476d"
+ },
+ "name": "sampleapp-584f99476d-mnrp4",
+ "namespace": "default",
+ "ownerReferences": [
+ {
+ "apiVersion": "apps/v1",
+ "kind": "ReplicaSet",
+ "blockOwnerDeletion": true,
+ "controller": true,
+ "name": "sampleapp-584f99476d",
+ "uid": "d68146d1-17c4-486e-aa8d-07d7d5d38b94"
+ }],
+ "resourceVersion": "1200430",
+ "selfLink": "/api/v1/namespaces/default/pods/sampleapp-584f99476d-mnrp4",
+ "uid": "9213879a-479c-42ce-856b-7e2666d21829"
+ },
+ "spec": {
+ "containers": [
+ {
+ "env": [
+ {
+ "name": "JAVA_OPTS",
+ "value": "-Delastic.search.host=host.docker.internal"
+ }],
+ "image": "localhost:5000/sampleapp:latest",
+ "imagePullPolicy": "Always",
+ "name": "sampleapp",
+ "ports": [
+ {
+ "containerPort": 8080,
+ "protocol": "TCP"
+ },
+ {
+ "containerPort": 5005,
+ "protocol": "TCP"
+ }],
+ "resources": {
+ },
+ "terminationMessagePath": "/dev/termination-log",
+ "terminationMessagePolicy": "File",
+ "volumeMounts": [
+ {
+ "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount",
+ "name": "default-token-jzq7d",
+ "readOnly": true
+ }]
+ }],
+ "dnsPolicy": "ClusterFirst",
+ "nodeName": "docker-desktop",
+ "priority": 0,
+ "restartPolicy": "Always",
+ "schedulerName": "default-scheduler",
+ "securityContext": {
+ },
+ "serviceAccount": "default",
+ "serviceAccountName": "default",
+ "terminationGracePeriodSeconds": 30,
+ "tolerations": [
+ {
+ "effect": "NoExecute",
+ "key": "node.kubernetes.io/not-ready",
+ "operator": "Exists",
+ "tolerationSeconds": 300
+ },
+ {
+ "effect": "NoExecute",
+ "key": "node.kubernetes.io/unreachable",
+ "operator": "Exists",
+ "tolerationSeconds": 300
+ }],
+ "volumes": [
+ {
+ "name": "default-token-jzq7d",
+ "secret": {
+ "defaultMode": 420,
+ "secretName": "default-token-jzq7d"
+ }
+ }],
+ "enableServiceLinks": true
+ },
+ "status": {
+ "conditions": [
+ {
+ "lastTransitionTime": "2020-06-14T21:50:09Z",
+ "status": "True",
+ "type": "Initialized"
+ },
+ {
+ "lastTransitionTime": "2020-06-14T21:50:10Z",
+ "status": "True",
+ "type": "Ready"
+ },
+ {
+ "lastTransitionTime": "2020-06-14T21:50:10Z",
+ "status": "True",
+ "type": "ContainersReady"
+ },
+ {
+ "lastTransitionTime": "2020-06-14T21:50:09Z",
+ "status": "True",
+ "type": "PodScheduled"
+ }],
+ "containerStatuses": [
+ {
+ "containerID": "docker://818b0098946c67e6ac56cb7c0934b7c2a9f50feb7244b422b2a7f566f7e5d0df",
+ "image": "sampleapp:latest",
+ "imageID":
+ "docker-pullable://localhost:5000/sampleapp@sha256:3cefb2db514db73c69854fee8abd072f27240519432d08aad177a57ee34b7d39",
+ "lastState": {
+ },
+ "name": "sampleapp",
+ "ready": true,
+ "restartCount": 0,
+ "state": {
+ "running": {
+ "startedAt": "2020-06-14T21:50:10Z"
+ }
+ },
+ "started": true
+ }],
+ "hostIP": "192.168.65.3",
+ "phase": "Running",
+ "podIP": "10.1.0.47",
+ "qosClass": "BestEffort",
+ "startTime": "2020-06-14T21:50:09Z",
+ "podIPs": [
+ {
+ "ip": "10.1.0.47"
+ }]
+ }
+}
+
diff --git a/log4j-layout-jackson-json/pom.xml b/log4j-layout-jackson-json/pom.xml
index 6c97168..5d5c175 100644
--- a/log4j-layout-jackson-json/pom.xml
+++ b/log4j-layout-jackson-json/pom.xml
@@ -118,6 +118,7 @@
<!-- module link generation is completely broken in the javadoc plugin for a multi-module non-aggregating project -->
<detectOfflineLinks>false</detectOfflineLinks>
<linksource>true</linksource>
+ <source>8</source>
</configuration>
<reportSets>
<reportSet>
diff --git a/log4j-layout-jackson-json/src/main/java/org/apache/logging/log4j/jackson/json/JsonSetupContextInitializer.java b/log4j-layout-jackson-json/src/main/java/org/apache/logging/log4j/jackson/json/JsonSetupContextInitializer.java
index e7c90a2..7e54ae6 100644
--- a/log4j-layout-jackson-json/src/main/java/org/apache/logging/log4j/jackson/json/JsonSetupContextInitializer.java
+++ b/log4j-layout-jackson-json/src/main/java/org/apache/logging/log4j/jackson/json/JsonSetupContextInitializer.java
@@ -48,4 +48,4 @@
? (stacktraceAsString ? ThrowableProxyWithStacktraceAsStringMixIn.class : ThrowableProxyMixIn.class)
: ThrowableProxyWithoutStacktraceMixIn.class);
}
-}
\ No newline at end of file
+}
diff --git a/log4j-layout-jackson-json/src/main/java/org/apache/logging/log4j/jackson/json/Log4jJsonObjectMapper.java b/log4j-layout-jackson-json/src/main/java/org/apache/logging/log4j/jackson/json/Log4jJsonObjectMapper.java
index f0df03b..f72df75 100644
--- a/log4j-layout-jackson-json/src/main/java/org/apache/logging/log4j/jackson/json/Log4jJsonObjectMapper.java
+++ b/log4j-layout-jackson-json/src/main/java/org/apache/logging/log4j/jackson/json/Log4jJsonObjectMapper.java
@@ -38,6 +38,10 @@
/**
* Create a new instance using the {@link Log4jJsonModule}.
+ * @param encodeThreadContextAsList true if ThreadContext should be rendered as a list.
+ * @param includeStacktrace true if the stack trace should be included.
+ * @param stacktraceAsString true if the stack trace should be printed as a simple string.
+ * @param objectMessageAsJsonObject true if the ObjectMessage should be converted to a JSON object.
*/
public Log4jJsonObjectMapper(final boolean encodeThreadContextAsList, final boolean includeStacktrace, final boolean stacktraceAsString, final boolean objectMessageAsJsonObject) {
this.registerModule(new Log4jJsonModule(encodeThreadContextAsList, includeStacktrace, stacktraceAsString, objectMessageAsJsonObject));
diff --git a/log4j-layout-jackson-json/src/main/java/org/apache/logging/log4j/jackson/json/layout/JsonJacksonFactory.java b/log4j-layout-jackson-json/src/main/java/org/apache/logging/log4j/jackson/json/layout/JsonJacksonFactory.java
index dd7b789..5ba38f9 100644
--- a/log4j-layout-jackson-json/src/main/java/org/apache/logging/log4j/jackson/json/layout/JsonJacksonFactory.java
+++ b/log4j-layout-jackson-json/src/main/java/org/apache/logging/log4j/jackson/json/layout/JsonJacksonFactory.java
@@ -27,6 +27,16 @@
}
@Override
+ protected String getPropertyNameForTimeMillis() {
+ return JsonConstants.ELT_TIME_MILLIS;
+ }
+
+ @Override
+ protected String getPropertyNameForInstant() {
+ return JsonConstants.ELT_INSTANT;
+ }
+
+ @Override
protected String getPropertyNameForNanoTime() {
return JsonConstants.ELT_NANO_TIME;
}
@@ -57,4 +67,4 @@
return new DefaultPrettyPrinter();
}
-}
\ No newline at end of file
+}
diff --git a/log4j-layout-jackson-json/src/main/java/org/apache/logging/log4j/jackson/json/layout/JsonLayout.java b/log4j-layout-jackson-json/src/main/java/org/apache/logging/log4j/jackson/json/layout/JsonLayout.java
index 224cfb4..f2485e4 100644
--- a/log4j-layout-jackson-json/src/main/java/org/apache/logging/log4j/jackson/json/layout/JsonLayout.java
+++ b/log4j-layout-jackson-json/src/main/java/org/apache/logging/log4j/jackson/json/layout/JsonLayout.java
@@ -16,6 +16,24 @@
*/
package org.apache.logging.log4j.jackson.json.layout;
+import com.fasterxml.jackson.annotation.JsonAnyGetter;
+import com.fasterxml.jackson.annotation.JsonRootName;
+import com.fasterxml.jackson.annotation.JsonUnwrapped;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import org.apache.logging.log4j.core.Layout;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.config.Configuration;
+import org.apache.logging.log4j.core.config.DefaultConfiguration;
+import org.apache.logging.log4j.core.layout.PatternLayout;
+import org.apache.logging.log4j.core.util.KeyValuePair;
+import org.apache.logging.log4j.jackson.AbstractJacksonLayout;
+import org.apache.logging.log4j.jackson.XmlConstants;
+import org.apache.logging.log4j.plugins.Node;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginBuilderAttribute;
+import org.apache.logging.log4j.plugins.PluginElement;
+import org.apache.logging.log4j.plugins.PluginFactory;
+
import java.io.IOException;
import java.io.Writer;
import java.nio.charset.Charset;
@@ -23,24 +41,6 @@
import java.util.HashMap;
import java.util.Map;
-import org.apache.logging.log4j.core.Layout;
-import org.apache.logging.log4j.core.LogEvent;
-import org.apache.logging.log4j.core.config.Configuration;
-import org.apache.logging.log4j.core.config.DefaultConfiguration;
-import org.apache.logging.log4j.core.config.Node;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
-import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
-import org.apache.logging.log4j.core.config.plugins.PluginElement;
-import org.apache.logging.log4j.core.layout.PatternLayout;
-import org.apache.logging.log4j.core.util.KeyValuePair;
-import org.apache.logging.log4j.jackson.AbstractJacksonLayout;
-import org.apache.logging.log4j.jackson.XmlConstants;
-
-import com.fasterxml.jackson.annotation.JsonAnyGetter;
-import com.fasterxml.jackson.annotation.JsonRootName;
-import com.fasterxml.jackson.annotation.JsonUnwrapped;
-
/**
* Appends a series of JSON events as strings serialized as bytes.
*
@@ -76,7 +76,7 @@
public final class JsonLayout extends AbstractJacksonLayout {
public static class Builder<B extends Builder<B>> extends AbstractJacksonLayout.Builder<B>
- implements org.apache.logging.log4j.core.util.Builder<JsonLayout> {
+ implements org.apache.logging.log4j.plugins.util.Builder<JsonLayout> {
@PluginBuilderAttribute
private boolean propertiesAsList;
@@ -98,8 +98,8 @@
final String headerPattern = toStringOrNull(getHeader());
final String footerPattern = toStringOrNull(getFooter());
return new JsonLayout(getConfiguration(), isLocationInfo(), isProperties(), encodeThreadContextAsList,
- isComplete(), isCompact(), getEventEol(), headerPattern, footerPattern, getCharset(),
- isIncludeStacktrace(), isStacktraceAsString(), isIncludeNullDelimiter(),
+ isComplete(), isCompact(), getEventEol(), getEndOfLine(), headerPattern, footerPattern, getCharset(),
+ isIncludeStacktrace(), isStacktraceAsString(), isIncludeNullDelimiter(), isIncludeTimeMillis(),
getAdditionalFields(), getObjectMessageAsJsonObject());
}
@@ -136,7 +136,7 @@
@JsonRootName(XmlConstants.ELT_EVENT)
public static class JsonLogEventWithAdditionalFields extends LogEventWithAdditionalFields {
- public JsonLogEventWithAdditionalFields(final Object logEvent, final Map<String, String> additionalFields) {
+ public JsonLogEventWithAdditionalFields(final LogEvent logEvent, final Map<String, String> additionalFields) {
super(logEvent, additionalFields);
}
@@ -148,7 +148,8 @@
@Override
@JsonUnwrapped
- public Object getLogEvent() {
+ @JsonSerialize(as = LogEvent.class)
+ public LogEvent getLogEvent() {
return super.getLogEvent();
}
}
@@ -166,91 +167,25 @@
* @return A JSON Layout.
*/
public static JsonLayout createDefaultLayout() {
- return new JsonLayout(new DefaultConfiguration(), false, false, false, false, false, false,
- DEFAULT_HEADER, DEFAULT_FOOTER, StandardCharsets.UTF_8, true, false, false, null, false);
+ return new JsonLayout(new DefaultConfiguration(), false, false, false, false, false, false, null,
+ DEFAULT_HEADER, DEFAULT_FOOTER, StandardCharsets.UTF_8, true, false, false, false, null, false);
}
- /**
- * Creates a JSON Layout.
- * @param config
- * The plugin configuration.
- * @param locationInfo
- * If "true", includes the location information in the generated JSON.
- * @param properties
- * If "true", includes the thread context map in the generated JSON.
- * @param propertiesAsList
- * If true, the thread context map is included as a list of map entry objects, where each entry has
- * a "key" attribute (whose value is the key) and a "value" attribute (whose value is the value).
- * Defaults to false, in which case the thread context map is included as a simple map of key-value
- * pairs.
- * @param complete
- * If "true", includes the JSON header and footer, and comma between records.
- * @param compact
- * If "true", does not use end-of-lines and indentation, defaults to "false".
- * @param eventEol
- * If "true", forces an EOL after each log event (even if compact is "true"), defaults to "false". This
- * allows one even per line, even in compact mode.
- * @param headerPattern
- * The header pattern, defaults to {@code "["} if null.
- * @param footerPattern
- * The header pattern, defaults to {@code "]"} if null.
- * @param charset
- * The character set to use, if {@code null}, uses "UTF-8".
- * @param includeStacktrace
- * If "true", includes the stacktrace of any Throwable in the generated JSON, defaults to "true".
- * @return A JSON Layout.
- *
- * @deprecated Use {@link #newBuilder()} instead
- */
- @Deprecated
- public static JsonLayout createLayout(
- final Configuration config,
- final boolean locationInfo,
- final boolean properties,
- final boolean propertiesAsList,
- final boolean complete,
- final boolean compact,
- final boolean eventEol,
- final String headerPattern,
- final String footerPattern,
- final Charset charset,
- final boolean includeStacktrace) {
- final boolean encodeThreadContextAsList = properties && propertiesAsList;
- return new JsonLayout(config, locationInfo, properties, encodeThreadContextAsList, complete, compact, eventEol,
- headerPattern, footerPattern, charset, includeStacktrace, false, false, null, false);
- }
-
- @PluginBuilderFactory
+ @PluginFactory
public static <B extends Builder<B>> B newBuilder() {
return new Builder<B>().asBuilder();
}
- /**
- * @deprecated Use {@link #newBuilder()} instead
- */
- @Deprecated
- protected JsonLayout(final Configuration config, final boolean locationInfo, final boolean properties,
- final boolean encodeThreadContextAsList,
- final boolean complete, final boolean compact, final boolean eventEol, final String headerPattern,
- final String footerPattern, final Charset charset, final boolean includeStacktrace) {
- super(config, new JsonJacksonFactory(encodeThreadContextAsList, includeStacktrace, false, false).newWriter(
- locationInfo, properties, compact),
- charset, compact, complete, eventEol,
- PatternLayout.newSerializerBuilder().setConfiguration(config).setPattern(headerPattern).setDefaultPattern(DEFAULT_HEADER).build(),
- PatternLayout.newSerializerBuilder().setConfiguration(config).setPattern(footerPattern).setDefaultPattern(DEFAULT_FOOTER).build(),
- false, null);
- }
-
private JsonLayout(final Configuration config, final boolean locationInfo, final boolean properties,
final boolean encodeThreadContextAsList,
- final boolean complete, final boolean compact, final boolean eventEol,
+ final boolean complete, final boolean compact, final boolean eventEol, final String endOfLine,
final String headerPattern, final String footerPattern, final Charset charset,
final boolean includeStacktrace, final boolean stacktraceAsString,
- final boolean includeNullDelimiter,
+ final boolean includeNullDelimiter, final boolean includeTimeMillis,
final KeyValuePair[] additionalFields, final boolean objectMessageAsJsonObject) {
super(config, new JsonJacksonFactory(encodeThreadContextAsList, includeStacktrace, stacktraceAsString, objectMessageAsJsonObject).newWriter(
- locationInfo, properties, compact),
- charset, compact, complete, eventEol,
+ locationInfo, properties, compact, includeTimeMillis),
+ charset, compact, complete, eventEol, endOfLine,
PatternLayout.newSerializerBuilder().setConfiguration(config).setPattern(headerPattern).setDefaultPattern(DEFAULT_HEADER).build(),
PatternLayout.newSerializerBuilder().setConfiguration(config).setPattern(footerPattern).setDefaultPattern(DEFAULT_FOOTER).build(),
includeNullDelimiter,
diff --git a/log4j-layout-jackson-json/src/site/manual/index.md b/log4j-layout-jackson-json/src/site/manual/index.md
index 6cadb78..6459110 100644
--- a/log4j-layout-jackson-json/src/site/manual/index.md
+++ b/log4j-layout-jackson-json/src/site/manual/index.md
@@ -18,7 +18,7 @@
# Apache Log4j Layout for Jackson JSON module
-As of Log4j 3.0.0, the layout based on Jackson JSON has moved from the existing module logj-core to the new modules log4j-layout-jackson-json.
+As of Log4j 3.0.0, the layout based on Jackson JSON has moved from the existing module log4j-core to the new modules log4j-layout-jackson-json.
## Requirements
diff --git a/log4j-layout-jackson-json/src/test/java/org/apache/logging/log4j/jackson/json/layout/ConcurrentLoggingWithJsonLayoutTest.java b/log4j-layout-jackson-json/src/test/java/org/apache/logging/log4j/jackson/json/layout/ConcurrentLoggingWithJsonLayoutTest.java
index 828c80b..bad6b2e 100644
--- a/log4j-layout-jackson-json/src/test/java/org/apache/logging/log4j/jackson/json/layout/ConcurrentLoggingWithJsonLayoutTest.java
+++ b/log4j-layout-jackson-json/src/test/java/org/apache/logging/log4j/jackson/json/layout/ConcurrentLoggingWithJsonLayoutTest.java
@@ -16,9 +16,7 @@
*/
package org.apache.logging.log4j.jackson.json.layout;
-import static org.hamcrest.CoreMatchers.endsWith;
-import static org.hamcrest.CoreMatchers.equalTo;
-import static org.hamcrest.CoreMatchers.startsWith;
+import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.assertThat;
import java.io.File;
@@ -108,7 +106,7 @@
final List<String> lines = Files.readAllLines(new File(PATH).toPath(), Charset.defaultCharset());
for (final String line : lines) {
assertThat(line, startsWith("{\"instant\":{\"epochSecond\":"));
- assertThat(line, endsWith("\"threadPriority\":5}"));
+ assertThat(line, containsString("\"threadPriority\":5"));
}
}
}
diff --git a/log4j-layout-jackson-json/src/test/java/org/apache/logging/log4j/jackson/json/layout/JsonLayoutMillisTest.java b/log4j-layout-jackson-json/src/test/java/org/apache/logging/log4j/jackson/json/layout/JsonLayoutMillisTest.java
new file mode 100644
index 0000000..2ec062d
--- /dev/null
+++ b/log4j-layout-jackson-json/src/test/java/org/apache/logging/log4j/jackson/json/layout/JsonLayoutMillisTest.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.jackson.json.layout;
+
+import org.apache.logging.log4j.categories.Layouts;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.Logger;
+import org.apache.logging.log4j.junit.LoggerContextRule;
+import org.apache.logging.log4j.test.appender.ListAppender;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * 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<LogEvent> 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");
+ List<String> message = app.getMessages();
+ assertTrue("No messages", message != null && message.size() > 0);
+ 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-layout-jackson-json/src/test/java/org/apache/logging/log4j/jackson/json/layout/JsonLayoutTest.java b/log4j-layout-jackson-json/src/test/java/org/apache/logging/log4j/jackson/json/layout/JsonLayoutTest.java
index d477d23..2b08e13 100644
--- a/log4j-layout-jackson-json/src/test/java/org/apache/logging/log4j/jackson/json/layout/JsonLayoutTest.java
+++ b/log4j-layout-jackson-json/src/test/java/org/apache/logging/log4j/jackson/json/layout/JsonLayoutTest.java
@@ -199,8 +199,31 @@
assertTrue(str, str.contains("\"KEY2\":\"" + new JavaLookup().getRuntime() + "\""));
}
+ @Test
+ public void testMutableLogEvent() throws Exception {
+ final AbstractJacksonLayout layout = JsonLayout.newBuilder()
+ .setLocationInfo(false)
+ .setProperties(false)
+ .setComplete(false)
+ .setCompact(true)
+ .setEventEol(false)
+ .setIncludeStacktrace(false)
+ .setAdditionalFields(new KeyValuePair[] {
+ new KeyValuePair("KEY1", "VALUE1"),
+ new KeyValuePair("KEY2", "${java:runtime}"), })
+ .setCharset(StandardCharsets.UTF_8)
+ .setConfiguration(ctx.getConfiguration())
+ .build();
+ Log4jLogEvent logEvent = LogEventFixtures.createLogEvent();
+ final MutableLogEvent mutableEvent = new MutableLogEvent();
+ mutableEvent.initFrom(logEvent);
+ final String strLogEvent = layout.toSerializable(logEvent);
+ final String strMutableEvent = layout.toSerializable(mutableEvent);
+ assertEquals(strMutableEvent, strLogEvent, strMutableEvent);
+ }
+
private void testAllFeatures(final boolean locationInfo, final boolean compact, final boolean eventEol,
- final boolean includeContext, final boolean contextMapAslist, final boolean includeStacktrace)
+ final String endOfLine, final boolean includeContext, final boolean contextMapAslist, final boolean includeStacktrace)
throws Exception {
final Log4jLogEvent expected = LogEventFixtures.createLogEvent();
// @formatter:off
@@ -211,14 +234,21 @@
.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);
- // Just check for \n since \r might or might not be there.
- assertEquals(str, !compact || eventEol, str.contains("\n"));
+ if (endOfLine == null) {
+ // Just check for \n since \r might or might not be there.
+ assertEquals(str, !compact || eventEol, str.contains("\n"));
+ }
+ else {
+ assertEquals(str, !compact || eventEol, str.contains(endOfLine));
+ assertEquals(str, compact && eventEol, str.endsWith(endOfLine));
+ }
assertEquals(str, locationInfo, str.contains("source"));
assertEquals(str, includeContext, str.contains("contextMap"));
final Log4jLogEvent actual = new Log4jJsonObjectMapper(contextMapAslist, includeStacktrace, false, false).readValue(str, Log4jLogEvent.class);
@@ -332,7 +362,12 @@
@Test
public void testExcludeStacktrace() throws Exception {
- this.testAllFeatures(false, false, false, false, false, false);
+ this.testAllFeatures(false, false, false, null, false, false, false);
+ }
+
+ @Test
+ public void testLocationOnCustomEndOfLine() throws Exception {
+ this.testAllFeatures(true, true, true, "CUSTOM_END_OF_LINE", true, false, true);
}
@Test
@@ -552,22 +587,22 @@
@Test
public void testLocationOffCompactOffMdcOff() throws Exception {
- this.testAllFeatures(false, false, false, false, false, true);
+ this.testAllFeatures(false, false, false, null, false, false, true);
}
@Test
public void testLocationOnCompactOnEventEolOnMdcOn() throws Exception {
- this.testAllFeatures(true, true, true, true, false, true);
+ this.testAllFeatures(true, true, true, null, true, false, true);
}
@Test
public void testLocationOnCompactOnEventEolOnMdcOnMdcAsList() throws Exception {
- this.testAllFeatures(true, true, true, true, true, true);
+ this.testAllFeatures(true, true, true, null, true, true, true);
}
@Test
public void testLocationOnCompactOnMdcOn() throws Exception {
- this.testAllFeatures(true, true, false, true, false, true);
+ this.testAllFeatures(true, true, false, null, true, false, true);
}
@Test
@@ -597,4 +632,18 @@
private String toPropertySeparator(final boolean compact) {
return compact ? ":" : " : ";
}
+
+ @Test // LOG4J2-2749 (#362)
+ public 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, str.contains("\"empty\""));
+ }
+
}
diff --git a/log4j-layout-jackson-json/src/test/resources/log4j2-json-layout-timestamp.xml b/log4j-layout-jackson-json/src/test/resources/log4j2-json-layout-timestamp.xml
new file mode 100644
index 0000000..7e199d1
--- /dev/null
+++ b/log4j-layout-jackson-json/src/test/resources/log4j2-json-layout-timestamp.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-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 "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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="INFO" packages="">
+ <Appenders>
+ <List name="List">
+ <JsonLayout compact="true" eventEol="true" includeTimeMillis="true"/>
+ </List>
+ </Appenders>
+
+ <Loggers>
+ <Root level="INFO">
+ <AppenderRef ref="List"/>
+ </Root>
+ </Loggers>
+</Configuration>
diff --git a/log4j-layout-jackson-xml/java/org/apache/logging/log4j/jackson/xml/Log4jXmlModule.java b/log4j-layout-jackson-xml/java/org/apache/logging/log4j/jackson/xml/Log4jXmlModule.java
deleted file mode 100644
index 0980f94..0000000
--- a/log4j-layout-jackson-xml/java/org/apache/logging/log4j/jackson/xml/Log4jXmlModule.java
+++ /dev/null
@@ -1,49 +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.jackson.xml;
-
-import org.apache.logging.log4j.core.jackson.SimpleModuleInitializer;
-
-import com.fasterxml.jackson.dataformat.xml.JacksonXmlModule;
-
-/**
- * <p>
- * <em>Consider this class private.</em>
- * </p>
- */
-final class Log4jXmlModule extends JacksonXmlModule {
-
- private static final long serialVersionUID = 1L;
- private final boolean includeStacktrace;
- private final boolean stacktraceAsString;
-
- Log4jXmlModule(final boolean includeStacktrace, final boolean stacktraceAsString) {
- super();
- this.includeStacktrace = includeStacktrace;
- this.stacktraceAsString = stacktraceAsString;
- // MUST init here.
- // Calling this from setupModule is too late!
- new SimpleModuleInitializer().initialize(this, false);
- }
-
- @Override
- public void setupModule(final SetupContext context) {
- // Calling super is a MUST!
- super.setupModule(context);
- new XmlSetupContextInitializer().setupModule(context, includeStacktrace, stacktraceAsString);
- }
-}
diff --git a/log4j-layout-jackson-xml/java/org/apache/logging/log4j/jackson/xml/Log4jXmlObjectMapper.java b/log4j-layout-jackson-xml/java/org/apache/logging/log4j/jackson/xml/Log4jXmlObjectMapper.java
deleted file mode 100644
index 632b83f..0000000
--- a/log4j-layout-jackson-xml/java/org/apache/logging/log4j/jackson/xml/Log4jXmlObjectMapper.java
+++ /dev/null
@@ -1,48 +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.jackson.xml;
-
-import com.fasterxml.jackson.annotation.JsonInclude;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.fasterxml.jackson.dataformat.xml.XmlMapper;
-
-/**
- * A Jackson XML {@link ObjectMapper} initialized for Log4j.
- * <p>
- * <em>Consider this class private.</em>
- * </p>
- */
-public class Log4jXmlObjectMapper extends XmlMapper {
-
- private static final long serialVersionUID = 1L;
-
- /**
- * Create a new instance using the {@link Log4jXmlModule}.
- */
- public Log4jXmlObjectMapper() {
- this(true, false);
- }
-
- /**
- * Create a new instance using the {@link Log4jXmlModule}.
- */
- public Log4jXmlObjectMapper(final boolean includeStacktrace, final boolean stacktraceAsString) {
- super(new Log4jXmlModule(includeStacktrace, stacktraceAsString));
- this.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
- }
-
-}
diff --git a/log4j-layout-jackson-xml/java/org/apache/logging/log4j/jackson/xml/XmlInstantMixIn.java b/log4j-layout-jackson-xml/java/org/apache/logging/log4j/jackson/xml/XmlInstantMixIn.java
deleted file mode 100644
index 0e18154..0000000
--- a/log4j-layout-jackson-xml/java/org/apache/logging/log4j/jackson/xml/XmlInstantMixIn.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.jackson.xml;
-
-import org.apache.logging.log4j.Marker;
-import org.apache.logging.log4j.core.jackson.InstantMixIn;
-import org.apache.logging.log4j.core.time.Instant;
-
-import com.fasterxml.jackson.annotation.JsonCreator;
-import com.fasterxml.jackson.annotation.JsonProperty;
-import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
-
-/**
- * Jackson mix-in for {@link Instant}.
- * <p>
- * <em>Consider this class private.</em>
- * </p>
- *
- * @see Marker
- */
-abstract class XmlInstantMixIn extends InstantMixIn {
-
- @JsonCreator
- protected XmlInstantMixIn(
- // @formatter:off
- @JsonProperty(ATTR_EPOCH_SECOND) final long epochSecond,
- @JsonProperty(ATTR_NANO_OF_SECOND) final int nanoOfSecond)
- // @formatter:on
- {
- super(epochSecond, nanoOfSecond);
- }
-
- @Override
- @JacksonXmlProperty(localName = ATTR_EPOCH_SECOND, isAttribute = true)
- protected abstract long getEpochSecond();
-
- @Override
- @JacksonXmlProperty(localName = ATTR_NANO_OF_SECOND, isAttribute = true)
- protected abstract int getNanoOfSecond();
-
-}
diff --git a/log4j-layout-jackson-xml/java/org/apache/logging/log4j/jackson/xml/XmlLogEventWithContextListMixIn.java b/log4j-layout-jackson-xml/java/org/apache/logging/log4j/jackson/xml/XmlLogEventWithContextListMixIn.java
deleted file mode 100644
index 771f955..0000000
--- a/log4j-layout-jackson-xml/java/org/apache/logging/log4j/jackson/xml/XmlLogEventWithContextListMixIn.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.jackson.xml;
-
-import org.apache.logging.log4j.core.jackson.AbstractXmlLogEventMixIn;
-import org.apache.logging.log4j.core.jackson.ContextDataAsEntryListDeserializer;
-import org.apache.logging.log4j.core.jackson.ContextDataAsEntryListSerializer;
-import org.apache.logging.log4j.core.jackson.XmlConstants;
-import org.apache.logging.log4j.util.ReadOnlyStringMap;
-
-import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
-import com.fasterxml.jackson.databind.annotation.JsonSerialize;
-import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
-
-/**
- * <pre>
- * AbstractLogEventMixIn
-*├─ XmlLogEventMixIn
-*├──── XmlLogEventWithContextListMixIn
-*├──── XmlLogEventWithContextMapMixIn
-*├─ JsonLogEventMixIn
-*├──── JsonLogEventWithContextListMixIn
-*├──── JsonLogEventWithContextMapMixIn
- * </pre>
- */
-public abstract class XmlLogEventWithContextListMixIn extends AbstractXmlLogEventMixIn {
-
- private static final long serialVersionUID = 1L;
-
- @JacksonXmlProperty(namespace = XmlConstants.XML_NAMESPACE, localName = XmlConstants.ELT_CONTEXT_MAP)
- @JsonSerialize(using = ContextDataAsEntryListSerializer.class)
- @JsonDeserialize(using = ContextDataAsEntryListDeserializer.class)
- @Override
- public abstract ReadOnlyStringMap getContextData();
-
-}
diff --git a/log4j-layout-jackson-xml/java/org/apache/logging/log4j/jackson/xml/XmlMarkerMixIn.java b/log4j-layout-jackson-xml/java/org/apache/logging/log4j/jackson/xml/XmlMarkerMixIn.java
deleted file mode 100644
index 9fa0e8a..0000000
--- a/log4j-layout-jackson-xml/java/org/apache/logging/log4j/jackson/xml/XmlMarkerMixIn.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.jackson.xml;
-
-import org.apache.logging.log4j.Marker;
-import org.apache.logging.log4j.core.jackson.MarkerMixIn;
-import org.apache.logging.log4j.core.jackson.XmlConstants;
-
-import com.fasterxml.jackson.annotation.JsonCreator;
-import com.fasterxml.jackson.annotation.JsonProperty;
-import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
-import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlElementWrapper;
-import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
-
-/**
- * Jackson mix-in for {@link Marker}.
- * <p>
- * If we want to deal with more than one {@link Marker} implementation then recode these annotations to include
- * metadata.
- * </p>
- * <p>
- * <em>Consider this class private.</em>
- * </p>
- * <p>
- * Example XML:
- * </p>
- *
- * <pre>
-<Marker name="Marker1">
- <Parents>
- <Marker name="ParentMarker1">
- <Parents>
- <Marker name="GrandMotherMarker"/>
- <Marker name="GrandFatherMarker"/>
- </Parents>
- </Marker>
- <Marker name="ParentMarker2"/>
- </Parents>
-</Marker>
- * </pre>
- *
- * @see Marker
- */
-// Alternate for multiple Marker implementation.
-// @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, include = JsonTypeInfo.As.PROPERTY, property = "@class")
-@JsonDeserialize(as = org.apache.logging.log4j.MarkerManager.Log4jMarker.class)
-abstract class XmlMarkerMixIn extends MarkerMixIn {
- public static final String ATTR_NAME = "name";
- private static final long serialVersionUID = 1L;
-
- @JsonCreator
- protected XmlMarkerMixIn(@JsonProperty(ATTR_NAME) final String name) {
- super(name);
- }
-
- @Override
- @JacksonXmlProperty(isAttribute = true)
- public abstract String getName();
-
- @Override
- @JacksonXmlElementWrapper(namespace = XmlConstants.XML_NAMESPACE, localName = XmlConstants.ELT_PARENTS)
- @JacksonXmlProperty(namespace = XmlConstants.XML_NAMESPACE, localName = XmlConstants.ELT_MARKER)
- public abstract Marker[] getParents();
-
-}
diff --git a/log4j-layout-jackson-xml/java/org/apache/logging/log4j/jackson/xml/XmlSetupContextInitializer.java b/log4j-layout-jackson-xml/java/org/apache/logging/log4j/jackson/xml/XmlSetupContextInitializer.java
deleted file mode 100644
index 442f9ab..0000000
--- a/log4j-layout-jackson-xml/java/org/apache/logging/log4j/jackson/xml/XmlSetupContextInitializer.java
+++ /dev/null
@@ -1,42 +0,0 @@
-package org.apache.logging.log4j.jackson.xml;
-
-import org.apache.logging.log4j.Level;
-import org.apache.logging.log4j.Marker;
-import org.apache.logging.log4j.core.LogEvent;
-import org.apache.logging.log4j.core.impl.ExtendedStackTraceElement;
-import org.apache.logging.log4j.core.impl.ThrowableProxy;
-import org.apache.logging.log4j.core.jackson.ExtendedStackTraceElementMixIn;
-import org.apache.logging.log4j.core.jackson.LevelMixIn;
-import org.apache.logging.log4j.core.jackson.ThrowableProxyMixIn;
-import org.apache.logging.log4j.core.jackson.ThrowableProxyWithStacktraceAsStringMixIn;
-import org.apache.logging.log4j.core.jackson.ThrowableProxyWithoutStacktraceMixIn;
-import org.apache.logging.log4j.core.time.Instant;
-
-import com.fasterxml.jackson.databind.Module.SetupContext;
-import com.fasterxml.jackson.databind.module.SimpleModule;
-
-/**
- * Used to set up {@link SetupContext} from different {@link SimpleModule}s.
- * <p>
- * <em>Consider this class private.</em>
- * </p>
- */
-class XmlSetupContextInitializer {
-
- public void setupModule(final SetupContext context, final boolean includeStacktrace,
- final boolean stacktraceAsString) {
- // JRE classes: we cannot edit those with Jackson annotations
- context.setMixInAnnotations(StackTraceElement.class, XmlStackTraceElementMixIn.class);
- // Log4j API classes: we do not want to edit those with Jackson annotations because the API module should not
- // depend on Jackson.
- context.setMixInAnnotations(Marker.class, XmlMarkerMixIn.class);
- context.setMixInAnnotations(Level.class, LevelMixIn.class);
- context.setMixInAnnotations(Instant.class, XmlInstantMixIn.class);
- context.setMixInAnnotations(LogEvent.class, XmlLogEventWithContextListMixIn.class);
- // Log4j Core classes: we do not want to bring in Jackson at runtime if we do not have to.
- context.setMixInAnnotations(ExtendedStackTraceElement.class, ExtendedStackTraceElementMixIn.class);
- context.setMixInAnnotations(ThrowableProxy.class, includeStacktrace
- ? (stacktraceAsString ? ThrowableProxyWithStacktraceAsStringMixIn.class : ThrowableProxyMixIn.class)
- : ThrowableProxyWithoutStacktraceMixIn.class);
- }
-}
\ No newline at end of file
diff --git a/log4j-layout-jackson-xml/java/org/apache/logging/log4j/jackson/xml/XmlStackTraceElementMixIn.java b/log4j-layout-jackson-xml/java/org/apache/logging/log4j/jackson/xml/XmlStackTraceElementMixIn.java
deleted file mode 100644
index abdba98..0000000
--- a/log4j-layout-jackson-xml/java/org/apache/logging/log4j/jackson/xml/XmlStackTraceElementMixIn.java
+++ /dev/null
@@ -1,39 +0,0 @@
-package org.apache.logging.log4j.jackson.xml;
-
-import org.apache.logging.log4j.core.jackson.StackTraceElementMixIn;
-
-import com.fasterxml.jackson.annotation.JsonCreator;
-import com.fasterxml.jackson.annotation.JsonProperty;
-import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlProperty;
-
-abstract class XmlStackTraceElementMixIn extends StackTraceElementMixIn {
-
- @JsonCreator
- protected XmlStackTraceElementMixIn(
- // @formatter:off
- @JsonProperty(ATTR_CLASS) final String declaringClass,
- @JsonProperty(ATTR_METHOD) final String methodName,
- @JsonProperty(ATTR_FILE) final String fileName,
- @JsonProperty(ATTR_LINE) final int lineNumber)
- // @formatter:on
- {
- super(declaringClass, methodName, fileName, lineNumber);
- }
-
- @Override
- @JacksonXmlProperty(localName = ATTR_CLASS, isAttribute = true)
- protected abstract String getClassName();
-
- @Override
- @JacksonXmlProperty(localName = ATTR_FILE, isAttribute = true)
- protected abstract String getFileName();
-
- @Override
- @JacksonXmlProperty(localName = ATTR_LINE, isAttribute = true)
- protected abstract int getLineNumber();
-
- @Override
- @JacksonXmlProperty(localName = ATTR_METHOD, isAttribute = true)
- protected abstract String getMethodName();
-
-}
diff --git a/log4j-layout-jackson-xml/java/org/apache/logging/log4j/jackson/xml/layout/Log4jXmlPrettyPrinter.java b/log4j-layout-jackson-xml/java/org/apache/logging/log4j/jackson/xml/layout/Log4jXmlPrettyPrinter.java
deleted file mode 100644
index 24c5ef0..0000000
--- a/log4j-layout-jackson-xml/java/org/apache/logging/log4j/jackson/xml/layout/Log4jXmlPrettyPrinter.java
+++ /dev/null
@@ -1,41 +0,0 @@
-package org.apache.logging.log4j.jackson.xml.layout;
-
-import javax.xml.stream.XMLStreamException;
-
-import org.codehaus.stax2.XMLStreamWriter2;
-
-import com.fasterxml.jackson.dataformat.xml.util.DefaultXmlPrettyPrinter;
-
-/**
- * When <Event>s are written into a XML file; the "Event" object is not the root element, but an element named
- * <Events> created using {@link XmlLayout#getHeader()} and {@link XmlLayout#getFooter()} methods.
- * <p>
- * {@link com.fasterxml.jackson.dataformat.xml.util.DefaultXmlPrettyPrinter} is used to print the Event object into
- * XML; hence it assumes <Event> tag as the root element, so it prints the <Event> tag without any
- * indentation. To add an indentation to the <Event> tag; hence an additional indentation for any
- * sub-elements, this class is written. As an additional task, to avoid the blank line printed after the ending
- * </Event> tag, {@link #writePrologLinefeed(XMLStreamWriter2)} method is also overridden.
- * </p>
- */
-class Log4jXmlPrettyPrinter extends DefaultXmlPrettyPrinter {
-
- private static final long serialVersionUID = 1L;
-
- Log4jXmlPrettyPrinter(final int nesting) {
- _nesting = nesting;
- }
-
- /**
- * Sets the nesting level to 1 rather than 0, so the "Event" tag will get indentation of next level below root.
- */
- @Override
- public DefaultXmlPrettyPrinter createInstance() {
- return new Log4jXmlPrettyPrinter(XmlJacksonFactory.DEFAULT_INDENT);
- }
-
- @Override
- public void writePrologLinefeed(final XMLStreamWriter2 sw) throws XMLStreamException {
- // nothing
- }
-
-}
\ No newline at end of file
diff --git a/log4j-layout-jackson-xml/java/org/apache/logging/log4j/jackson/xml/layout/XmlJacksonFactory.java b/log4j-layout-jackson-xml/java/org/apache/logging/log4j/jackson/xml/layout/XmlJacksonFactory.java
deleted file mode 100644
index 0289c71..0000000
--- a/log4j-layout-jackson-xml/java/org/apache/logging/log4j/jackson/xml/layout/XmlJacksonFactory.java
+++ /dev/null
@@ -1,54 +0,0 @@
-package org.apache.logging.log4j.jackson.xml.layout;
-
-import org.apache.logging.log4j.core.jackson.AbstractJacksonFactory;
-import org.apache.logging.log4j.core.jackson.JsonConstants;
-import org.apache.logging.log4j.core.jackson.XmlConstants;
-import org.apache.logging.log4j.jackson.xml.Log4jXmlObjectMapper;
-
-import com.fasterxml.jackson.core.PrettyPrinter;
-import com.fasterxml.jackson.databind.ObjectMapper;
-
-class XmlJacksonFactory extends AbstractJacksonFactory {
-
- static final int DEFAULT_INDENT = 1;
-
- public XmlJacksonFactory(final boolean includeStacktrace, final boolean stacktraceAsString) {
- super(includeStacktrace, stacktraceAsString);
- }
-
- @Override
- protected String getPropertyNameForContextMap() {
- return XmlConstants.ELT_CONTEXT_MAP;
- }
-
- @Override
- protected String getPropertyNameForNanoTime() {
- return JsonConstants.ELT_NANO_TIME;
- }
-
- @Override
- protected String getPropertyNameForSource() {
- return XmlConstants.ELT_SOURCE;
- }
-
- @Override
- protected String getPropertyNameForStackTrace() {
- return XmlConstants.ELT_EXTENDED_STACK_TRACE;
- }
-
- @Override
- protected PrettyPrinter newCompactPrinter() {
- // Yes, null is the proper answer.
- return null;
- }
-
- @Override
- protected ObjectMapper newObjectMapper() {
- return new Log4jXmlObjectMapper(includeStacktrace, stacktraceAsString);
- }
-
- @Override
- protected PrettyPrinter newPrettyPrinter() {
- return new Log4jXmlPrettyPrinter(DEFAULT_INDENT);
- }
-}
\ No newline at end of file
diff --git a/log4j-layout-jackson-xml/java/org/apache/logging/log4j/jackson/xml/layout/XmlLayout.java b/log4j-layout-jackson-xml/java/org/apache/logging/log4j/jackson/xml/layout/XmlLayout.java
deleted file mode 100644
index c305c33..0000000
--- a/log4j-layout-jackson-xml/java/org/apache/logging/log4j/jackson/xml/layout/XmlLayout.java
+++ /dev/null
@@ -1,226 +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.jackson.xml.layout;
-
-import java.nio.charset.Charset;
-import java.nio.charset.StandardCharsets;
-import java.util.HashMap;
-import java.util.Map;
-
-import org.apache.logging.log4j.core.Layout;
-import org.apache.logging.log4j.core.LogEvent;
-import org.apache.logging.log4j.core.config.Configuration;
-import org.apache.logging.log4j.core.config.Node;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
-import org.apache.logging.log4j.core.jackson.AbstractJacksonLayout;
-import org.apache.logging.log4j.core.jackson.XmlConstants;
-import org.apache.logging.log4j.core.util.KeyValuePair;
-
-import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
-
-/**
- * Appends a series of {@code event} elements as defined in the <a href="log4j.dtd">log4j.dtd</a>.
- *
- * <h3>Complete well-formed XML vs. fragment XML</h3>
- * <p>
- * If you configure {@code complete="true"}, the appender outputs a well-formed XML document where the default namespace
- * is the log4j namespace {@value XmlConstants#XML_NAMESPACE}. By default, with {@code complete="false"}, you should
- * include the output as an <em>external entity</em> in a separate file to form a well-formed XML document.
- * </p>
- * <p>
- * If {@code complete="false"}, the appender does not write the XML processing instruction and the root element.
- * </p>
- * <h3>Encoding</h3>
- * <p>
- * Appenders using this layout should have their {@code charset} set to {@code UTF-8} or {@code UTF-16}, otherwise
- * events containing non-ASCII characters could result in corrupted log files.
- * </p>
- * <h3>Pretty vs. compact XML</h3>
- * <p>
- * By default, the XML layout is not compact (compact = not "pretty") with {@code compact="false"}, which means the
- * appender uses end-of-line characters and indents lines to format the XML. If {@code compact="true"}, then no
- * end-of-line or indentation is used. Message content may contain, of course, end-of-lines.
- * </p>
- * <h3>Additional Fields</h3>
- * <p>
- * This property allows addition of custom fields into generated JSON.
- * {@code <XmlLayout><KeyValuePair key="foo" value="bar"/></XmlLayout>} inserts {@code <foo>bar</foo>} directly into XML
- * output. Supports Lookup expressions.
- * </p>
- */
-@Plugin(name = "XmlLayout", category = Node.CATEGORY, elementType = Layout.ELEMENT_TYPE, printObject = true)
-public final class XmlLayout extends AbstractJacksonLayout {
-
- public static class Builder<B extends Builder<B>> extends AbstractJacksonLayout.Builder<B>
- implements org.apache.logging.log4j.core.util.Builder<XmlLayout> {
-
- public Builder() {
- super();
- setCharset(StandardCharsets.UTF_8);
- }
-
- @Override
- public XmlLayout build() {
- return new XmlLayout(getConfiguration(), isLocationInfo(), isProperties(), isComplete(), isCompact(),
- getCharset(), isIncludeStacktrace(), isStacktraceAsString(), isIncludeNullDelimiter(),
- getAdditionalFields());
- }
- }
-
- @JacksonXmlRootElement(namespace = XmlConstants.XML_NAMESPACE, localName = XmlConstants.ELT_EVENT)
- public static class XmlLogEventWithAdditionalFields extends LogEventWithAdditionalFields {
-
- public XmlLogEventWithAdditionalFields(final Object logEvent, final Map<String, String> additionalFields) {
- super(logEvent, additionalFields);
- }
-
- }
-
- private static final String ROOT_TAG = "Events";
-
- /**
- * Creates an XML Layout using the default settings.
- *
- * @return an XML Layout.
- */
- public static XmlLayout createDefaultLayout() {
- return new XmlLayout(null, false, false, false, false, StandardCharsets.UTF_8, true, false, false, null);
- }
-
- /**
- * Creates an XML Layout.
- *
- * @param locationInfo
- * If "true", includes the location information in the generated XML.
- * @param properties
- * If "true", includes the thread context map in the generated XML.
- * @param complete
- * If "true", includes the XML header and footer, defaults to "false".
- * @param compact
- * If "true", does not use end-of-lines and indentation, defaults to "false".
- * @param charset
- * The character set to use, if {@code null}, uses "UTF-8".
- * @param includeStacktrace
- * If "true", includes the stacktrace of any Throwable in the generated XML, defaults to "true".
- * @return An XML Layout.
- *
- * @deprecated Use {@link #newBuilder()} instead
- */
- @Deprecated
- public static XmlLayout createLayout(final boolean locationInfo, final boolean properties, final boolean complete,
- final boolean compact, final Charset charset, final boolean includeStacktrace) {
- return new XmlLayout(null, locationInfo, properties, complete, compact, charset, includeStacktrace, false,
- false, null);
- }
-
- @PluginBuilderFactory
- public static <B extends Builder<B>> B newBuilder() {
- return new Builder<B>().asBuilder();
- }
-
- /**
- * @deprecated Use {@link #newBuilder()} instead
- */
- @Deprecated
- protected XmlLayout(final boolean locationInfo, final boolean properties, final boolean complete,
- final boolean compact, final Charset charset, final boolean includeStacktrace) {
- this(null, locationInfo, properties, complete, compact, charset, includeStacktrace, false, false, null);
- }
-
- private XmlLayout(final Configuration config, final boolean locationInfo, final boolean properties,
- final boolean complete, final boolean compact, final Charset charset, final boolean includeStacktrace,
- final boolean stacktraceAsString, final boolean includeNullDelimiter,
- final KeyValuePair[] additionalFields) {
- super(config,
- new XmlJacksonFactory(includeStacktrace, stacktraceAsString).newWriter(locationInfo, properties,
- compact),
- charset, compact, complete, false, null, null, includeNullDelimiter, additionalFields);
- }
-
- @Override
- protected LogEventWithAdditionalFields createLogEventWithAdditionalFields(final LogEvent event,
- final Map<String, String> additionalFieldsMap) {
- return new XmlLogEventWithAdditionalFields(event, additionalFieldsMap);
- }
-
- /**
- * Gets this XmlLayout's content format. Specified by:
- * <ul>
- * <li>Key: "dtd" Value: "log4j-events.dtd"</li>
- * <li>Key: "version" Value: "2.0"</li>
- * </ul>
- *
- * @return Map of content format keys supporting XmlLayout
- */
- @Override
- public Map<String, String> getContentFormat() {
- final Map<String, String> result = new HashMap<>();
- // result.put("dtd", "log4j-events.dtd");
- result.put("xsd", "log4j-events.xsd");
- result.put("version", "2.0");
- return result;
- }
-
- /**
- * @return The content type.
- */
- @Override
- public String getContentType() {
- return "text/xml; charset=" + this.getCharset();
- }
-
- /**
- * Returns appropriate XML footer.
- *
- * @return a byte array containing the footer, closing the XML root element.
- */
- @Override
- public byte[] getFooter() {
- if (!complete) {
- return null;
- }
- return getBytes("</" + ROOT_TAG + '>' + this.eol);
- }
-
- /**
- * Returns appropriate XML headers.
- * <ol>
- * <li>XML processing instruction</li>
- * <li>XML root element</li>
- * </ol>
- *
- * @return a byte array containing the header.
- */
- @Override
- public byte[] getHeader() {
- if (!complete) {
- return null;
- }
- final StringBuilder buf = new StringBuilder();
- buf.append("<?xml version=\"1.0\" encoding=\"");
- buf.append(this.getCharset().name());
- buf.append("\"?>");
- buf.append(this.eol);
- // Make the log4j namespace the default namespace, no need to use more space with a namespace prefix.
- buf.append('<');
- buf.append(ROOT_TAG);
- buf.append(" xmlns=\"" + XmlConstants.XML_NAMESPACE + "\">");
- buf.append(this.eol);
- return buf.toString().getBytes(this.getCharset());
- }
-}
diff --git a/log4j-layout-jackson-xml/java/org/apache/logging/log4j/jackson/xml/parser/XmlLogEventParser.java b/log4j-layout-jackson-xml/java/org/apache/logging/log4j/jackson/xml/parser/XmlLogEventParser.java
deleted file mode 100644
index 32776a2..0000000
--- a/log4j-layout-jackson-xml/java/org/apache/logging/log4j/jackson/xml/parser/XmlLogEventParser.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.jackson.xml.parser;
-
-import org.apache.logging.log4j.core.LogEvent;
-import org.apache.logging.log4j.core.parser.AbstractJacksonLogEventParser;
-import org.apache.logging.log4j.jackson.xml.Log4jXmlObjectMapper;
-
-/**
- * Parses the output from XmlLayout layout into instances of {@link LogEvent}.
- */
-public class XmlLogEventParser extends AbstractJacksonLogEventParser {
-
- public XmlLogEventParser() {
- super(new Log4jXmlObjectMapper());
- }
-
-}
diff --git a/log4j-layout-jackson-xml/pom.xml b/log4j-layout-jackson-xml/pom.xml
index 2f3dbb4..68a5ae8 100644
--- a/log4j-layout-jackson-xml/pom.xml
+++ b/log4j-layout-jackson-xml/pom.xml
@@ -35,6 +35,11 @@
<version>${project.version}</version>
</dependency>
<dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-1.2-api</artifactId>
+ <optional>true</optional>
+ </dependency>
+ <dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
</dependency>
@@ -117,6 +122,7 @@
<!-- module link generation is completely broken in the javadoc plugin for a multi-module non-aggregating project -->
<detectOfflineLinks>false</detectOfflineLinks>
<linksource>true</linksource>
+ <source>8</source>
</configuration>
<reportSets>
<reportSet>
diff --git a/log4j-layout-jackson-xml/src/main/java/org/apache/logging/log4j/jackson/xml/XmlMapEntry.java b/log4j-layout-jackson-xml/src/main/java/org/apache/logging/log4j/jackson/xml/XmlMapEntry.java
index 2500c11..d4a90ce 100644
--- a/log4j-layout-jackson-xml/src/main/java/org/apache/logging/log4j/jackson/xml/XmlMapEntry.java
+++ b/log4j-layout-jackson-xml/src/main/java/org/apache/logging/log4j/jackson/xml/XmlMapEntry.java
@@ -38,4 +38,4 @@
super(key, value);
}
-}
\ No newline at end of file
+}
diff --git a/log4j-layout-jackson-xml/src/main/java/org/apache/logging/log4j/jackson/xml/XmlSetupContextInitializer.java b/log4j-layout-jackson-xml/src/main/java/org/apache/logging/log4j/jackson/xml/XmlSetupContextInitializer.java
index 4cc5124..52f155b 100644
--- a/log4j-layout-jackson-xml/src/main/java/org/apache/logging/log4j/jackson/xml/XmlSetupContextInitializer.java
+++ b/log4j-layout-jackson-xml/src/main/java/org/apache/logging/log4j/jackson/xml/XmlSetupContextInitializer.java
@@ -35,4 +35,4 @@
? (stacktraceAsString ? ThrowableProxyWithStacktraceAsStringXmlMixIn.class : ThrowableProxyXmlMixIn.class)
: ThrowableProxyWithoutStacktraceXmlMixIn.class);
}
-}
\ No newline at end of file
+}
diff --git a/log4j-layout-jackson-xml/src/main/java/org/apache/logging/log4j/jackson/xml/builders/layout/XmlLayoutBuilder.java b/log4j-layout-jackson-xml/src/main/java/org/apache/logging/log4j/jackson/xml/builders/layout/XmlLayoutBuilder.java
new file mode 100644
index 0000000..28f0474
--- /dev/null
+++ b/log4j-layout-jackson-xml/src/main/java/org/apache/logging/log4j/jackson/xml/builders/layout/XmlLayoutBuilder.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.jackson.xml.builders.layout;
+
+import org.apache.log4j.Layout;
+import org.apache.log4j.bridge.LayoutWrapper;
+import org.apache.log4j.builders.AbstractBuilder;
+import org.apache.log4j.builders.BooleanHolder;
+import org.apache.log4j.builders.Holder;
+import org.apache.log4j.builders.layout.LayoutBuilder;
+import org.apache.log4j.config.PropertiesConfiguration;
+import org.apache.log4j.xml.XmlConfiguration;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.jackson.xml.layout.XmlLayout;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.status.StatusLogger;
+import org.w3c.dom.Element;
+
+import java.util.Properties;
+
+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;
+
+/**
+ * Build an XML Layout
+ */
+@Plugin(name = "org.apache.log4j.xml.XMLLayout", category = CATEGORY)
+public class XmlLayoutBuilder extends AbstractBuilder implements LayoutBuilder {
+
+ private static final Logger LOGGER = StatusLogger.getLogger();
+
+ private static final String LOCATION_INFO = "LocationInfo";
+ private static final String PROPERTIES = "Properties";
+
+ public XmlLayoutBuilder() {
+ }
+
+ public XmlLayoutBuilder(String prefix, Properties props) {
+ super(prefix, props);
+ }
+
+
+ @Override
+ public Layout parseLayout(Element layoutElement, XmlConfiguration config) {
+ final Holder<Boolean> properties = new BooleanHolder();
+ final Holder<Boolean> locationInfo = new BooleanHolder();
+ forEachElement(layoutElement.getElementsByTagName(PARAM_TAG), (currentElement) -> {
+ if (PROPERTIES.equalsIgnoreCase(currentElement.getAttribute("name"))) {
+ properties.set(Boolean.parseBoolean(currentElement.getAttribute("value")));
+ } else if (LOCATION_INFO.equalsIgnoreCase(currentElement.getAttribute("name"))) {
+ locationInfo.set(Boolean.parseBoolean(currentElement.getAttribute("value")));
+ }
+ });
+ return createLayout(properties.get(), locationInfo.get());
+ }
+
+ @Override
+ public Layout parseLayout(PropertiesConfiguration config) {
+ boolean properties = getBooleanProperty(PROPERTIES);
+ boolean locationInfo = getBooleanProperty(LOCATION_INFO);
+ return createLayout(properties, locationInfo);
+ }
+
+ private Layout createLayout(boolean properties, boolean locationInfo) {
+ return new LayoutWrapper(XmlLayout.newBuilder()
+ .setLocationInfo(locationInfo)
+ .setProperties(properties)
+ .build());
+ }
+}
diff --git a/log4j-layout-jackson-xml/src/main/java/org/apache/logging/log4j/jackson/xml/layout/Log4jXmlPrettyPrinter.java b/log4j-layout-jackson-xml/src/main/java/org/apache/logging/log4j/jackson/xml/layout/Log4jXmlPrettyPrinter.java
index 6956861..e578e87 100644
--- a/log4j-layout-jackson-xml/src/main/java/org/apache/logging/log4j/jackson/xml/layout/Log4jXmlPrettyPrinter.java
+++ b/log4j-layout-jackson-xml/src/main/java/org/apache/logging/log4j/jackson/xml/layout/Log4jXmlPrettyPrinter.java
@@ -38,4 +38,4 @@
// nothing
}
-}
\ No newline at end of file
+}
diff --git a/log4j-layout-jackson-xml/src/main/java/org/apache/logging/log4j/jackson/xml/layout/XmlJacksonFactory.java b/log4j-layout-jackson-xml/src/main/java/org/apache/logging/log4j/jackson/xml/layout/XmlJacksonFactory.java
index 0cfba5b..616e48a 100644
--- a/log4j-layout-jackson-xml/src/main/java/org/apache/logging/log4j/jackson/xml/layout/XmlJacksonFactory.java
+++ b/log4j-layout-jackson-xml/src/main/java/org/apache/logging/log4j/jackson/xml/layout/XmlJacksonFactory.java
@@ -22,6 +22,16 @@
}
@Override
+ protected String getPropertyNameForTimeMillis() {
+ return XmlConstants.ELT_TIME_MILLIS;
+ }
+
+ @Override
+ protected String getPropertyNameForInstant() {
+ return XmlConstants.ELT_INSTANT;
+ }
+
+ @Override
protected String getPropertyNameForNanoTime() {
return JsonConstants.ELT_NANO_TIME;
}
@@ -51,4 +61,4 @@
protected PrettyPrinter newPrettyPrinter() {
return new Log4jXmlPrettyPrinter(DEFAULT_INDENT);
}
-}
\ No newline at end of file
+}
diff --git a/log4j-layout-jackson-xml/src/main/java/org/apache/logging/log4j/jackson/xml/layout/XmlLayout.java b/log4j-layout-jackson-xml/src/main/java/org/apache/logging/log4j/jackson/xml/layout/XmlLayout.java
index 6d723b0..2072ff1 100644
--- a/log4j-layout-jackson-xml/src/main/java/org/apache/logging/log4j/jackson/xml/layout/XmlLayout.java
+++ b/log4j-layout-jackson-xml/src/main/java/org/apache/logging/log4j/jackson/xml/layout/XmlLayout.java
@@ -16,23 +16,23 @@
*/
package org.apache.logging.log4j.jackson.xml.layout;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
+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.util.KeyValuePair;
+import org.apache.logging.log4j.jackson.AbstractJacksonLayout;
+import org.apache.logging.log4j.jackson.XmlConstants;
+import org.apache.logging.log4j.plugins.Node;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginFactory;
+
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
-import org.apache.logging.log4j.core.Layout;
-import org.apache.logging.log4j.core.LogEvent;
-import org.apache.logging.log4j.core.config.Configuration;
-import org.apache.logging.log4j.core.config.Node;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
-import org.apache.logging.log4j.core.util.KeyValuePair;
-import org.apache.logging.log4j.jackson.AbstractJacksonLayout;
-import org.apache.logging.log4j.jackson.XmlConstants;
-
-import com.fasterxml.jackson.dataformat.xml.annotation.JacksonXmlRootElement;
-
/**
* Appends a series of {@code event} elements as defined in the <a href="log4j.dtd">log4j.dtd</a>.
*
@@ -67,7 +67,7 @@
public final class XmlLayout extends AbstractJacksonLayout {
public static class Builder<B extends Builder<B>> extends AbstractJacksonLayout.Builder<B>
- implements org.apache.logging.log4j.core.util.Builder<XmlLayout> {
+ implements org.apache.logging.log4j.plugins.util.Builder<XmlLayout> {
public Builder() {
super();
@@ -78,15 +78,22 @@
public XmlLayout build() {
return new XmlLayout(getConfiguration(), isLocationInfo(), isProperties(), isComplete(), isCompact(),
getCharset(), isIncludeStacktrace(), isStacktraceAsString(), isIncludeNullDelimiter(),
- getAdditionalFields());
+ isIncludeNullDelimiter(), getAdditionalFields());
}
}
@JacksonXmlRootElement(namespace = XmlConstants.XML_NAMESPACE, localName = XmlConstants.ELT_EVENT)
public static class XmlLogEventWithAdditionalFields extends LogEventWithAdditionalFields {
- public XmlLogEventWithAdditionalFields(final Object logEvent, final Map<String, String> additionalFields) {
+ public XmlLogEventWithAdditionalFields(final LogEvent logEvent, final Map<String, String> additionalFields) {
super(logEvent, additionalFields);
+
+ }
+
+ @Override
+ @JsonSerialize(as = LogEvent.class)
+ public LogEvent getLogEvent() {
+ return super.getLogEvent();
}
}
@@ -99,56 +106,21 @@
* @return an XML Layout.
*/
public static XmlLayout createDefaultLayout() {
- return new XmlLayout(null, false, false, false, false, StandardCharsets.UTF_8, true, false, false, null);
+ return new XmlLayout(null, false, false, false, false, StandardCharsets.UTF_8, true, false, false, false, null);
}
- /**
- * Creates an XML Layout.
- *
- * @param locationInfo
- * If "true", includes the location information in the generated XML.
- * @param properties
- * If "true", includes the thread context map in the generated XML.
- * @param complete
- * If "true", includes the XML header and footer, defaults to "false".
- * @param compact
- * If "true", does not use end-of-lines and indentation, defaults to "false".
- * @param charset
- * The character set to use, if {@code null}, uses "UTF-8".
- * @param includeStacktrace
- * If "true", includes the stacktrace of any Throwable in the generated XML, defaults to "true".
- * @return An XML Layout.
- *
- * @deprecated Use {@link #newBuilder()} instead
- */
- @Deprecated
- public static XmlLayout createLayout(final boolean locationInfo, final boolean properties, final boolean complete,
- final boolean compact, final Charset charset, final boolean includeStacktrace) {
- return new XmlLayout(null, locationInfo, properties, complete, compact, charset, includeStacktrace, false,
- false, null);
- }
-
- @PluginBuilderFactory
+ @PluginFactory
public static <B extends Builder<B>> B newBuilder() {
return new Builder<B>().asBuilder();
}
- /**
- * @deprecated Use {@link #newBuilder()} instead
- */
- @Deprecated
- protected XmlLayout(final boolean locationInfo, final boolean properties, final boolean complete,
- final boolean compact, final Charset charset, final boolean includeStacktrace) {
- this(null, locationInfo, properties, complete, compact, charset, includeStacktrace, false, false, null);
- }
-
private XmlLayout(final Configuration config, final boolean locationInfo, final boolean properties,
final boolean complete, final boolean compact, final Charset charset, final boolean includeStacktrace,
- final boolean stacktraceAsString, final boolean includeNullDelimiter,
+ final boolean stacktraceAsString, final boolean includeNullDelimiter, final boolean includeTimeMillis,
final KeyValuePair[] additionalFields) {
super(config,
new XmlJacksonFactory(includeStacktrace, stacktraceAsString).newWriter(locationInfo, properties,
- compact),
+ compact, includeTimeMillis),
charset, compact, complete, false, null, null, includeNullDelimiter, additionalFields);
}
diff --git a/log4j-layout-jackson-xml/src/site/manual/index.md b/log4j-layout-jackson-xml/src/site/manual/index.md
index 216fe05..b46d368 100644
--- a/log4j-layout-jackson-xml/src/site/manual/index.md
+++ b/log4j-layout-jackson-xml/src/site/manual/index.md
@@ -18,7 +18,7 @@
# Apache Log4j Layout for Jackson XML module
-As of Log4j 3.0.0, the layout based on Jackson XML has moved from the existing module logj-core to the new modules log4j-layout-jackson-xml.
+As of Log4j 3.0.0, the layout based on Jackson XML has moved from the existing module log4j-core to the new modules log4j-layout-jackson-xml.
## Requirements
diff --git a/log4j-layout-jackson-xml/src/test/java/org/apache/logging/log4j/jackson/xml/layout/XmlLayoutTest.java b/log4j-layout-jackson-xml/src/test/java/org/apache/logging/log4j/jackson/xml/layout/XmlLayoutTest.java
index 5323163..6aae517 100644
--- a/log4j-layout-jackson-xml/src/test/java/org/apache/logging/log4j/jackson/xml/layout/XmlLayoutTest.java
+++ b/log4j-layout-jackson-xml/src/test/java/org/apache/logging/log4j/jackson/xml/layout/XmlLayoutTest.java
@@ -37,6 +37,7 @@
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.impl.MutableLogEvent;
import org.apache.logging.log4j.core.layout.LogEventFixtures;
import org.apache.logging.log4j.core.lookup.JavaLookup;
import org.apache.logging.log4j.core.util.KeyValuePair;
@@ -197,6 +198,20 @@
assertTrue(str, str.contains("<KEY2>" + new JavaLookup().getRuntime() + "</KEY2>"));
}
+ @Test
+ public void testMutableLogEvent() throws Exception {
+ final AbstractJacksonLayout layout = XmlLayout.newBuilder().setLocationInfo(false).setProperties(false)
+ .setIncludeStacktrace(false)
+ .setAdditionalFields(new KeyValuePair[] { new KeyValuePair("KEY1", "VALUE1"),
+ new KeyValuePair("KEY2", "${java:runtime}"), })
+ .setCharset(StandardCharsets.UTF_8).setConfiguration(ctx.getConfiguration()).build();
+ Log4jLogEvent logEvent = LogEventFixtures.createLogEvent();
+ final MutableLogEvent mutableEvent = new MutableLogEvent();
+ mutableEvent.initFrom(logEvent);
+ final String strLogEvent = layout.toSerializable(logEvent);
+ final String strMutableEvent = layout.toSerializable(mutableEvent);
+ assertEquals(strMutableEvent, strLogEvent, strMutableEvent);
+ }
/**
* @param includeLocationInfo
* TODO
diff --git a/log4j-layout-jackson-yaml/pom.xml b/log4j-layout-jackson-yaml/pom.xml
index 90e1f82..ded423c 100644
--- a/log4j-layout-jackson-yaml/pom.xml
+++ b/log4j-layout-jackson-yaml/pom.xml
@@ -117,6 +117,7 @@
<!-- module link generation is completely broken in the javadoc plugin for a multi-module non-aggregating project -->
<detectOfflineLinks>false</detectOfflineLinks>
<linksource>true</linksource>
+ <source>8</source>
</configuration>
<reportSets>
<reportSet>
diff --git a/log4j-layout-jackson-yaml/src/main/java/org/apache/logging/log4j/jackson/yaml/Log4jYamlObjectMapper.java b/log4j-layout-jackson-yaml/src/main/java/org/apache/logging/log4j/jackson/yaml/Log4jYamlObjectMapper.java
index 62c6483..15f2ce6 100644
--- a/log4j-layout-jackson-yaml/src/main/java/org/apache/logging/log4j/jackson/yaml/Log4jYamlObjectMapper.java
+++ b/log4j-layout-jackson-yaml/src/main/java/org/apache/logging/log4j/jackson/yaml/Log4jYamlObjectMapper.java
@@ -18,6 +18,7 @@
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator;
import com.fasterxml.jackson.dataformat.yaml.YAMLMapper;
/**
@@ -43,6 +44,7 @@
public Log4jYamlObjectMapper(final boolean encodeThreadContextAsList, final boolean includeStacktrace, final boolean stacktraceAsString) {
this.registerModule(new Log4jYamlModule(encodeThreadContextAsList, includeStacktrace, stacktraceAsString));
this.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
+ this.disable(YAMLGenerator.Feature.SPLIT_LINES);
}
}
diff --git a/log4j-layout-jackson-yaml/src/main/java/org/apache/logging/log4j/jackson/yaml/YamlSetupContextInitializer.java b/log4j-layout-jackson-yaml/src/main/java/org/apache/logging/log4j/jackson/yaml/YamlSetupContextInitializer.java
index 67f6fd1..a3c8e4e 100644
--- a/log4j-layout-jackson-yaml/src/main/java/org/apache/logging/log4j/jackson/yaml/YamlSetupContextInitializer.java
+++ b/log4j-layout-jackson-yaml/src/main/java/org/apache/logging/log4j/jackson/yaml/YamlSetupContextInitializer.java
@@ -44,4 +44,4 @@
: ThrowableProxyWithoutStacktraceMixIn.class);
}
-}
\ No newline at end of file
+}
diff --git a/log4j-layout-jackson-yaml/src/main/java/org/apache/logging/log4j/jackson/yaml/layout/YamlConstants.java b/log4j-layout-jackson-yaml/src/main/java/org/apache/logging/log4j/jackson/yaml/layout/YamlConstants.java
index 856a201..f93c379 100644
--- a/log4j-layout-jackson-yaml/src/main/java/org/apache/logging/log4j/jackson/yaml/layout/YamlConstants.java
+++ b/log4j-layout-jackson-yaml/src/main/java/org/apache/logging/log4j/jackson/yaml/layout/YamlConstants.java
@@ -32,4 +32,5 @@
static final String ELT_EXTENDED_STACK_TRACE = "extendedStackTrace";
static final String ELT_NANO_TIME = "nanoTime";
static final String ELT_INSTANT = "instant";
+ static final String ELT_TIME_MILLIS = "timeMillis";
}
diff --git a/log4j-layout-jackson-yaml/src/main/java/org/apache/logging/log4j/jackson/yaml/layout/YamlJacksonFactory.java b/log4j-layout-jackson-yaml/src/main/java/org/apache/logging/log4j/jackson/yaml/layout/YamlJacksonFactory.java
index e32041a..a55365d 100644
--- a/log4j-layout-jackson-yaml/src/main/java/org/apache/logging/log4j/jackson/yaml/layout/YamlJacksonFactory.java
+++ b/log4j-layout-jackson-yaml/src/main/java/org/apache/logging/log4j/jackson/yaml/layout/YamlJacksonFactory.java
@@ -37,6 +37,16 @@
}
@Override
+ protected String getPropertyNameForTimeMillis() {
+ return YamlConstants.ELT_TIME_MILLIS;
+ }
+
+ @Override
+ protected String getPropertyNameForInstant() {
+ return YamlConstants.ELT_INSTANT;
+ }
+
+ @Override
protected String getPropertyNameForNanoTime() {
return YamlConstants.ELT_NANO_TIME;
}
@@ -65,4 +75,4 @@
protected PrettyPrinter newPrettyPrinter() {
return new DefaultPrettyPrinter();
}
-}
\ No newline at end of file
+}
diff --git a/log4j-layout-jackson-yaml/src/main/java/org/apache/logging/log4j/jackson/yaml/layout/YamlLayout.java b/log4j-layout-jackson-yaml/src/main/java/org/apache/logging/log4j/jackson/yaml/layout/YamlLayout.java
index 418ef9a..e0f3c1e 100644
--- a/log4j-layout-jackson-yaml/src/main/java/org/apache/logging/log4j/jackson/yaml/layout/YamlLayout.java
+++ b/log4j-layout-jackson-yaml/src/main/java/org/apache/logging/log4j/jackson/yaml/layout/YamlLayout.java
@@ -17,26 +17,26 @@
package org.apache.logging.log4j.jackson.yaml.layout;
-import java.nio.charset.Charset;
-import java.nio.charset.StandardCharsets;
-import java.util.HashMap;
-import java.util.Map;
-
+import com.fasterxml.jackson.annotation.JsonAnyGetter;
+import com.fasterxml.jackson.annotation.JsonRootName;
+import com.fasterxml.jackson.annotation.JsonUnwrapped;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import org.apache.logging.log4j.core.Layout;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.DefaultConfiguration;
-import org.apache.logging.log4j.core.config.Node;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
import org.apache.logging.log4j.core.layout.PatternLayout;
import org.apache.logging.log4j.core.util.KeyValuePair;
import org.apache.logging.log4j.jackson.AbstractJacksonLayout;
+import org.apache.logging.log4j.plugins.Node;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginFactory;
import org.apache.logging.log4j.util.Strings;
-import com.fasterxml.jackson.annotation.JsonAnyGetter;
-import com.fasterxml.jackson.annotation.JsonRootName;
-import com.fasterxml.jackson.annotation.JsonUnwrapped;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.HashMap;
+import java.util.Map;
/**
* Appends a series of YAML events as strings serialized as bytes.
@@ -57,7 +57,7 @@
public final class YamlLayout extends AbstractJacksonLayout {
public static class Builder<B extends Builder<B>> extends AbstractJacksonLayout.Builder<B>
- implements org.apache.logging.log4j.core.util.Builder<YamlLayout> {
+ implements org.apache.logging.log4j.plugins.util.Builder<YamlLayout> {
public Builder() {
super();
@@ -70,14 +70,14 @@
final String footerPattern = toStringOrNull(getFooter());
return new YamlLayout(getConfiguration(), isLocationInfo(), isProperties(), isComplete(), isCompact(),
getEventEol(), headerPattern, footerPattern, getCharset(), isIncludeStacktrace(),
- isStacktraceAsString(), isIncludeNullDelimiter(), getAdditionalFields());
+ isStacktraceAsString(), isIncludeNullDelimiter(), isIncludeTimeMillis(), getAdditionalFields());
}
}
@JsonRootName(YamlConstants.EVENT)
public static class YamlLogEventWithAdditionalFields extends LogEventWithAdditionalFields {
- public YamlLogEventWithAdditionalFields(final Object logEvent, final Map<String, String> additionalFields) {
+ public YamlLogEventWithAdditionalFields(final LogEvent logEvent, final Map<String, String> additionalFields) {
super(logEvent, additionalFields);
}
@@ -89,7 +89,8 @@
@Override
@JsonUnwrapped
- public Object getLogEvent() {
+ @JsonSerialize(as = LogEvent.class)
+ public LogEvent getLogEvent() {
return super.getLogEvent();
}
@@ -108,67 +109,22 @@
*/
public static AbstractJacksonLayout createDefaultLayout() {
return new YamlLayout(new DefaultConfiguration(), false, false, false, false, false, DEFAULT_HEADER,
- DEFAULT_FOOTER, StandardCharsets.UTF_8, true, false, false, null);
+ DEFAULT_FOOTER, StandardCharsets.UTF_8, true, false, false, false, null);
}
- /**
- * Creates a YAML Layout.
- *
- * @param config
- * The plugin configuration.
- * @param locationInfo
- * If "true", includes the location information in the generated YAML.
- * @param properties
- * If "true", includes the thread context map in the generated YAML.
- * @param headerPattern
- * The header pattern, defaults to {@code ""} if null.
- * @param footerPattern
- * The header pattern, defaults to {@code ""} if null.
- * @param charset
- * The character set to use, if {@code null}, uses "UTF-8".
- * @param includeStacktrace
- * If "true", includes the stacktrace of any Throwable in the generated YAML, defaults to "true".
- * @return A YAML Layout.
- *
- * @deprecated Use {@link #newBuilder()} instead
- */
- @Deprecated
- public static AbstractJacksonLayout createLayout(final Configuration config, final boolean locationInfo,
- final boolean properties, final String headerPattern, final String footerPattern, final Charset charset,
- final boolean includeStacktrace) {
- return new YamlLayout(config, locationInfo, properties, false, false, true, headerPattern, footerPattern,
- charset, includeStacktrace, false, false, null);
- }
-
- @PluginBuilderFactory
+ @PluginFactory
public static <B extends Builder<B>> B newBuilder() {
return new Builder<B>().asBuilder();
}
- /**
- * @deprecated Use {@link #newBuilder()} instead
- */
- @Deprecated
- protected YamlLayout(final Configuration config, final boolean locationInfo, final boolean properties,
- final boolean complete, final boolean compact, final boolean eventEol, final String headerPattern,
- final String footerPattern, final Charset charset, final boolean includeStacktrace) {
- super(config, new YamlJacksonFactory(includeStacktrace, false).newWriter(locationInfo, properties, compact),
- charset, compact, complete, eventEol,
- PatternLayout.newSerializerBuilder().setConfiguration(config).setPattern(headerPattern)
- .setDefaultPattern(DEFAULT_HEADER).build(),
- PatternLayout.newSerializerBuilder().setConfiguration(config).setPattern(footerPattern)
- .setDefaultPattern(DEFAULT_FOOTER).build(),
- false, null);
- }
-
private YamlLayout(final Configuration config, final boolean locationInfo, final boolean properties,
final boolean complete, final boolean compact, final boolean eventEol, final String headerPattern,
final String footerPattern, final Charset charset, final boolean includeStacktrace,
- final boolean stacktraceAsString, final boolean includeNullDelimiter,
+ final boolean stacktraceAsString, final boolean includeNullDelimiter, final boolean includeTimeMillis,
final KeyValuePair[] additionalFields) {
super(config,
new YamlJacksonFactory(includeStacktrace, stacktraceAsString).newWriter(locationInfo, properties,
- compact),
+ compact, includeTimeMillis),
charset, compact, complete, eventEol,
PatternLayout.newSerializerBuilder().setConfiguration(config).setPattern(headerPattern)
.setDefaultPattern(DEFAULT_HEADER).build(),
diff --git a/log4j-layout-jackson-yaml/src/site/manual/index.md b/log4j-layout-jackson-yaml/src/site/manual/index.md
index 83112af..6fb90f1 100644
--- a/log4j-layout-jackson-yaml/src/site/manual/index.md
+++ b/log4j-layout-jackson-yaml/src/site/manual/index.md
@@ -18,7 +18,7 @@
# Apache Log4j Layout for Jackson YAML module
-As of Log4j 3.0.0, the layout based on Jackson YAML has moved from the existing module logj-core to the new modules log4j-layout-jackson-yaml.
+As of Log4j 3.0.0, the layout based on Jackson YAML has moved from the existing module log4j-core to the new modules log4j-layout-jackson-yaml.
## Requirements
diff --git a/log4j-layout-jackson-yaml/src/test/java/org/apache/logging/log4j/jackson/yaml/layout/YamlLayoutTest.java b/log4j-layout-jackson-yaml/src/test/java/org/apache/logging/log4j/jackson/yaml/layout/YamlLayoutTest.java
index b8e3ee6..33f187c 100644
--- a/log4j-layout-jackson-yaml/src/test/java/org/apache/logging/log4j/jackson/yaml/layout/YamlLayoutTest.java
+++ b/log4j-layout-jackson-yaml/src/test/java/org/apache/logging/log4j/jackson/yaml/layout/YamlLayoutTest.java
@@ -16,11 +16,6 @@
*/
package org.apache.logging.log4j.jackson.yaml.layout;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Map;
@@ -35,6 +30,7 @@
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.ConfigurationFactory;
import org.apache.logging.log4j.core.impl.Log4jLogEvent;
+import org.apache.logging.log4j.core.impl.MutableLogEvent;
import org.apache.logging.log4j.core.layout.LogEventFixtures;
import org.apache.logging.log4j.core.lookup.JavaLookup;
import org.apache.logging.log4j.core.util.KeyValuePair;
@@ -50,6 +46,9 @@
import org.junit.Test;
import org.junit.experimental.categories.Category;
+import static org.hamcrest.CoreMatchers.*;
+import static org.junit.Assert.*;
+
/**
* Tests the YamlLayout class.
*/
@@ -77,8 +76,7 @@
private void checkAt(final String expected, final int lineIndex, final List<String> list) {
final String trimedLine = list.get(lineIndex).trim();
- assertTrue("Incorrect line index " + lineIndex + ": " + Strings.dquote(trimedLine),
- trimedLine.equals(expected));
+ assertEquals("Incorrect line index " + lineIndex + ": " + Strings.dquote(trimedLine), trimedLine, expected);
}
private void checkContains(final String expected, final List<String> list) {
@@ -95,7 +93,7 @@
// "name":"value"
//final String expected = String.format("- key: \"%s\"\n value: \"%s\"", key, value);
final String expected = String.format("%s: \"%s\"", key, value);
- assertTrue("Cannot find " + expected + " in " + str, str.contains(expected));
+ assertThat(str, containsString(expected));
}
private void checkProperty(final String key, final String value, final boolean compact, final String str,
@@ -103,17 +101,17 @@
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("Cannot find " + expected + " in " + str, str.contains(expected));
+ assertThat(str, containsString(expected));
}
private void checkPropertyName(final String name, final boolean compact, final String str, final boolean isValue) {
final String propSep = this.toPropertySeparator(compact, isValue);
- assertTrue(str, str.contains(name + propSep));
+ assertThat(str, containsString(name + propSep));
}
private void checkPropertyNameAbsent(final String name, final boolean compact, final String str, final boolean isValue) {
final String propSep = this.toPropertySeparator(compact, isValue);
- assertFalse(str, str.contains(name + propSep));
+ assertThat(str, not(containsString(name + propSep)));
}
private String prepareYAMLForStacktraceTests(final boolean stacktraceAsString) {
@@ -123,7 +121,7 @@
.setIncludeStacktrace(true)
.setStacktraceAsString(stacktraceAsString)
.build();
- // @formatter:off
+ // @formatter:on
return layout.toSerializable(expected);
}
@@ -140,8 +138,28 @@
.setConfiguration(ctx.getConfiguration())
.build();
final String str = layout.toSerializable(LogEventFixtures.createLogEvent());
- assertTrue(str, str.contains("KEY1: \"VALUE1\""));
- assertTrue(str, str.contains("KEY2: \"" + new JavaLookup().getRuntime() + "\""));
+ assertThat(str, containsString("KEY1: \"VALUE1\""));
+ assertThat(str, containsString("KEY2: \"" + new JavaLookup().getRuntime() + "\""));
+ }
+
+ @Test
+ public void testMutableLogEvent() 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();
+ Log4jLogEvent logEvent = LogEventFixtures.createLogEvent();
+ final MutableLogEvent mutableEvent = new MutableLogEvent();
+ mutableEvent.initFrom(logEvent);
+ final String strLogEvent = layout.toSerializable(logEvent);
+ final String strMutableEvent = layout.toSerializable(mutableEvent);
+ assertEquals(strMutableEvent, strLogEvent, strMutableEvent);
}
private void testAllFeatures(final boolean includeSource, final boolean compact, final boolean eventEol,
@@ -272,7 +290,7 @@
.setIncludeNullDelimiter(false)
.build();
final String str = layout.toSerializable(LogEventFixtures.createLogEvent());
- assertFalse(str.endsWith("\0"));
+ assertThat(str, not(endsWith("\0")));
}
@Test
@@ -281,7 +299,7 @@
.setIncludeNullDelimiter(true)
.build();
final String str = layout.toSerializable(LogEventFixtures.createLogEvent());
- assertTrue(str.endsWith("\0"));
+ assertThat(str, endsWith("\0"));
}
/**
@@ -296,7 +314,9 @@
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 AbstractJacksonLayout layout = YamlLayout.newBuilder().setConfiguration(configuration).setLocationInfo(true).setProperties(true)
+ .setHeader("[[".getBytes(StandardCharsets.UTF_8)).setFooter("]]".getBytes(StandardCharsets.UTF_8))
+ .setIncludeStacktrace(true).build();
final ListAppender appender = new ListAppender("List", null, layout, true, false);
appender.start();
@@ -348,7 +368,7 @@
.setThreadName("threadName") //
.setTimeMillis(1).build();
final String str = layout.toSerializable(expected);
- assertTrue(str, str.contains("loggerName: \"a.B\""));
+ assertThat(str, containsString("loggerName: \"a.B\""));
final Log4jLogEvent actual = new Log4jYamlObjectMapper(false, true, false).readValue(str, Log4jLogEvent.class);
assertEquals(expected.getLoggerName(), actual.getLoggerName());
assertEquals(expected, actual);
@@ -367,13 +387,13 @@
@Test
public void testStacktraceAsNonString() throws Exception {
final String str = prepareYAMLForStacktraceTests(false);
- assertTrue(str, str.contains("extendedStackTrace:\n - "));
+ assertThat(str, containsString("extendedStackTrace:\n - "));
}
@Test
public void testStacktraceAsString() throws Exception {
final String str = prepareYAMLForStacktraceTests(true);
- assertTrue(str, str.contains("extendedStackTrace: \"java.lang.NullPointerException"));
+ assertThat(str, containsString("extendedStackTrace: \"java.lang.NullPointerException"));
}
private String toPropertySeparator(final boolean compact, final boolean value) {
diff --git a/log4j-layout-jackson/pom.xml b/log4j-layout-jackson/pom.xml
index 5c8095d..928da2a 100644
--- a/log4j-layout-jackson/pom.xml
+++ b/log4j-layout-jackson/pom.xml
@@ -160,6 +160,7 @@
<!-- module link generation is completely broken in the javadoc plugin for a multi-module non-aggregating project -->
<detectOfflineLinks>false</detectOfflineLinks>
<linksource>true</linksource>
+ <source>8</source>
</configuration>
<reportSets>
<reportSet>
diff --git a/log4j-layout-jackson/src/main/java/org/apache/logging/log4j/jackson/AbstractJacksonFactory.java b/log4j-layout-jackson/src/main/java/org/apache/logging/log4j/jackson/AbstractJacksonFactory.java
index ad4503c..ba56e3f 100644
--- a/log4j-layout-jackson/src/main/java/org/apache/logging/log4j/jackson/AbstractJacksonFactory.java
+++ b/log4j-layout-jackson/src/main/java/org/apache/logging/log4j/jackson/AbstractJacksonFactory.java
@@ -40,6 +40,10 @@
abstract protected String getPropertyNameForContextMap();
+ abstract protected String getPropertyNameForTimeMillis();
+
+ abstract protected String getPropertyNameForInstant();
+
abstract protected String getPropertyNameForNanoTime();
abstract protected String getPropertyNameForSource();
@@ -52,9 +56,10 @@
abstract protected PrettyPrinter newPrettyPrinter();
- public ObjectWriter newWriter(final boolean locationInfo, final boolean properties, final boolean compact) {
+ public ObjectWriter newWriter(final boolean locationInfo, final boolean properties, final boolean compact,
+ final boolean includeMillis) {
final SimpleFilterProvider filters = new SimpleFilterProvider();
- final Set<String> except = new HashSet<>(4);
+ final Set<String> except = new HashSet<>(5);
if (!locationInfo) {
except.add(this.getPropertyNameForSource());
}
@@ -64,6 +69,11 @@
if (!includeStacktrace) {
except.add(this.getPropertyNameForStackTrace());
}
+ if (includeMillis) {
+ except.add(getPropertyNameForInstant());
+ } else {
+ except.add(getPropertyNameForTimeMillis());
+ }
except.add(this.getPropertyNameForNanoTime());
filters.addFilter(Log4jLogEvent.class.getName(), SimpleBeanPropertyFilter.serializeAllExcept(except));
final ObjectWriter writer = this.newObjectMapper()
diff --git a/log4j-layout-jackson/src/main/java/org/apache/logging/log4j/jackson/AbstractJacksonLayout.java b/log4j-layout-jackson/src/main/java/org/apache/logging/log4j/jackson/AbstractJacksonLayout.java
index 2e35b01..bbddb7a 100644
--- a/log4j-layout-jackson/src/main/java/org/apache/logging/log4j/jackson/AbstractJacksonLayout.java
+++ b/log4j-layout-jackson/src/main/java/org/apache/logging/log4j/jackson/AbstractJacksonLayout.java
@@ -22,15 +22,22 @@
import java.util.LinkedHashMap;
import java.util.Map;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+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.config.Configuration;
-import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
-import org.apache.logging.log4j.core.config.plugins.PluginElement;
-import org.apache.logging.log4j.core.impl.Log4jLogEvent;
+import org.apache.logging.log4j.plugins.PluginBuilderAttribute;
+import org.apache.logging.log4j.plugins.PluginElement;
+import org.apache.logging.log4j.core.impl.ThrowableProxy;
import org.apache.logging.log4j.core.layout.AbstractStringLayout;
import org.apache.logging.log4j.core.lookup.StrSubstitutor;
+import org.apache.logging.log4j.core.time.Instant;
import org.apache.logging.log4j.core.util.KeyValuePair;
import org.apache.logging.log4j.core.util.StringBuilderWriter;
+import org.apache.logging.log4j.message.Message;
+import org.apache.logging.log4j.util.ReadOnlyStringMap;
import org.apache.logging.log4j.util.Strings;
import com.fasterxml.jackson.databind.ObjectWriter;
@@ -43,6 +50,9 @@
private boolean eventEol;
@PluginBuilderAttribute
+ private String endOfLine;
+
+ @PluginBuilderAttribute
private boolean compact;
@PluginBuilderAttribute
@@ -63,6 +73,9 @@
@PluginBuilderAttribute
private boolean includeNullDelimiter = false;
+ @PluginBuilderAttribute
+ private boolean includeTimeMillis = false;
+
@PluginElement("AdditionalField")
private KeyValuePair[] additionalFields;
@@ -74,6 +87,10 @@
return eventEol;
}
+ public String getEndOfLine() {
+ return endOfLine;
+ }
+
public boolean isCompact() {
return compact;
}
@@ -86,6 +103,10 @@
return includeNullDelimiter;
}
+ public boolean isIncludeTimeMillis() {
+ return includeTimeMillis;
+ }
+
/**
* If "true", includes the stack trace of any Throwable in the generated data, defaults to "true".
*
@@ -109,7 +130,7 @@
/**
* Additional fields to set on each log event.
- *
+ * @param additionalFields The additional Key/Value pairs to add.
* @return this builder
*/
public B setAdditionalFields(final KeyValuePair[] additionalFields) {
@@ -132,9 +153,14 @@
return asBuilder();
}
+ public B setEndOfLine(final String endOfLine) {
+ this.endOfLine = endOfLine;
+ return asBuilder();
+ }
+
/**
* Whether to include NULL byte as delimiter after each event (optional, default to false).
- *
+ * @param includeNullDelimiter true if a null delimiter should be included.
* @return this builder
*/
public B setIncludeNullDelimiter(final boolean includeNullDelimiter) {
@@ -143,6 +169,16 @@
}
/**
+ * Whether to include the timestamp (in addition to the Instant) (optional, default to false).
+ *
+ * @return this builder
+ */
+ public B setIncludeTimeMillis(final boolean includeTimeMillis) {
+ this.includeTimeMillis = includeTimeMillis;
+ return asBuilder();
+ }
+
+ /**
* If "true", includes the stacktrace of any Throwable in the generated JSON, defaults to "true".
*
* @param includeStacktrace
@@ -166,7 +202,7 @@
/**
* Whether to format the stacktrace as a string, and not a nested object (optional, defaults to false).
- *
+ * @param stacktraceAsString true if the stacktrace should be formatted as a String.
* @return this builder
*/
public B setStacktraceAsString(final boolean stacktraceAsString) {
@@ -183,10 +219,10 @@
*/
public static class LogEventWithAdditionalFields {
- private final Object logEvent;
+ private final LogEvent logEvent;
private final Map<String, String> additionalFields;
- public LogEventWithAdditionalFields(final Object logEvent, final Map<String, String> additionalFields) {
+ public LogEventWithAdditionalFields(final LogEvent logEvent, final Map<String, String> additionalFields) {
this.logEvent = logEvent;
this.additionalFields = additionalFields;
}
@@ -195,7 +231,7 @@
return additionalFields;
}
- public Object getLogEvent() {
+ public LogEvent getLogEvent() {
return logEvent;
}
}
@@ -215,12 +251,7 @@
protected static final String DEFAULT_EOL = "\r\n";
protected static final String COMPACT_EOL = Strings.EMPTY;
- private static LogEvent convertMutableToLog4jEvent(final LogEvent event) {
- // TODO Jackson-based layouts have certain filters set up for Log4jLogEvent.
- // TODO Need to set up the same filters for MutableLogEvent but don't know how...
- // This is a workaround.
- return event instanceof Log4jLogEvent ? event : Log4jLogEvent.createMemento(event);
- }
+
private static ResolvableKeyValuePair[] prepareAdditionalFields(final Configuration config,
final KeyValuePair[] additionalFields) {
if (additionalFields == null || additionalFields.length == 0) {
@@ -258,30 +289,23 @@
protected final ResolvableKeyValuePair[] additionalFields;
- @Deprecated
- protected AbstractJacksonLayout(final Configuration config, final ObjectWriter objectWriter, final Charset charset,
- final boolean compact, final boolean complete, final boolean eventEol, final Serializer headerSerializer,
- final Serializer footerSerializer) {
- this(config, objectWriter, charset, compact, complete, eventEol, headerSerializer, footerSerializer, false);
- }
-
- @Deprecated
- protected AbstractJacksonLayout(final Configuration config, final ObjectWriter objectWriter, final Charset charset,
- final boolean compact, final boolean complete, final boolean eventEol, final Serializer headerSerializer,
- final Serializer footerSerializer, final boolean includeNullDelimiter) {
- this(config, objectWriter, charset, compact, complete, eventEol, headerSerializer, footerSerializer,
- includeNullDelimiter, null);
- }
-
protected AbstractJacksonLayout(final Configuration config, final ObjectWriter objectWriter, final Charset charset,
final boolean compact, final boolean complete, final boolean eventEol, final Serializer headerSerializer,
final Serializer footerSerializer, final boolean includeNullDelimiter,
final KeyValuePair[] additionalFields) {
+ this(config, objectWriter, charset, compact, complete, eventEol, null, headerSerializer, footerSerializer,
+ includeNullDelimiter, additionalFields);
+ }
+
+ protected AbstractJacksonLayout(final Configuration config, final ObjectWriter objectWriter, final Charset charset,
+ final boolean compact, final boolean complete, final boolean eventEol, final String endOfLine,
+ final Serializer headerSerializer, final Serializer footerSerializer, final boolean includeNullDelimiter,
+ final KeyValuePair[] additionalFields) {
super(config, charset, headerSerializer, footerSerializer);
this.objectWriter = objectWriter;
this.compact = compact;
this.complete = complete;
- this.eol = compact && !eventEol ? COMPACT_EOL : DEFAULT_EOL;
+ this.eol = endOfLine != null ? endOfLine : compact && !eventEol ? COMPACT_EOL : DEFAULT_EOL;
this.includeNullDelimiter = includeNullDelimiter;
this.additionalFields = prepareAdditionalFields(config, additionalFields);
}
@@ -330,7 +354,7 @@
}
public void toSerializable(final LogEvent event, final Writer writer) throws IOException {
- objectWriter.writeValue(writer, wrapLogEvent(convertMutableToLog4jEvent(event)));
+ objectWriter.writeValue(writer, wrapLogEvent(event));
writer.write(eol);
if (includeNullDelimiter) {
writer.write('\0');
@@ -344,8 +368,125 @@
final Map<String, String> additionalFieldsMap = resolveAdditionalFields(event);
// This class combines LogEvent with AdditionalFields during serialization
return createLogEventWithAdditionalFields(event, additionalFieldsMap);
+ } else if (event instanceof Message) {
+ // If the LogEvent implements the Message interface Jackson will not treat is as a LogEvent.
+ return new ReadOnlyLogEventWrapper(event);
+ // No additional fields, return original object
}
- // No additional fields, return original object
return event;
}
+ private static class ReadOnlyLogEventWrapper implements LogEvent {
+
+ @JsonIgnore
+ private final LogEvent event;
+
+ public ReadOnlyLogEventWrapper(LogEvent event) {
+ this.event = event;
+ }
+
+ @Override
+ public LogEvent toImmutable() {
+ return event.toImmutable();
+ }
+
+ @Override
+ public ReadOnlyStringMap getContextData() {
+ return event.getContextData();
+ }
+
+ @Override
+ public ThreadContext.ContextStack getContextStack() {
+ return event.getContextStack();
+ }
+
+ @Override
+ public String getLoggerFqcn() {
+ return event.getLoggerFqcn();
+ }
+
+ @Override
+ public Level getLevel() {
+ return event.getLevel();
+ }
+
+ @Override
+ public String getLoggerName() {
+ return event.getLoggerName();
+ }
+
+ @Override
+ public Marker getMarker() {
+ return event.getMarker();
+ }
+
+ @Override
+ public Message getMessage() {
+ return event.getMessage();
+ }
+
+ @Override
+ public long getTimeMillis() {
+ return event.getTimeMillis();
+ }
+
+ @Override
+ public Instant getInstant() {
+ return event.getInstant();
+ }
+
+ @Override
+ public StackTraceElement getSource() {
+ return event.getSource();
+ }
+
+ @Override
+ public String getThreadName() {
+ return event.getThreadName();
+ }
+
+ @Override
+ public long getThreadId() {
+ return event.getThreadId();
+ }
+
+ @Override
+ public int getThreadPriority() {
+ return event.getThreadPriority();
+ }
+
+ @Override
+ public Throwable getThrown() {
+ return event.getThrown();
+ }
+
+ @Override
+ public ThrowableProxy getThrownProxy() {
+ return event.getThrownProxy();
+ }
+
+ @Override
+ public boolean isEndOfBatch() {
+ return event.isEndOfBatch();
+ }
+
+ @Override
+ public boolean isIncludeLocation() {
+ return event.isIncludeLocation();
+ }
+
+ @Override
+ public void setEndOfBatch(boolean endOfBatch) {
+
+ }
+
+ @Override
+ public void setIncludeLocation(boolean locationRequired) {
+
+ }
+
+ @Override
+ public long getNanoTime() {
+ return event.getNanoTime();
+ }
+ }
}
diff --git a/log4j-layout-jackson/src/main/java/org/apache/logging/log4j/jackson/AbstractLogEventMixIn.java b/log4j-layout-jackson/src/main/java/org/apache/logging/log4j/jackson/AbstractLogEventMixIn.java
index 43784d6..059c09d 100644
--- a/log4j-layout-jackson/src/main/java/org/apache/logging/log4j/jackson/AbstractLogEventMixIn.java
+++ b/log4j-layout-jackson/src/main/java/org/apache/logging/log4j/jackson/AbstractLogEventMixIn.java
@@ -16,8 +16,6 @@
*/
package org.apache.logging.log4j.jackson;
-import java.util.Map;
-
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.message.Message;
@@ -53,11 +51,6 @@
private static final long serialVersionUID = 1L;
- @Deprecated
- @Override
- @JsonIgnore
- public abstract Map<String, String> getContextMap();
-
@JsonSerialize(using = MessageSerializer.class)
@JsonDeserialize(using = SimpleMessageDeserializer.class)
@Override
diff --git a/log4j-layout-jackson/src/main/java/org/apache/logging/log4j/jackson/JsonConstants.java b/log4j-layout-jackson/src/main/java/org/apache/logging/log4j/jackson/JsonConstants.java
index 1db6f52..e28941b 100644
--- a/log4j-layout-jackson/src/main/java/org/apache/logging/log4j/jackson/JsonConstants.java
+++ b/log4j-layout-jackson/src/main/java/org/apache/logging/log4j/jackson/JsonConstants.java
@@ -33,4 +33,5 @@
public static final String ELT_EXTENDED_STACK_TRACE = "extendedStackTrace";
public static final String ELT_NANO_TIME = "nanoTime";
public static final String ELT_INSTANT = "instant";
+ public static final String ELT_TIME_MILLIS = "timeMillis";
}
diff --git a/log4j-layout-jackson/src/main/java/org/apache/logging/log4j/jackson/Log4jStackTraceElementDeserializer.java b/log4j-layout-jackson/src/main/java/org/apache/logging/log4j/jackson/Log4jStackTraceElementDeserializer.java
index 7d03805..f155a49 100644
--- a/log4j-layout-jackson/src/main/java/org/apache/logging/log4j/jackson/Log4jStackTraceElementDeserializer.java
+++ b/log4j-layout-jackson/src/main/java/org/apache/logging/log4j/jackson/Log4jStackTraceElementDeserializer.java
@@ -79,4 +79,4 @@
}
throw ctxt.mappingException(this._valueClass, t);
}
-}
\ No newline at end of file
+}
diff --git a/log4j-layout-jackson/src/main/java/org/apache/logging/log4j/jackson/LogEventJsonMixIn.java b/log4j-layout-jackson/src/main/java/org/apache/logging/log4j/jackson/LogEventJsonMixIn.java
index e9607bd..034c40a 100644
--- a/log4j-layout-jackson/src/main/java/org/apache/logging/log4j/jackson/LogEventJsonMixIn.java
+++ b/log4j-layout-jackson/src/main/java/org/apache/logging/log4j/jackson/LogEventJsonMixIn.java
@@ -16,8 +16,6 @@
*/
package org.apache.logging.log4j.jackson;
-import java.util.Map;
-
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.Marker;
import org.apache.logging.log4j.ThreadContext.ContextStack;
@@ -67,10 +65,6 @@
@Override
public abstract ReadOnlyStringMap getContextData();
- @Override
- @JsonIgnore
- public abstract Map<String, String> getContextMap();
-
@JsonProperty(JsonConstants.ELT_CONTEXT_STACK)
@Override
public abstract ContextStack getContextStack();
@@ -125,7 +119,7 @@
@Override
public abstract ThrowableProxy getThrownProxy();
- @JsonIgnore // ignore from 2.11
+ @JsonProperty(value = JsonConstants.ELT_TIME_MILLIS, access = JsonProperty.Access.READ_ONLY)
@Override
public abstract long getTimeMillis();
diff --git a/log4j-layout-jackson/src/main/java/org/apache/logging/log4j/jackson/LogEventWithContextListMixIn.java b/log4j-layout-jackson/src/main/java/org/apache/logging/log4j/jackson/LogEventWithContextListMixIn.java
index cd0639b..2c26489 100644
--- a/log4j-layout-jackson/src/main/java/org/apache/logging/log4j/jackson/LogEventWithContextListMixIn.java
+++ b/log4j-layout-jackson/src/main/java/org/apache/logging/log4j/jackson/LogEventWithContextListMixIn.java
@@ -16,8 +16,6 @@
*/
package org.apache.logging.log4j.jackson;
-import java.util.Map;
-
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.Marker;
import org.apache.logging.log4j.ThreadContext.ContextStack;
@@ -50,10 +48,6 @@
@Override
public abstract ReadOnlyStringMap getContextData();
- @Override
- @JsonIgnore
- public abstract Map<String, String> getContextMap();
-
@JsonProperty(JsonConstants.ELT_CONTEXT_STACK)
@Override
public abstract ContextStack getContextStack();
@@ -109,7 +103,7 @@
@Override
public abstract ThrowableProxy getThrownProxy();
- @JsonIgnore // ignore from 2.11
+ @JsonProperty(value = JsonConstants.ELT_TIME_MILLIS, access = JsonProperty.Access.READ_ONLY)
@Override
public abstract long getTimeMillis();
diff --git a/log4j-layout-jackson/src/main/java/org/apache/logging/log4j/jackson/MapEntry.java b/log4j-layout-jackson/src/main/java/org/apache/logging/log4j/jackson/MapEntry.java
index fc02180..998751e 100644
--- a/log4j-layout-jackson/src/main/java/org/apache/logging/log4j/jackson/MapEntry.java
+++ b/log4j-layout-jackson/src/main/java/org/apache/logging/log4j/jackson/MapEntry.java
@@ -106,4 +106,4 @@
public String toString() {
return Strings.EMPTY + this.getKey() + "=" + this.getValue();
}
-}
\ No newline at end of file
+}
diff --git a/log4j-layout-jackson/src/main/java/org/apache/logging/log4j/jackson/SetupContextInitializer.java b/log4j-layout-jackson/src/main/java/org/apache/logging/log4j/jackson/SetupContextInitializer.java
index 3d73d93..b644a6e 100644
--- a/log4j-layout-jackson/src/main/java/org/apache/logging/log4j/jackson/SetupContextInitializer.java
+++ b/log4j-layout-jackson/src/main/java/org/apache/logging/log4j/jackson/SetupContextInitializer.java
@@ -35,4 +35,4 @@
: ThrowableProxyWithoutStacktraceMixIn.class);
}
-}
\ No newline at end of file
+}
diff --git a/log4j-layout-jackson/src/main/java/org/apache/logging/log4j/jackson/SimpleModuleInitializer.java b/log4j-layout-jackson/src/main/java/org/apache/logging/log4j/jackson/SimpleModuleInitializer.java
index edae1bc..562f76d 100644
--- a/log4j-layout-jackson/src/main/java/org/apache/logging/log4j/jackson/SimpleModuleInitializer.java
+++ b/log4j-layout-jackson/src/main/java/org/apache/logging/log4j/jackson/SimpleModuleInitializer.java
@@ -23,4 +23,4 @@
}
simpleModule.addSerializer(Message.class, new MessageSerializer());
}
-}
\ No newline at end of file
+}
diff --git a/log4j-layout-jackson/src/main/java/org/apache/logging/log4j/jackson/XmlConstants.java b/log4j-layout-jackson/src/main/java/org/apache/logging/log4j/jackson/XmlConstants.java
index 10dc37d..f736ca8 100644
--- a/log4j-layout-jackson/src/main/java/org/apache/logging/log4j/jackson/XmlConstants.java
+++ b/log4j-layout-jackson/src/main/java/org/apache/logging/log4j/jackson/XmlConstants.java
@@ -27,6 +27,7 @@
public static final String ELT_EVENT = "Event";
public static final String ELT_EXTENDED_STACK_TRACE = "ExtendedStackTrace";
public static final String ELT_EXTENDED_STACK_TRACE_ITEM = "ExtendedStackTraceItem";
+ public static final String ELT_TIME_MILLIS = "TimeMillis";
public static final String ELT_INSTANT = "Instant";
public static final String ELT_MARKER = "Marker";
public static final String ELT_MESSAGE = "Message";
diff --git a/log4j-layout-jackson/src/main/java/org/apache/logging/log4j/jackson/layout/AbstractJacksonLayout.java b/log4j-layout-jackson/src/main/java/org/apache/logging/log4j/jackson/layout/AbstractJacksonLayout.java
index 175a470..9dd7c83 100644
--- a/log4j-layout-jackson/src/main/java/org/apache/logging/log4j/jackson/layout/AbstractJacksonLayout.java
+++ b/log4j-layout-jackson/src/main/java/org/apache/logging/log4j/jackson/layout/AbstractJacksonLayout.java
@@ -42,8 +42,8 @@
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.config.Configuration;
-import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
-import org.apache.logging.log4j.core.config.plugins.PluginElement;
+import org.apache.logging.log4j.plugins.PluginBuilderAttribute;
+import org.apache.logging.log4j.plugins.PluginElement;
import org.apache.logging.log4j.core.impl.Log4jLogEvent;
import org.apache.logging.log4j.core.layout.AbstractStringLayout;
import org.apache.logging.log4j.core.lookup.StrSubstitutor;
@@ -67,6 +67,9 @@
private boolean eventEol;
@PluginBuilderAttribute
+ private String endOfLine;
+
+ @PluginBuilderAttribute
private boolean compact;
@PluginBuilderAttribute
@@ -98,6 +101,10 @@
return eventEol;
}
+ public String getEndOfLine() {
+ return endOfLine;
+ }
+
public boolean isCompact() {
return compact;
}
@@ -156,6 +163,11 @@
return asBuilder();
}
+ public B setEndOfLine(final String endOfLine) {
+ this.endOfLine = endOfLine;
+ return asBuilder();
+ }
+
/**
* Whether to include NULL byte as delimiter after each event (optional, default to false).
*
@@ -283,30 +295,24 @@
protected final ResolvableKeyValuePair[] additionalFields;
- @Deprecated
- protected AbstractJacksonLayout(final Configuration config, final ObjectWriter objectWriter, final Charset charset,
- final boolean compact, final boolean complete, final boolean eventEol, final Serializer headerSerializer,
- final Serializer footerSerializer) {
- this(config, objectWriter, charset, compact, complete, eventEol, headerSerializer, footerSerializer, false);
- }
-
- @Deprecated
- protected AbstractJacksonLayout(final Configuration config, final ObjectWriter objectWriter, final Charset charset,
- final boolean compact, final boolean complete, final boolean eventEol, final Serializer headerSerializer,
- final Serializer footerSerializer, final boolean includeNullDelimiter) {
- this(config, objectWriter, charset, compact, complete, eventEol, headerSerializer, footerSerializer,
- includeNullDelimiter, null);
- }
-
protected AbstractJacksonLayout(final Configuration config, final ObjectWriter objectWriter, final Charset charset,
final boolean compact, final boolean complete, final boolean eventEol, final Serializer headerSerializer,
final Serializer footerSerializer, final boolean includeNullDelimiter,
final KeyValuePair[] additionalFields) {
+ this(config, objectWriter, charset, compact, complete, eventEol, null,
+ headerSerializer, footerSerializer, includeNullDelimiter, additionalFields);
+ }
+
+
+ protected AbstractJacksonLayout(final Configuration config, final ObjectWriter objectWriter, final Charset charset,
+ final boolean compact, final boolean complete, final boolean eventEol, String endOfLine,
+ final Serializer headerSerializer, final Serializer footerSerializer, final boolean includeNullDelimiter,
+ final KeyValuePair[] additionalFields) {
super(config, charset, headerSerializer, footerSerializer);
this.objectWriter = objectWriter;
this.compact = compact;
this.complete = complete;
- this.eol = compact && !eventEol ? COMPACT_EOL : DEFAULT_EOL;
+ this.eol = endOfLine != null ? endOfLine : compact && !eventEol ? COMPACT_EOL : DEFAULT_EOL;
this.includeNullDelimiter = includeNullDelimiter;
this.additionalFields = prepareAdditionalFields(config, additionalFields);
}
diff --git a/log4j-layout-jackson/src/site/manual/index.md b/log4j-layout-jackson/src/site/manual/index.md
index b1d22b6..8f3b15d 100644
--- a/log4j-layout-jackson/src/site/manual/index.md
+++ b/log4j-layout-jackson/src/site/manual/index.md
@@ -18,7 +18,7 @@
# Apache Log4j Layout for Jackson module
-As of Log4j 3.0.0, common code for layouts based on Jackson have moved from the existing module logj-core to the new modules log4j-layout-jackson.
+As of Log4j 3.0.0, common code for layouts based on Jackson have moved from the existing module log4j-core to the new modules log4j-layout-jackson.
## Requirements
diff --git a/log4j-layout-json-template/pom.xml b/log4j-layout-json-template/pom.xml
new file mode 100644
index 0000000..f9e74d6
--- /dev/null
+++ b/log4j-layout-json-template/pom.xml
@@ -0,0 +1,552 @@
+<?xml version="1.0" encoding="UTF-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 "License"); you may not use this file except in compliance with
+ ~ the License. You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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 xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j</artifactId>
+ <version>3.0.0-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>log4j-layout-json-template</artifactId>
+ <name>Apache Log4j Layout for JSON template</name>
+ <description>
+ Apache Log4j Layout for JSON template.
+ </description>
+
+ <properties>
+ <log4jParentDir>${basedir}/..</log4jParentDir>
+ <docLabel>Log4j Layout for JSON Template Documentation</docLabel>
+ <projectDir>/log4j-layout-json-template</projectDir>
+ <module.name>org.apache.logging.log4j.layout.json.template</module.name>
+ </properties>
+
+ <dependencies>
+
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-core</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-core</artifactId>
+ <version>${project.version}</version>
+ <type>test-jar</type>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-layout-jackson-json</artifactId>
+ <version>${project.version}</version>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.jctools</groupId>
+ <artifactId>jctools-core</artifactId>
+ <optional>true</optional>
+ </dependency>
+
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.assertj</groupId>
+ <artifactId>assertj-core</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>com.fasterxml.jackson.core</groupId>
+ <artifactId>jackson-databind</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>co.elastic.logging</groupId>
+ <artifactId>log4j2-ecs-layout</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>com.google.code.java-allocation-instrumenter</groupId>
+ <artifactId>java-allocation-instrumenter</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.elasticsearch.client</groupId>
+ <artifactId>elasticsearch-rest-high-level-client</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.awaitility</groupId>
+ <artifactId>awaitility</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ </dependencies>
+
+ <build>
+ <plugins>
+
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <configuration>
+ <instructions>
+ <Fragment-Host>org.apache.logging.log4j.layout.json.template</Fragment-Host>
+ <Export-Package>*</Export-Package>
+ </instructions>
+ </configuration>
+ </plugin>
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jar-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>default-jar</id>
+ <goals>
+ <goal>jar</goal>
+ </goals>
+ <configuration combine.self="override">
+ <archive>
+ <manifestFile>${manifestfile}</manifestFile>
+ <manifestEntries>
+ <Specification-Title>${project.name}</Specification-Title>
+ <Specification-Version>${project.version}</Specification-Version>
+ <Specification-Vendor>${project.organization.name}</Specification-Vendor>
+ <Implementation-Title>${project.name}</Implementation-Title>
+ <Implementation-Version>${project.version}</Implementation-Version>
+ <Implementation-Vendor>${project.organization.name}</Implementation-Vendor>
+ <Implementation-Vendor-Id>org.apache</Implementation-Vendor-Id>
+ <X-Compile-Source-JDK>${maven.compiler.source}</X-Compile-Source-JDK>
+ <X-Compile-Target-JDK>${maven.compiler.target}</X-Compile-Target-JDK>
+ <Multi-Release>true</Multi-Release>
+ </manifestEntries>
+ </archive>
+ </configuration>
+ </execution>
+ <execution>
+ <id>default</id>
+ <goals>
+ <goal>test-jar</goal>
+ </goals>
+ <configuration>
+ <archive>
+ <manifestFile>${manifestfile}</manifestFile>
+ <manifestEntries>
+ <Specification-Title>${project.name}</Specification-Title>
+ <Specification-Version>${project.version}</Specification-Version>
+ <Specification-Vendor>${project.organization.name}</Specification-Vendor>
+ <Implementation-Title>${project.name}</Implementation-Title>
+ <Implementation-Version>${project.version}</Implementation-Version>
+ <Implementation-Vendor>${project.organization.name}</Implementation-Vendor>
+ <Implementation-Vendor-Id>org.apache</Implementation-Vendor-Id>
+ <X-Compile-Source-JDK>${maven.compiler.source}</X-Compile-Source-JDK>
+ <X-Compile-Target-JDK>${maven.compiler.target}</X-Compile-Target-JDK>
+ </manifestEntries>
+ </archive>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <skip>${maven.test.skip}</skip>
+ <excludes>
+ <exclude>**/JsonTemplateLayoutConcurrentEncodeTest.java</exclude>
+ <exclude>**/JsonTemplateLayoutTest.java</exclude>
+ </excludes>
+ <!-- Enforcing a non-UTF-8 encoding to check that the layout
+ indeed handles everything in UTF-8 without implicitly
+ relying on the system defaults. -->
+ <argLine>-Dfile.encoding=US-ASCII</argLine>
+ </configuration>
+ <executions>
+ <!-- Dummy recycler execution -->
+ <execution>
+ <id>recycler-dummy</id>
+ <goals>
+ <goal>test</goal>
+ </goals>
+ <configuration>
+ <skip>${skipTests}</skip>
+ <systemPropertyVariables>
+ <log4j2.layout.jsonTemplate.recyclerFactory>threadLocal</log4j2.layout.jsonTemplate.recyclerFactory>
+ </systemPropertyVariables>
+ <includes>
+ <include>**/JsonTemplateLayoutConcurrentEncodeTest.java</include>
+ <include>**/JsonTemplateLayoutTest.java</include>
+ </includes>
+ </configuration>
+ </execution>
+ <!-- Thread-Local recycler execution -->
+ <execution>
+ <id>recycler-tl</id>
+ <goals>
+ <goal>test</goal>
+ </goals>
+ <configuration>
+ <skip>${skipTests}</skip>
+ <systemPropertyVariables>
+ <log4j2.layout.jsonTemplate.recyclerFactory>threadLocal</log4j2.layout.jsonTemplate.recyclerFactory>
+ </systemPropertyVariables>
+ <includes>
+ <include>**/JsonTemplateLayoutConcurrentEncodeTest.java</include>
+ <include>**/JsonTemplateLayoutTest.java</include>
+ </includes>
+ </configuration>
+ </execution>
+ <!-- ArrayBlockingQueue recycler execution -->
+ <execution>
+ <id>recycler-abq</id>
+ <goals>
+ <goal>test</goal>
+ </goals>
+ <configuration>
+ <skip>${skipTests}</skip>
+ <systemPropertyVariables>
+ <log4j2.layout.jsonTemplate.recyclerFactory>queue:supplier=java.util.concurrent.ArrayBlockingQueue.new</log4j2.layout.jsonTemplate.recyclerFactory>
+ </systemPropertyVariables>
+ <includes>
+ <include>**/JsonTemplateLayoutConcurrentEncodeTest.java</include>
+ <include>**/JsonTemplateLayoutTest.java</include>
+ </includes>
+ </configuration>
+ </execution>
+ <!-- MpmcArrayQueue recycler execution -->
+ <execution>
+ <id>recycler-mpmc</id>
+ <goals>
+ <goal>test</goal>
+ </goals>
+ <configuration>
+ <skip>${skipTests}</skip>
+ <systemPropertyVariables>
+ <log4j2.layout.jsonTemplate.recyclerFactory>queue:supplier=org.jctools.queues.MpmcArrayQueue.new</log4j2.layout.jsonTemplate.recyclerFactory>
+ </systemPropertyVariables>
+ <includes>
+ <include>**/JsonTemplateLayoutConcurrentEncodeTest.java</include>
+ <include>**/JsonTemplateLayoutTest.java</include>
+ </includes>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+
+ <!-- Disable ITs, which are Docker-dependent, by default. -->
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-failsafe-plugin</artifactId>
+ <executions>
+ <execution>
+ <goals>
+ <goal>integration-test</goal>
+ <goal>verify</goal>
+ </goals>
+ </execution>
+ </executions>
+ <configuration>
+ <skip>true</skip>
+ </configuration>
+ </plugin>
+
+ </plugins>
+ </build>
+
+ <profiles>
+ <profile>
+ <id>docker</id>
+ <activation>
+ <activeByDefault>false</activeByDefault>
+ </activation>
+ <build>
+ <plugins>
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-failsafe-plugin</artifactId>
+ <executions>
+ <execution>
+ <goals>
+ <goal>integration-test</goal>
+ <goal>verify</goal>
+ </goals>
+ </execution>
+ </executions>
+ <configuration>
+ <skip>${skipTests}</skip>
+ <includes>
+ <include>**/*IT.java</include>
+ </includes>
+ </configuration>
+ </plugin>
+
+ <plugin>
+ <groupId>io.fabric8</groupId>
+ <artifactId>docker-maven-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>start</id>
+ <phase>pre-integration-test</phase>
+ <goals>
+ <goal>start</goal>
+ </goals>
+ </execution>
+ <execution>
+ <id>stop</id>
+ <phase>post-integration-test</phase>
+ <goals>
+ <goal>stop</goal>
+ </goals>
+ </execution>
+ </executions>
+ <configuration>
+ <verbose>all</verbose>
+ <startParallel>true</startParallel>
+ <autoCreateCustomNetworks>true</autoCreateCustomNetworks>
+ <images>
+ <image>
+ <alias>elasticsearch</alias>
+ <name>elasticsearch:${elastic.version}</name>
+ <run>
+ <env>
+ <discovery.type>single-node</discovery.type>
+ </env>
+ <ports>
+ <port>9200:9200</port>
+ </ports>
+ <network>
+ <mode>custom</mode>
+ <name>log4j-layout-json-template-network</name>
+ <alias>elasticsearch</alias>
+ </network>
+ <log>
+ <prefix>[ES]</prefix>
+ <color>cyan</color>
+ </log>
+ <wait>
+ <log>recovered \[0\] indices into cluster_state</log>
+ <time>60000</time>
+ </wait>
+ </run>
+ </image>
+ <image>
+ <alias>logstash</alias>
+ <name>logstash:${elastic.version}</name>
+ <run>
+ <dependsOn>
+ <container>elasticsearch</container>
+ </dependsOn>
+ <network>
+ <mode>custom</mode>
+ <name>log4j-layout-json-template-network</name>
+ <alias>logstash</alias>
+ </network>
+ <ports>
+ <port>12222:12222</port>
+ <port>12345:12345</port>
+ </ports>
+ <log>
+ <prefix>[LS]</prefix>
+ <color>green</color>
+ </log>
+ <entrypoint>
+ <exec>
+ <arg>logstash</arg>
+ <arg>--pipeline.batch.size</arg>
+ <arg>1</arg>
+ <arg>-e</arg>
+ <arg>
+ input {
+ gelf {
+ host => "logstash"
+ use_tcp => true
+ use_udp => false
+ port => 12222
+ type => "gelf"
+ }
+ tcp {
+ port => 12345
+ codec => json
+ type => "tcp"
+ }
+ }
+
+ filter {
+ if [type] == "gelf" {
+ # These are GELF/Syslog logging levels as defined in RFC 3164.
+ # Map the integer level to its human readable format.
+ translate {
+ field => "[level]"
+ destination => "[levelName]"
+ dictionary => {
+ "0" => "EMERG"
+ "1" => "ALERT"
+ "2" => "CRITICAL"
+ "3" => "ERROR"
+ "4" => "WARN"
+ "5" => "NOTICE"
+ "6" => "INFO"
+ "7" => "DEBUG"
+ }
+ }
+ }
+ }
+
+ output {
+ # (Un)comment for debugging purposes
+ # stdout { codec => rubydebug }
+ elasticsearch {
+ hosts => ["http://elasticsearch:9200"]
+ index => "log4j"
+ }
+ }
+ </arg>
+ </exec>
+ </entrypoint>
+ <wait>
+ <log>Successfully started Logstash API endpoint</log>
+ <time>60000</time>
+ </wait>
+ </run>
+ </image>
+ </images>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ </profile>
+ </profiles>
+
+ <reporting>
+ <plugins>
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-changes-plugin</artifactId>
+ <version>${changes.plugin.version}</version>
+ <reportSets>
+ <reportSet>
+ <reports>
+ <report>changes-report</report>
+ </reports>
+ </reportSet>
+ </reportSets>
+ <configuration>
+ <issueLinkTemplate>%URL%/show_bug.cgi?id=%ISSUE%</issueLinkTemplate>
+ <useJql>true</useJql>
+ </configuration>
+ </plugin>
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-checkstyle-plugin</artifactId>
+ <version>${checkstyle.plugin.version}</version>
+ <configuration>
+ <!--<propertiesLocation>${vfs.parent.dir}/checkstyle.properties</propertiesLocation> -->
+ <configLocation>${log4jParentDir}/checkstyle.xml</configLocation>
+ <suppressionsLocation>${log4jParentDir}/checkstyle-suppressions.xml</suppressionsLocation>
+ <enableRulesSummary>false</enableRulesSummary>
+ <propertyExpansion>basedir=${basedir}</propertyExpansion>
+ <propertyExpansion>licensedir=${log4jParentDir}/checkstyle-header.txt</propertyExpansion>
+ </configuration>
+ </plugin>
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-javadoc-plugin</artifactId>
+ <version>${javadoc.plugin.version}</version>
+ <configuration>
+ <bottom><![CDATA[<p align="center">Copyright © {inceptionYear}-{currentYear} {organizationName}. All Rights Reserved.<br />
+ Apache Logging, Apache Log4j, Log4j, Apache, the Apache feather logo, the Apache Logging project logo,
+ and the Apache Log4j logo are trademarks of The Apache Software Foundation.</p>]]></bottom>
+ <!-- module link generation is completely broken in the javadoc plugin for a multi-module non-aggregating project -->
+ <detectOfflineLinks>false</detectOfflineLinks>
+ <linksource>true</linksource>
+ </configuration>
+ <reportSets>
+ <reportSet>
+ <id>non-aggregate</id>
+ <reports>
+ <report>javadoc</report>
+ </reports>
+ </reportSet>
+ </reportSets>
+ </plugin>
+
+ <plugin>
+ <groupId>com.github.spotbugs</groupId>
+ <artifactId>spotbugs-maven-plugin</artifactId>
+ <configuration>
+ <fork>true</fork>
+ <jvmArgs>-Duser.language=en</jvmArgs>
+ <threshold>Normal</threshold>
+ <effort>Default</effort>
+ <excludeFilterFile>${log4jParentDir}/spotbugs-exclude-filter.xml</excludeFilterFile>
+ </configuration>
+ </plugin>
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jxr-plugin</artifactId>
+ <version>${jxr.plugin.version}</version>
+ <reportSets>
+ <reportSet>
+ <id>non-aggregate</id>
+ <reports>
+ <report>jxr</report>
+ </reports>
+ </reportSet>
+ <reportSet>
+ <id>aggregate</id>
+ <reports>
+ <report>aggregate</report>
+ </reports>
+ </reportSet>
+ </reportSets>
+ </plugin>
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-pmd-plugin</artifactId>
+ <version>${pmd.plugin.version}</version>
+ <configuration>
+ <targetJdk>${maven.compiler.target}</targetJdk>
+ </configuration>
+ </plugin>
+
+ </plugins>
+ </reporting>
+
+</project>
diff --git a/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/JsonTemplateLayout.java b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/JsonTemplateLayout.java
new file mode 100644
index 0000000..355816e
--- /dev/null
+++ b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/JsonTemplateLayout.java
@@ -0,0 +1,696 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.logging.log4j.layout.json.template;
+
+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.Configuration;
+import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
+import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
+import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
+import org.apache.logging.log4j.core.config.plugins.PluginElement;
+import org.apache.logging.log4j.core.layout.ByteBufferDestination;
+import org.apache.logging.log4j.core.layout.Encoder;
+import org.apache.logging.log4j.core.layout.LockingStringBuilderEncoder;
+import org.apache.logging.log4j.core.lookup.StrSubstitutor;
+import org.apache.logging.log4j.core.util.Constants;
+import org.apache.logging.log4j.core.util.KeyValuePair;
+import org.apache.logging.log4j.core.util.StringEncoder;
+import org.apache.logging.log4j.layout.json.template.resolver.EventResolverContext;
+import org.apache.logging.log4j.layout.json.template.resolver.StackTraceElementObjectResolverContext;
+import org.apache.logging.log4j.layout.json.template.resolver.TemplateResolver;
+import org.apache.logging.log4j.layout.json.template.resolver.TemplateResolvers;
+import org.apache.logging.log4j.layout.json.template.util.JsonWriter;
+import org.apache.logging.log4j.layout.json.template.util.Recycler;
+import org.apache.logging.log4j.layout.json.template.util.RecyclerFactory;
+import org.apache.logging.log4j.layout.json.template.util.Uris;
+import org.apache.logging.log4j.plugins.Node;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.util.Strings;
+
+import java.nio.charset.Charset;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Objects;
+import java.util.function.Supplier;
+
+@Plugin(name = "JsonTemplateLayout",
+ category = Node.CATEGORY,
+ elementType = Layout.ELEMENT_TYPE)
+public class JsonTemplateLayout implements StringLayout {
+
+ private static final Map<String, String> CONTENT_FORMAT =
+ Collections.singletonMap("version", "1");
+
+ private final Charset charset;
+
+ private final String contentType;
+
+ private final TemplateResolver<LogEvent> eventResolver;
+
+ private final String eventDelimiter;
+
+ private final Recycler<Context> contextRecycler;
+
+ // The class and fields are visible for tests.
+ static final class Context implements AutoCloseable {
+
+ final JsonWriter jsonWriter;
+
+ final Encoder<StringBuilder> encoder;
+
+ private Context(
+ final JsonWriter jsonWriter,
+ final Encoder<StringBuilder> encoder) {
+ this.jsonWriter = jsonWriter;
+ this.encoder = encoder;
+ }
+
+ @Override
+ public void close() {
+ jsonWriter.close();
+ }
+
+ }
+
+ private JsonTemplateLayout(final Builder builder) {
+ this.charset = builder.charset;
+ this.contentType = "application/json; charset=" + charset;
+ final String eventDelimiterSuffix = builder.isNullEventDelimiterEnabled() ? "\0" : "";
+ this.eventDelimiter = builder.eventDelimiter + eventDelimiterSuffix;
+ final Configuration configuration = builder.configuration;
+ final StrSubstitutor substitutor = configuration.getStrSubstitutor();
+ final JsonWriter jsonWriter = JsonWriter
+ .newBuilder()
+ .setMaxStringLength(builder.maxStringLength)
+ .setTruncatedStringSuffix(builder.truncatedStringSuffix)
+ .build();
+ final TemplateResolver<StackTraceElement> stackTraceElementObjectResolver =
+ builder.stackTraceEnabled
+ ? createStackTraceElementResolver(builder, substitutor, jsonWriter)
+ : null;
+ this.eventResolver = createEventResolver(
+ builder,
+ configuration,
+ substitutor,
+ charset,
+ jsonWriter,
+ stackTraceElementObjectResolver);
+ this.contextRecycler = createContextRecycler(builder, jsonWriter);
+ }
+
+ private static TemplateResolver<StackTraceElement> createStackTraceElementResolver(
+ final Builder builder,
+ final StrSubstitutor substitutor,
+ final JsonWriter jsonWriter) {
+ final StackTraceElementObjectResolverContext stackTraceElementObjectResolverContext =
+ StackTraceElementObjectResolverContext
+ .newBuilder()
+ .setSubstitutor(substitutor)
+ .setJsonWriter(jsonWriter)
+ .build();
+ final String stackTraceElementTemplate = readStackTraceElementTemplate(builder);
+ return TemplateResolvers.ofTemplate(stackTraceElementObjectResolverContext, stackTraceElementTemplate);
+ }
+
+ private TemplateResolver<LogEvent> createEventResolver(
+ final Builder builder,
+ final Configuration configuration,
+ final StrSubstitutor substitutor,
+ final Charset charset,
+ final JsonWriter jsonWriter,
+ final TemplateResolver<StackTraceElement> stackTraceElementObjectResolver) {
+ final String eventTemplate = readEventTemplate(builder);
+ final float maxByteCountPerChar = builder.charset.newEncoder().maxBytesPerChar();
+ final int maxStringByteCount =
+ Math.toIntExact(Math.round(
+ maxByteCountPerChar * builder.maxStringLength));
+ final EventResolverContext resolverContext = EventResolverContext
+ .newBuilder()
+ .setConfiguration(configuration)
+ .setSubstitutor(substitutor)
+ .setCharset(charset)
+ .setJsonWriter(jsonWriter)
+ .setRecyclerFactory(builder.recyclerFactory)
+ .setMaxStringByteCount(maxStringByteCount)
+ .setLocationInfoEnabled(builder.locationInfoEnabled)
+ .setStackTraceEnabled(builder.stackTraceEnabled)
+ .setStackTraceElementObjectResolver(stackTraceElementObjectResolver)
+ .setEventTemplateAdditionalFields(builder.eventTemplateAdditionalFields.additionalFields)
+ .build();
+ return TemplateResolvers.ofTemplate(resolverContext, eventTemplate);
+ }
+
+ private static String readEventTemplate(final Builder builder) {
+ return readTemplate(
+ builder.eventTemplate,
+ builder.eventTemplateUri,
+ builder.charset);
+ }
+
+ private static String readStackTraceElementTemplate(final Builder builder) {
+ return readTemplate(
+ builder.stackTraceElementTemplate,
+ builder.stackTraceElementTemplateUri,
+ builder.charset);
+ }
+
+ private static String readTemplate(
+ final String template,
+ final String templateUri,
+ final Charset charset) {
+ return Strings.isBlank(template)
+ ? Uris.readUri(templateUri, charset)
+ : template;
+ }
+
+ private static Recycler<Context> createContextRecycler(
+ final Builder builder,
+ final JsonWriter jsonWriter) {
+ final Supplier<Context> supplier =
+ createContextSupplier(builder.charset, jsonWriter);
+ return builder
+ .recyclerFactory
+ .create(supplier, Context::close);
+ }
+
+ private static Supplier<Context> createContextSupplier(
+ final Charset charset,
+ final JsonWriter jsonWriter) {
+ return () -> {
+ final JsonWriter clonedJsonWriter = jsonWriter.clone();
+ final Encoder<StringBuilder> encoder =
+ Constants.ENABLE_DIRECT_ENCODERS
+ ? new LockingStringBuilderEncoder(charset)
+ : null;
+ return new Context(clonedJsonWriter, encoder);
+ };
+ }
+
+ @Override
+ public byte[] toByteArray(final LogEvent event) {
+ final String eventJson = toSerializable(event);
+ return StringEncoder.toBytes(eventJson, charset);
+ }
+
+ @Override
+ public String toSerializable(final LogEvent event) {
+ final Context context = acquireContext();
+ final JsonWriter jsonWriter = context.jsonWriter;
+ final StringBuilder stringBuilder = jsonWriter.getStringBuilder();
+ try {
+ eventResolver.resolve(event, jsonWriter);
+ stringBuilder.append(eventDelimiter);
+ return stringBuilder.toString();
+ } finally {
+ contextRecycler.release(context);
+ }
+ }
+
+ @Override
+ public void encode(final LogEvent event, final ByteBufferDestination destination) {
+
+ // Acquire a context.
+ final Context context = acquireContext();
+ final JsonWriter jsonWriter = context.jsonWriter;
+ final StringBuilder stringBuilder = jsonWriter.getStringBuilder();
+ final Encoder<StringBuilder> encoder = context.encoder;
+
+ try {
+
+ // Render the JSON.
+ eventResolver.resolve(event, jsonWriter);
+ if (eventDelimiter != null && eventDelimiter.equalsIgnoreCase("null")) {
+ stringBuilder.append('\0');
+ } else {
+ stringBuilder.append(eventDelimiter);
+ }
+
+ // Write to the destination.
+ if (encoder == null) {
+ final String eventJson = stringBuilder.toString();
+ final byte[] eventJsonBytes = StringEncoder.toBytes(eventJson, charset);
+ destination.writeBytes(eventJsonBytes, 0, eventJsonBytes.length);
+ } else {
+ encoder.encode(stringBuilder, destination);
+ }
+
+ }
+
+ // Release the context.
+ finally {
+ contextRecycler.release(context);
+ }
+
+ }
+
+ // Visible for tests.
+ Context acquireContext() {
+ return contextRecycler.acquire();
+ }
+
+ @Override
+ public byte[] getFooter() {
+ return null;
+ }
+
+ @Override
+ public byte[] getHeader() {
+ return null;
+ }
+
+ @Override
+ public Charset getCharset() {
+ return charset;
+ }
+
+ @Override
+ public String getContentType() {
+ return contentType;
+ }
+
+ @Override
+ public Map<String, String> getContentFormat() {
+ return CONTENT_FORMAT;
+ }
+
+ @PluginBuilderFactory
+ @SuppressWarnings("WeakerAccess")
+ public static Builder newBuilder() {
+ return new Builder();
+ }
+
+ @SuppressWarnings({"unused", "WeakerAccess"})
+ public static final class Builder
+ implements org.apache.logging.log4j.core.util.Builder<JsonTemplateLayout> {
+
+ @PluginConfiguration
+ private Configuration configuration;
+
+ @PluginBuilderAttribute
+ private Charset charset = JsonTemplateLayoutDefaults.getCharset();
+
+ @PluginBuilderAttribute
+ private boolean locationInfoEnabled =
+ JsonTemplateLayoutDefaults.isLocationInfoEnabled();
+
+ @PluginBuilderAttribute
+ private boolean stackTraceEnabled =
+ JsonTemplateLayoutDefaults.isStackTraceEnabled();
+
+ @PluginBuilderAttribute
+ private String eventTemplate = JsonTemplateLayoutDefaults.getEventTemplate();
+
+ @PluginBuilderAttribute
+ private String eventTemplateUri =
+ JsonTemplateLayoutDefaults.getEventTemplateUri();
+
+ @PluginElement("EventTemplateAdditionalFields")
+ private EventTemplateAdditionalFields eventTemplateAdditionalFields
+ = EventTemplateAdditionalFields.EMPTY;
+
+ @PluginBuilderAttribute
+ private String stackTraceElementTemplate =
+ JsonTemplateLayoutDefaults.getStackTraceElementTemplate();
+
+ @PluginBuilderAttribute
+ private String stackTraceElementTemplateUri =
+ JsonTemplateLayoutDefaults.getStackTraceElementTemplateUri();
+
+ @PluginBuilderAttribute
+ private String eventDelimiter = JsonTemplateLayoutDefaults.getEventDelimiter();
+
+ @PluginBuilderAttribute
+ private boolean nullEventDelimiterEnabled =
+ JsonTemplateLayoutDefaults.isNullEventDelimiterEnabled();
+
+ @PluginBuilderAttribute
+ private int maxStringLength = JsonTemplateLayoutDefaults.getMaxStringLength();
+
+ @PluginBuilderAttribute
+ private String truncatedStringSuffix =
+ JsonTemplateLayoutDefaults.getTruncatedStringSuffix();
+
+ @PluginBuilderAttribute
+ private RecyclerFactory recyclerFactory =
+ JsonTemplateLayoutDefaults.getRecyclerFactory();
+
+ private Builder() {
+ // Do nothing.
+ }
+
+ public Configuration getConfiguration() {
+ return configuration;
+ }
+
+ public Builder setConfiguration(final Configuration configuration) {
+ this.configuration = configuration;
+ return this;
+ }
+
+ public Charset getCharset() {
+ return charset;
+ }
+
+ public Builder setCharset(final Charset charset) {
+ this.charset = charset;
+ return this;
+ }
+
+ public boolean isLocationInfoEnabled() {
+ return locationInfoEnabled;
+ }
+
+ public Builder setLocationInfoEnabled(final boolean locationInfoEnabled) {
+ this.locationInfoEnabled = locationInfoEnabled;
+ return this;
+ }
+
+ public boolean isStackTraceEnabled() {
+ return stackTraceEnabled;
+ }
+
+ public Builder setStackTraceEnabled(final boolean stackTraceEnabled) {
+ this.stackTraceEnabled = stackTraceEnabled;
+ return this;
+ }
+
+ public String getEventTemplate() {
+ return eventTemplate;
+ }
+
+ public Builder setEventTemplate(final String eventTemplate) {
+ this.eventTemplate = eventTemplate;
+ return this;
+ }
+
+ public String getEventTemplateUri() {
+ return eventTemplateUri;
+ }
+
+ public Builder setEventTemplateUri(final String eventTemplateUri) {
+ this.eventTemplateUri = eventTemplateUri;
+ return this;
+ }
+
+ public EventTemplateAdditionalFields getEventTemplateAdditionalFields() {
+ return eventTemplateAdditionalFields;
+ }
+
+ public Builder setEventTemplateAdditionalFields(
+ final EventTemplateAdditionalFields eventTemplateAdditionalFields) {
+ this.eventTemplateAdditionalFields = eventTemplateAdditionalFields;
+ return this;
+ }
+
+ public String getStackTraceElementTemplate() {
+ return stackTraceElementTemplate;
+ }
+
+ public Builder setStackTraceElementTemplate(
+ final String stackTraceElementTemplate) {
+ this.stackTraceElementTemplate = stackTraceElementTemplate;
+ return this;
+ }
+
+ public String getStackTraceElementTemplateUri() {
+ return stackTraceElementTemplateUri;
+ }
+
+ public Builder setStackTraceElementTemplateUri(
+ final String stackTraceElementTemplateUri) {
+ this.stackTraceElementTemplateUri = stackTraceElementTemplateUri;
+ return this;
+ }
+
+ public String getEventDelimiter() {
+ return eventDelimiter;
+ }
+
+ public Builder setEventDelimiter(final String eventDelimiter) {
+ this.eventDelimiter = eventDelimiter;
+ return this;
+ }
+
+ public boolean isNullEventDelimiterEnabled() {
+ return nullEventDelimiterEnabled;
+ }
+
+ public Builder setNullEventDelimiterEnabled(
+ final boolean nullEventDelimiterEnabled) {
+ this.nullEventDelimiterEnabled = nullEventDelimiterEnabled;
+ return this;
+ }
+
+ public int getMaxStringLength() {
+ return maxStringLength;
+ }
+
+ public Builder setMaxStringLength(final int maxStringLength) {
+ this.maxStringLength = maxStringLength;
+ return this;
+ }
+
+ public String getTruncatedStringSuffix() {
+ return truncatedStringSuffix;
+ }
+
+ public Builder setTruncatedStringSuffix(final String truncatedStringSuffix) {
+ this.truncatedStringSuffix = truncatedStringSuffix;
+ return this;
+ }
+
+ public RecyclerFactory getRecyclerFactory() {
+ return recyclerFactory;
+ }
+
+ public Builder setRecyclerFactory(final RecyclerFactory recyclerFactory) {
+ this.recyclerFactory = recyclerFactory;
+ return this;
+ }
+
+ @Override
+ public JsonTemplateLayout build() {
+ validate();
+ return new JsonTemplateLayout(this);
+ }
+
+ private void validate() {
+ Objects.requireNonNull(configuration, "config");
+ if (Strings.isBlank(eventTemplate) && Strings.isBlank(eventTemplateUri)) {
+ throw new IllegalArgumentException(
+ "both eventTemplate and eventTemplateUri are blank");
+ }
+ Objects.requireNonNull(eventTemplateAdditionalFields, "eventTemplateAdditionalFields");
+ if (stackTraceEnabled &&
+ Strings.isBlank(stackTraceElementTemplate)
+ && Strings.isBlank(stackTraceElementTemplateUri)) {
+ throw new IllegalArgumentException(
+ "both stackTraceElementTemplate and stackTraceElementTemplateUri are blank");
+ }
+ if (maxStringLength <= 0) {
+ throw new IllegalArgumentException(
+ "was expecting a non-zero positive maxStringLength: " +
+ maxStringLength);
+ }
+ Objects.requireNonNull(truncatedStringSuffix, "truncatedStringSuffix");
+ Objects.requireNonNull(recyclerFactory, "recyclerFactory");
+ }
+
+ }
+
+ // We need this ugly model and its builder just to be able to allow
+ // key-value pairs in a dedicated element.
+ @SuppressWarnings({"unused", "WeakerAccess"})
+ @Plugin(name = "EventTemplateAdditionalFields",
+ category = Node.CATEGORY,
+ printObject = true)
+ public static final class EventTemplateAdditionalFields {
+
+ private static final EventTemplateAdditionalFields EMPTY = newBuilder().build();
+
+ private final EventTemplateAdditionalField[] additionalFields;
+
+ private EventTemplateAdditionalFields(final Builder builder) {
+ this.additionalFields = builder.additionalFields != null
+ ? builder.additionalFields
+ : new EventTemplateAdditionalField[0];
+ }
+
+ public EventTemplateAdditionalField[] getAdditionalFields() {
+ return additionalFields;
+ }
+
+ @Override
+ public boolean equals(Object object) {
+ if (this == object) return true;
+ if (object == null || getClass() != object.getClass()) return false;
+ EventTemplateAdditionalFields that = (EventTemplateAdditionalFields) object;
+ return Arrays.equals(additionalFields, that.additionalFields);
+ }
+
+ @Override
+ public int hashCode() {
+ return Arrays.hashCode(additionalFields);
+ }
+
+ @Override
+ public String toString() {
+ return Arrays.toString(additionalFields);
+ }
+
+ @PluginBuilderFactory
+ public static Builder newBuilder() {
+ return new Builder();
+ }
+
+ public static class Builder
+ implements org.apache.logging.log4j.core.util.Builder<EventTemplateAdditionalFields> {
+
+ @PluginElement("AdditionalField")
+ private EventTemplateAdditionalField[] additionalFields;
+
+ private Builder() {}
+
+ public EventTemplateAdditionalField[] getAdditionalFields() {
+ return additionalFields;
+ }
+
+ public Builder setAdditionalFields(
+ final EventTemplateAdditionalField[] additionalFields) {
+ this.additionalFields = additionalFields;
+ return this;
+ }
+
+ @Override
+ public EventTemplateAdditionalFields build() {
+ return new EventTemplateAdditionalFields(this);
+ }
+
+ }
+
+ }
+
+ @Plugin(name = "EventTemplateAdditionalField",
+ category = Node.CATEGORY,
+ printObject = true)
+ public static final class EventTemplateAdditionalField {
+
+ public enum Type { STRING, JSON }
+
+ private final String key;
+
+ private final String value;
+
+ private final Type type;
+
+ private EventTemplateAdditionalField(final Builder builder) {
+ this.key = builder.key;
+ this.value = builder.value;
+ this.type = builder.type;
+ }
+
+ public String getKey() {
+ return key;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ public Type getType() {
+ return type;
+ }
+
+ @Override
+ public boolean equals(Object object) {
+ if (this == object) return true;
+ if (object == null || getClass() != object.getClass()) return false;
+ EventTemplateAdditionalField that = (EventTemplateAdditionalField) object;
+ return key.equals(that.key) &&
+ value.equals(that.value) &&
+ type == that.type;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(key, value, type);
+ }
+
+ @Override
+ public String toString() {
+ final String formattedValue = Type.STRING.equals(type)
+ ? String.format("\"%s\"", value)
+ : value;
+ return String.format("%s=%s", key, formattedValue);
+ }
+
+ @PluginBuilderFactory
+ public static EventTemplateAdditionalField.Builder newBuilder() {
+ return new EventTemplateAdditionalField.Builder();
+ }
+
+ public static class Builder
+ implements org.apache.logging.log4j.core.util.Builder<EventTemplateAdditionalField> {
+
+ @org.apache.logging.log4j.plugins.PluginBuilderAttribute
+ private String key;
+
+ @org.apache.logging.log4j.plugins.PluginBuilderAttribute
+ private String value;
+
+ @org.apache.logging.log4j.plugins.PluginBuilderAttribute
+ private Type type = Type.STRING;
+
+ public Builder setKey(final String key) {
+ this.key = key;
+ return this;
+ }
+
+ public Builder setValue(final String value) {
+ this.value = value;
+ return this;
+ }
+
+ public Builder setType(final Type type) {
+ this.type = type;
+ return this;
+ }
+
+ @Override
+ public EventTemplateAdditionalField build() {
+ validate();
+ return new EventTemplateAdditionalField(this);
+ }
+
+ private void validate() {
+ if (Strings.isBlank(key)) {
+ throw new IllegalArgumentException("blank key");
+ }
+ if (Strings.isBlank(value)) {
+ throw new IllegalArgumentException("blank value");
+ }
+ Objects.requireNonNull(type, "type");
+ }
+
+ }
+
+ }
+
+}
diff --git a/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/JsonTemplateLayoutDefaults.java b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/JsonTemplateLayoutDefaults.java
new file mode 100644
index 0000000..7c28b9f
--- /dev/null
+++ b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/JsonTemplateLayoutDefaults.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.layout.json.template;
+
+import org.apache.logging.log4j.layout.json.template.util.RecyclerFactories;
+import org.apache.logging.log4j.layout.json.template.util.RecyclerFactory;
+import org.apache.logging.log4j.util.PropertiesUtil;
+
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.Locale;
+import java.util.TimeZone;
+
+public enum JsonTemplateLayoutDefaults {;
+
+ private static final PropertiesUtil PROPERTIES = PropertiesUtil.getProperties();
+
+ private static final Charset CHARSET = readCharset();
+
+ private static final boolean LOCATION_INFO_ENABLED =
+ PROPERTIES.getBooleanProperty(
+ "log4j.layout.jsonTemplate.locationInfoEnabled",
+ false);
+
+ private static final boolean STACK_TRACE_ENABLED =
+ PROPERTIES.getBooleanProperty(
+ "log4j.layout.jsonTemplate.stackTraceEnabled",
+ true);
+
+ private static final String TIMESTAMP_FORMAT_PATTERN =
+ PROPERTIES.getStringProperty(
+ "log4j.layout.jsonTemplate.timestampFormatPattern",
+ "yyyy-MM-dd'T'HH:mm:ss.SSSZZZ");
+
+ private static final TimeZone TIME_ZONE = readTimeZone();
+
+ private static final Locale LOCALE = readLocale();
+
+ private static final String EVENT_TEMPLATE =
+ PROPERTIES.getStringProperty(
+ "log4j.layout.jsonTemplate.eventTemplate");
+
+ private static final String EVENT_TEMPLATE_URI =
+ PROPERTIES.getStringProperty(
+ "log4j.layout.jsonTemplate.eventTemplateUri",
+ "classpath:EcsLayout.json");
+
+ private static final String STACK_TRACE_ELEMENT_TEMPLATE =
+ PROPERTIES.getStringProperty(
+ "log4j.layout.jsonTemplate.stackTraceElementTemplate");
+
+ private static final String STACK_TRACE_ELEMENT_TEMPLATE_URI =
+ PROPERTIES.getStringProperty(
+ "log4j.layout.jsonTemplate.stackTraceElementTemplateUri",
+ "classpath:StackTraceElementLayout.json");
+
+ private static final String MDC_KEY_PATTERN =
+ PROPERTIES.getStringProperty("log4j.layout.jsonTemplate.mdcKeyPattern");
+
+ private static final String NDC_PATTERN =
+ PROPERTIES.getStringProperty("log4j.layout.jsonTemplate.ndcPattern");
+
+ private static final String EVENT_DELIMITER =
+ PROPERTIES.getStringProperty(
+ "log4j.layout.jsonTemplate.eventDelimiter",
+ System.lineSeparator());
+
+ private static final boolean NULL_EVENT_DELIMITER_ENABLED =
+ PROPERTIES.getBooleanProperty(
+ "log4j.layout.jsonTemplate.nullEventDelimiterEnabled",
+ false);
+
+ private static final int MAX_STRING_LENGTH = readMaxStringLength();
+
+ private static final String TRUNCATED_STRING_SUFFIX =
+ PROPERTIES.getStringProperty(
+ "log4j.layout.jsonTemplate.truncatedStringSuffix",
+ "…");
+
+ private static final RecyclerFactory RECYCLER_FACTORY = readRecyclerFactory();
+
+ private static Charset readCharset() {
+ final String charsetName =
+ PROPERTIES.getStringProperty("log4j.layout.jsonTemplate.charset");
+ return charsetName != null
+ ? Charset.forName(charsetName)
+ : StandardCharsets.UTF_8;
+ }
+
+ private static TimeZone readTimeZone() {
+ final String timeZoneId =
+ PROPERTIES.getStringProperty("log4j.layout.jsonTemplate.timeZone");
+ return timeZoneId != null
+ ? TimeZone.getTimeZone(timeZoneId)
+ : TimeZone.getDefault();
+ }
+
+ private static Locale readLocale() {
+ final String locale =
+ PROPERTIES.getStringProperty("log4j.layout.jsonTemplate.locale");
+ if (locale == null) {
+ return Locale.getDefault();
+ }
+ final String[] localeFields = locale.split("_", 3);
+ switch (localeFields.length) {
+ case 1: return new Locale(localeFields[0]);
+ case 2: return new Locale(localeFields[0], localeFields[1]);
+ case 3: return new Locale(localeFields[0], localeFields[1], localeFields[2]);
+ default: throw new IllegalArgumentException("invalid locale: " + locale);
+ }
+ }
+
+ private static int readMaxStringLength() {
+ final int maxStringLength = PROPERTIES.getIntegerProperty(
+ "log4j.layout.jsonTemplate.maxStringLength",
+ 16 * 1_024);
+ if (maxStringLength <= 0) {
+ throw new IllegalArgumentException(
+ "was expecting a non-zero positive maxStringLength: " +
+ maxStringLength);
+ }
+ return maxStringLength;
+ }
+
+ private static RecyclerFactory readRecyclerFactory() {
+ final String recyclerFactorySpec = PROPERTIES.getStringProperty(
+ "log4j.layout.jsonTemplate.recyclerFactory");
+ return RecyclerFactories.ofSpec(recyclerFactorySpec);
+ }
+
+ public static Charset getCharset() {
+ return CHARSET;
+ }
+
+ public static boolean isLocationInfoEnabled() {
+ return LOCATION_INFO_ENABLED;
+ }
+
+ public static boolean isStackTraceEnabled() {
+ return STACK_TRACE_ENABLED;
+ }
+
+ public static String getTimestampFormatPattern() {
+ return TIMESTAMP_FORMAT_PATTERN;
+ }
+
+ public static TimeZone getTimeZone() {
+ return TIME_ZONE;
+ }
+
+ public static Locale getLocale() {
+ return LOCALE;
+ }
+
+ public static String getEventTemplate() {
+ return EVENT_TEMPLATE;
+ }
+
+ public static String getEventTemplateUri() {
+ return EVENT_TEMPLATE_URI;
+ }
+
+ public static String getStackTraceElementTemplate() {
+ return STACK_TRACE_ELEMENT_TEMPLATE;
+ }
+
+ public static String getStackTraceElementTemplateUri() {
+ return STACK_TRACE_ELEMENT_TEMPLATE_URI;
+ }
+
+ public static String getMdcKeyPattern() {
+ return MDC_KEY_PATTERN;
+ }
+
+ public static String getNdcPattern() {
+ return NDC_PATTERN;
+ }
+
+ public static String getEventDelimiter() {
+ return EVENT_DELIMITER;
+ }
+
+ public static boolean isNullEventDelimiterEnabled() {
+ return NULL_EVENT_DELIMITER_ENABLED;
+ }
+
+ public static int getMaxStringLength() {
+ return MAX_STRING_LENGTH;
+ }
+
+ public static String getTruncatedStringSuffix() {
+ return TRUNCATED_STRING_SUFFIX;
+ }
+
+ public static RecyclerFactory getRecyclerFactory() {
+ return RECYCLER_FACTORY;
+ }
+
+}
diff --git a/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/EndOfBatchResolver.java b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/EndOfBatchResolver.java
new file mode 100644
index 0000000..268df52
--- /dev/null
+++ b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/EndOfBatchResolver.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.layout.json.template.resolver;
+
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.layout.json.template.util.JsonWriter;
+
+final class EndOfBatchResolver implements EventResolver {
+
+ private static final EndOfBatchResolver INSTANCE = new EndOfBatchResolver();
+
+ private EndOfBatchResolver() {}
+
+ static EndOfBatchResolver getInstance() {
+ return INSTANCE;
+ }
+
+ static String getName() {
+ return "endOfBatch";
+ }
+
+ @Override
+ public void resolve(
+ final LogEvent logEvent,
+ final JsonWriter jsonWriter) {
+ final boolean endOfBatch = logEvent.isEndOfBatch();
+ jsonWriter.writeBoolean(endOfBatch);
+ }
+
+}
diff --git a/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/EndOfBatchResolverFactory.java b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/EndOfBatchResolverFactory.java
new file mode 100644
index 0000000..0f013a4
--- /dev/null
+++ b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/EndOfBatchResolverFactory.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.layout.json.template.resolver;
+
+final class EndOfBatchResolverFactory implements EventResolverFactory<EndOfBatchResolver> {
+
+ private static final EndOfBatchResolverFactory INSTANCE = new EndOfBatchResolverFactory();
+
+ private EndOfBatchResolverFactory() {}
+
+ static EndOfBatchResolverFactory getInstance() {
+ return INSTANCE;
+ }
+
+ @Override
+ public String getName() {
+ return EndOfBatchResolver.getName();
+ }
+
+ @Override
+ public EndOfBatchResolver create(
+ final EventResolverContext context,
+ final TemplateResolverConfig config) {
+ return EndOfBatchResolver.getInstance();
+ }
+
+}
diff --git a/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/EventResolver.java b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/EventResolver.java
new file mode 100644
index 0000000..ce21181
--- /dev/null
+++ b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/EventResolver.java
@@ -0,0 +1,21 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.logging.log4j.layout.json.template.resolver;
+
+import org.apache.logging.log4j.core.LogEvent;
+
+interface EventResolver extends TemplateResolver<LogEvent> {}
diff --git a/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/EventResolverContext.java b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/EventResolverContext.java
new file mode 100644
index 0000000..a83b2c9
--- /dev/null
+++ b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/EventResolverContext.java
@@ -0,0 +1,228 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.logging.log4j.layout.json.template.resolver;
+
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.config.Configuration;
+import org.apache.logging.log4j.core.lookup.StrSubstitutor;
+import org.apache.logging.log4j.core.util.KeyValuePair;
+import org.apache.logging.log4j.layout.json.template.JsonTemplateLayout;
+import org.apache.logging.log4j.layout.json.template.JsonTemplateLayout.EventTemplateAdditionalField;
+import org.apache.logging.log4j.layout.json.template.util.JsonWriter;
+import org.apache.logging.log4j.layout.json.template.util.RecyclerFactory;
+
+import java.nio.charset.Charset;
+import java.util.Map;
+import java.util.Objects;
+
+public final class EventResolverContext implements TemplateResolverContext<LogEvent, EventResolverContext> {
+
+ private final Configuration configuration;
+
+ private final StrSubstitutor substitutor;
+
+ private final Charset charset;
+
+ private final JsonWriter jsonWriter;
+
+ private final RecyclerFactory recyclerFactory;
+
+ private final int maxStringByteCount;
+
+ private final boolean locationInfoEnabled;
+
+ private final boolean stackTraceEnabled;
+
+ private final TemplateResolver<Throwable> stackTraceObjectResolver;
+
+ private final EventTemplateAdditionalField[] additionalFields;
+
+ private EventResolverContext(final Builder builder) {
+ this.configuration = builder.configuration;
+ this.substitutor = builder.substitutor;
+ this.charset = builder.charset;
+ this.jsonWriter = builder.jsonWriter;
+ this.recyclerFactory = builder.recyclerFactory;
+ this.maxStringByteCount = builder.maxStringByteCount;
+ this.locationInfoEnabled = builder.locationInfoEnabled;
+ this.stackTraceEnabled = builder.stackTraceEnabled;
+ this.stackTraceObjectResolver = stackTraceEnabled
+ ? new StackTraceObjectResolver(builder.stackTraceElementObjectResolver)
+ : null;
+ this.additionalFields = builder.eventTemplateAdditionalFields;
+ }
+
+ @Override
+ public Class<EventResolverContext> getContextClass() {
+ return EventResolverContext.class;
+ }
+
+ @Override
+ public Map<String, TemplateResolverFactory<LogEvent, EventResolverContext, ? extends TemplateResolver<LogEvent>>> getResolverFactoryByName() {
+ return EventResolverFactories.getResolverFactoryByName();
+ }
+
+ public Configuration getConfiguration() {
+ return configuration;
+ }
+
+ @Override
+ public StrSubstitutor getSubstitutor() {
+ return substitutor;
+ }
+
+ public Charset getCharset() {
+ return charset;
+ }
+
+ @Override
+ public JsonWriter getJsonWriter() {
+ return jsonWriter;
+ }
+
+ RecyclerFactory getRecyclerFactory() {
+ return recyclerFactory;
+ }
+
+ int getMaxStringByteCount() {
+ return maxStringByteCount;
+ }
+
+ boolean isLocationInfoEnabled() {
+ return locationInfoEnabled;
+ }
+
+ boolean isStackTraceEnabled() {
+ return stackTraceEnabled;
+ }
+
+ TemplateResolver<Throwable> getStackTraceObjectResolver() {
+ return stackTraceObjectResolver;
+ }
+
+ EventTemplateAdditionalField[] getAdditionalFields() {
+ return additionalFields;
+ }
+
+ public static Builder newBuilder() {
+ return new Builder();
+ }
+
+ public static class Builder {
+
+ private Configuration configuration;
+
+ private StrSubstitutor substitutor;
+
+ private Charset charset;
+
+ private JsonWriter jsonWriter;
+
+ private RecyclerFactory recyclerFactory;
+
+ private int maxStringByteCount;
+
+ private boolean locationInfoEnabled;
+
+ private boolean stackTraceEnabled;
+
+ private TemplateResolver<StackTraceElement> stackTraceElementObjectResolver;
+
+ private EventTemplateAdditionalField[] eventTemplateAdditionalFields;
+
+ private Builder() {
+ // Do nothing.
+ }
+
+ public Builder setConfiguration(final Configuration configuration) {
+ this.configuration = configuration;
+ return this;
+ }
+
+ public Builder setSubstitutor(final StrSubstitutor substitutor) {
+ this.substitutor = substitutor;
+ return this;
+ }
+
+ public Builder setCharset(final Charset charset) {
+ this.charset = charset;
+ return this;
+ }
+
+ public Builder setJsonWriter(final JsonWriter jsonWriter) {
+ this.jsonWriter = jsonWriter;
+ return this;
+ }
+
+ public Builder setRecyclerFactory(final RecyclerFactory recyclerFactory) {
+ this.recyclerFactory = recyclerFactory;
+ return this;
+ }
+
+ public Builder setMaxStringByteCount(final int maxStringByteCount) {
+ this.maxStringByteCount = maxStringByteCount;
+ return this;
+ }
+
+ public Builder setLocationInfoEnabled(final boolean locationInfoEnabled) {
+ this.locationInfoEnabled = locationInfoEnabled;
+ return this;
+ }
+
+ public Builder setStackTraceEnabled(final boolean stackTraceEnabled) {
+ this.stackTraceEnabled = stackTraceEnabled;
+ return this;
+ }
+
+ public Builder setStackTraceElementObjectResolver(
+ final TemplateResolver<StackTraceElement> stackTraceElementObjectResolver) {
+ this.stackTraceElementObjectResolver = stackTraceElementObjectResolver;
+ return this;
+ }
+
+ public Builder setEventTemplateAdditionalFields(
+ final EventTemplateAdditionalField[] eventTemplateAdditionalFields) {
+ this.eventTemplateAdditionalFields = eventTemplateAdditionalFields;
+ return this;
+ }
+
+ public EventResolverContext build() {
+ validate();
+ return new EventResolverContext(this);
+ }
+
+ private void validate() {
+ Objects.requireNonNull(configuration, "configuration");
+ Objects.requireNonNull(substitutor, "substitutor");
+ Objects.requireNonNull(charset, "charset");
+ Objects.requireNonNull(jsonWriter, "jsonWriter");
+ Objects.requireNonNull(recyclerFactory, "recyclerFactory");
+ if (maxStringByteCount <= 0) {
+ throw new IllegalArgumentException(
+ "was expecting maxStringByteCount > 0: " +
+ maxStringByteCount);
+ }
+ if (stackTraceEnabled) {
+ Objects.requireNonNull(
+ stackTraceElementObjectResolver,
+ "stackTraceElementObjectResolver");
+ }
+ }
+
+ }
+
+}
diff --git a/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/EventResolverFactories.java b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/EventResolverFactories.java
new file mode 100644
index 0000000..fc8c6e9
--- /dev/null
+++ b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/EventResolverFactories.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.layout.json.template.resolver;
+
+import org.apache.logging.log4j.core.LogEvent;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+enum EventResolverFactories {;
+
+ private static final Map<String, TemplateResolverFactory<LogEvent, EventResolverContext, ? extends TemplateResolver<LogEvent>>> RESOLVER_FACTORY_BY_NAME =
+ createResolverFactoryByName();
+
+ private static Map<String, TemplateResolverFactory<LogEvent, EventResolverContext, ? extends TemplateResolver<LogEvent>>> createResolverFactoryByName() {
+
+ // Collect resolver factories.
+ final List<EventResolverFactory<? extends EventResolver>> resolverFactories = Arrays.asList(
+ ThreadContextDataResolverFactory.getInstance(),
+ ThreadContextStackResolverFactory.getInstance(),
+ EndOfBatchResolverFactory.getInstance(),
+ ExceptionResolverFactory.getInstance(),
+ ExceptionRootCauseResolverFactory.getInstance(),
+ LevelResolverFactory.getInstance(),
+ LoggerResolverFactory.getInstance(),
+ MainMapResolverFactory.getInstance(),
+ MapResolverFactory.getInstance(),
+ MarkerResolverFactory.getInstance(),
+ MessageResolverFactory.getInstance(),
+ PatternResolverFactory.getInstance(),
+ SourceResolverFactory.getInstance(),
+ ThreadResolverFactory.getInstance(),
+ TimestampResolverFactory.getInstance());
+
+ // Convert collection to map.
+ final Map<String, TemplateResolverFactory<LogEvent, EventResolverContext, ? extends TemplateResolver<LogEvent>>> resolverFactoryByName = new LinkedHashMap<>();
+ for (final EventResolverFactory<? extends EventResolver> resolverFactory : resolverFactories) {
+ resolverFactoryByName.put(resolverFactory.getName(), resolverFactory);
+ }
+ return Collections.unmodifiableMap(resolverFactoryByName);
+
+ }
+
+ static Map<String, TemplateResolverFactory<LogEvent, EventResolverContext, ? extends TemplateResolver<LogEvent>>> getResolverFactoryByName() {
+ return RESOLVER_FACTORY_BY_NAME;
+ }
+
+}
diff --git a/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/EventResolverFactory.java b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/EventResolverFactory.java
new file mode 100644
index 0000000..3c2f2db
--- /dev/null
+++ b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/EventResolverFactory.java
@@ -0,0 +1,21 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.logging.log4j.layout.json.template.resolver;
+
+import org.apache.logging.log4j.core.LogEvent;
+
+interface EventResolverFactory<R extends TemplateResolver<LogEvent>> extends TemplateResolverFactory<LogEvent, EventResolverContext, R> {}
diff --git a/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/ExceptionInternalResolverFactory.java b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/ExceptionInternalResolverFactory.java
new file mode 100644
index 0000000..b6e5ff8
--- /dev/null
+++ b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/ExceptionInternalResolverFactory.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.layout.json.template.resolver;
+
+/**
+ * Exception resolver factory.
+ *
+ * <h3>Configuration</h3>
+ *
+ * <pre>
+ * config = field , [ stringified ]
+ * field = "field" -> ( "className" | "message" | "stackTrace" )
+ * stringified = "stringified" -> boolean
+ * </pre>
+ */
+abstract class ExceptionInternalResolverFactory {
+
+ private static final EventResolver NULL_RESOLVER =
+ (ignored, jsonGenerator) -> jsonGenerator.writeNull();
+
+ EventResolver createInternalResolver(
+ final EventResolverContext context,
+ final TemplateResolverConfig config) {
+ final String fieldName = config.getString("field");
+ switch (fieldName) {
+ case "className": return createClassNameResolver();
+ case "message": return createMessageResolver(context);
+ case "stackTrace": return createStackTraceResolver(context, config);
+ }
+ throw new IllegalArgumentException("unknown field: " + config);
+
+ }
+
+ abstract EventResolver createClassNameResolver();
+
+ abstract EventResolver createMessageResolver(EventResolverContext context);
+
+ private EventResolver createStackTraceResolver(
+ final EventResolverContext context,
+ final TemplateResolverConfig config) {
+ if (!context.isStackTraceEnabled()) {
+ return NULL_RESOLVER;
+ }
+ final boolean stringified = config.getBoolean("stringified", false);
+ return stringified
+ ? createStackTraceStringResolver(context)
+ : createStackTraceObjectResolver(context);
+ }
+
+ abstract EventResolver createStackTraceStringResolver(EventResolverContext context);
+
+ abstract EventResolver createStackTraceObjectResolver(EventResolverContext context);
+
+}
diff --git a/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/ExceptionResolver.java b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/ExceptionResolver.java
new file mode 100644
index 0000000..140cc42
--- /dev/null
+++ b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/ExceptionResolver.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.layout.json.template.resolver;
+
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.layout.json.template.util.JsonWriter;
+
+/**
+ * Exception resolver.
+ *
+ * Note that this resolver is toggled by {@link
+ * org.apache.logging.log4j.layout.json.template.JsonTemplateLayout.Builder#setStackTraceEnabled(boolean)}.
+ *
+ * @see ExceptionInternalResolverFactory
+ */
+class ExceptionResolver implements EventResolver {
+
+ private static final ExceptionInternalResolverFactory INTERNAL_RESOLVER_FACTORY =
+ new ExceptionInternalResolverFactory() {
+
+ @Override
+ EventResolver createClassNameResolver() {
+ return (final LogEvent logEvent, final JsonWriter jsonWriter) -> {
+ final Throwable exception = logEvent.getThrown();
+ if (exception == null) {
+ jsonWriter.writeNull();
+ } else {
+ String exceptionClassName = exception.getClass().getCanonicalName();
+ jsonWriter.writeString(exceptionClassName);
+ }
+ };
+ }
+
+ @Override
+ EventResolver createMessageResolver(final EventResolverContext context) {
+ return (final LogEvent logEvent, final JsonWriter jsonWriter) -> {
+ final Throwable exception = logEvent.getThrown();
+ if (exception == null) {
+ jsonWriter.writeNull();
+ } else {
+ String exceptionMessage = exception.getMessage();
+ jsonWriter.writeString(exceptionMessage);
+ }
+ };
+ }
+
+ @Override
+ EventResolver createStackTraceStringResolver(final EventResolverContext context) {
+ StackTraceStringResolver stackTraceStringResolver =
+ new StackTraceStringResolver(context);
+ return (final LogEvent logEvent, final JsonWriter jsonWriter) -> {
+ final Throwable exception = logEvent.getThrown();
+ if (exception == null) {
+ jsonWriter.writeNull();
+ } else {
+ stackTraceStringResolver.resolve(exception, jsonWriter);
+ }
+ };
+ }
+
+ @Override
+ EventResolver createStackTraceObjectResolver(final EventResolverContext context) {
+ return (final LogEvent logEvent, final JsonWriter jsonWriter) -> {
+ final Throwable exception = logEvent.getThrown();
+ if (exception == null) {
+ jsonWriter.writeNull();
+ } else {
+ context.getStackTraceObjectResolver().resolve(exception, jsonWriter);
+ }
+ };
+ }
+
+ };
+
+ private final boolean stackTraceEnabled;
+
+ private final EventResolver internalResolver;
+
+ ExceptionResolver(
+ final EventResolverContext context,
+ final TemplateResolverConfig config) {
+ this.stackTraceEnabled = context.isStackTraceEnabled();
+ this.internalResolver = INTERNAL_RESOLVER_FACTORY
+ .createInternalResolver(context, config);
+ }
+
+ static String getName() {
+ return "exception";
+ }
+
+ @Override
+ public boolean isResolvable() {
+ return stackTraceEnabled;
+ }
+
+ @Override
+ public boolean isResolvable(final LogEvent logEvent) {
+ return stackTraceEnabled && logEvent.getThrown() != null;
+ }
+
+ @Override
+ public void resolve(
+ final LogEvent logEvent,
+ final JsonWriter jsonWriter) {
+ internalResolver.resolve(logEvent, jsonWriter);
+ }
+
+}
diff --git a/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/ExceptionResolverFactory.java b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/ExceptionResolverFactory.java
new file mode 100644
index 0000000..7ca79b0
--- /dev/null
+++ b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/ExceptionResolverFactory.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.layout.json.template.resolver;
+
+final class ExceptionResolverFactory
+ implements EventResolverFactory<ExceptionResolver> {
+
+ private static final ExceptionResolverFactory INSTANCE =
+ new ExceptionResolverFactory();
+
+ private ExceptionResolverFactory() {}
+
+ static ExceptionResolverFactory getInstance() {
+ return INSTANCE;
+ }
+
+ @Override
+ public String getName() {
+ return ExceptionResolver.getName();
+ }
+
+ @Override
+ public ExceptionResolver create(
+ final EventResolverContext context,
+ final TemplateResolverConfig config) {
+ return new ExceptionResolver(context, config);
+ }
+
+}
diff --git a/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/ExceptionRootCauseResolver.java b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/ExceptionRootCauseResolver.java
new file mode 100644
index 0000000..f3d4705
--- /dev/null
+++ b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/ExceptionRootCauseResolver.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.layout.json.template.resolver;
+
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.util.Throwables;
+import org.apache.logging.log4j.layout.json.template.util.JsonWriter;
+
+/**
+ * Exception root cause resolver.
+ *
+ * Note that this resolver is toggled by {@link
+ * org.apache.logging.log4j.layout.json.template.JsonTemplateLayout.Builder#setStackTraceEnabled(boolean)}.
+ *
+ * @see ExceptionInternalResolverFactory
+ */
+final class ExceptionRootCauseResolver implements EventResolver {
+
+ private static final ExceptionInternalResolverFactory INTERNAL_RESOLVER_FACTORY =
+ new ExceptionInternalResolverFactory() {
+
+ @Override
+ EventResolver createClassNameResolver() {
+ return (final LogEvent logEvent, final JsonWriter jsonWriter) -> {
+ final Throwable exception = logEvent.getThrown();
+ if (exception == null) {
+ jsonWriter.writeNull();
+ } else {
+ final Throwable rootCause = Throwables.getRootCause(exception);
+ final String rootCauseClassName = rootCause.getClass().getCanonicalName();
+ jsonWriter.writeString(rootCauseClassName);
+ }
+ };
+ }
+
+ @Override
+ EventResolver createMessageResolver(final EventResolverContext context) {
+ return (final LogEvent logEvent, final JsonWriter jsonWriter) -> {
+ final Throwable exception = logEvent.getThrown();
+ if (exception == null) {
+ jsonWriter.writeNull();
+ } else {
+ final Throwable rootCause = Throwables.getRootCause(exception);
+ final String rootCauseMessage = rootCause.getMessage();
+ jsonWriter.writeString(rootCauseMessage);
+ }
+ };
+ }
+
+ @Override
+ EventResolver createStackTraceStringResolver(final EventResolverContext context) {
+ final StackTraceStringResolver stackTraceStringResolver =
+ new StackTraceStringResolver(context);
+ return (final LogEvent logEvent, final JsonWriter jsonWriter) -> {
+ final Throwable exception = logEvent.getThrown();
+ if (exception == null) {
+ jsonWriter.writeNull();
+ } else {
+ final Throwable rootCause = Throwables.getRootCause(exception);
+ stackTraceStringResolver.resolve(rootCause, jsonWriter);
+ }
+ };
+ }
+
+ @Override
+ EventResolver createStackTraceObjectResolver(EventResolverContext context) {
+ return (final LogEvent logEvent, final JsonWriter jsonWriter) -> {
+ final Throwable exception = logEvent.getThrown();
+ if (exception == null) {
+ jsonWriter.writeNull();
+ } else {
+ final Throwable rootCause = Throwables.getRootCause(exception);
+ context.getStackTraceObjectResolver().resolve(rootCause, jsonWriter);
+ }
+ };
+ }
+
+ };
+
+ private final boolean stackTraceEnabled;
+
+ private final EventResolver internalResolver;
+
+ ExceptionRootCauseResolver(
+ final EventResolverContext context,
+ final TemplateResolverConfig config) {
+ this.stackTraceEnabled = context.isStackTraceEnabled();
+ this.internalResolver = INTERNAL_RESOLVER_FACTORY
+ .createInternalResolver(context, config);
+ }
+
+ static String getName() {
+ return "exceptionRootCause";
+ }
+
+ @Override
+ public boolean isResolvable() {
+ return stackTraceEnabled;
+ }
+
+ @Override
+ public boolean isResolvable(final LogEvent logEvent) {
+ return stackTraceEnabled && logEvent.getThrown() != null;
+ }
+
+ @Override
+ public void resolve(
+ final LogEvent logEvent,
+ final JsonWriter jsonWriter) {
+ internalResolver.resolve(logEvent, jsonWriter);
+ }
+
+}
diff --git a/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/ExceptionRootCauseResolverFactory.java b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/ExceptionRootCauseResolverFactory.java
new file mode 100644
index 0000000..e511f0d
--- /dev/null
+++ b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/ExceptionRootCauseResolverFactory.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.layout.json.template.resolver;
+
+final class ExceptionRootCauseResolverFactory implements EventResolverFactory<ExceptionRootCauseResolver> {
+
+ private static final ExceptionRootCauseResolverFactory INSTANCE = new ExceptionRootCauseResolverFactory();
+
+ private ExceptionRootCauseResolverFactory() {}
+
+ static ExceptionRootCauseResolverFactory getInstance() {
+ return INSTANCE;
+ }
+
+ @Override
+ public String getName() {
+ return ExceptionRootCauseResolver.getName();
+ }
+
+ @Override
+ public ExceptionRootCauseResolver create(
+ final EventResolverContext context,
+ final TemplateResolverConfig config) {
+ return new ExceptionRootCauseResolver(context, config);
+ }
+
+}
diff --git a/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/LevelResolver.java b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/LevelResolver.java
new file mode 100644
index 0000000..422e445
--- /dev/null
+++ b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/LevelResolver.java
@@ -0,0 +1,176 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.logging.log4j.layout.json.template.resolver;
+
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.net.Severity;
+import org.apache.logging.log4j.layout.json.template.util.JsonWriter;
+
+import java.util.Arrays;
+import java.util.Map;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+
+/**
+ * Level resolver.
+ *
+ * <h3>Configuration</h3>
+ *
+ * <pre>
+ * config = field , [ severity ]
+ * field = "field" -> ( "name" | "severity" )
+ * severity = severity-field
+ * severity-field = "field" -> ( "keyword" | "code" )
+ * </pre>
+ *
+ * <h3>Examples</h3>
+ *
+ * Resolve the level name:
+ *
+ * <pre>
+ * {
+ * "$resolver": "level",
+ * "field": "name"
+ * }
+ * </pre>
+ *
+ * Resolve the severity keyword:
+ *
+ * <pre>
+ * {
+ * "$resolver": "level",
+ * "field": "severity",
+ * "severity": {
+ * "field": "keyword"
+ * }
+ * }
+ *
+ * Resolve the severity code:
+ *
+ * <pre>
+ * {
+ * "$resolver": "level",
+ * "field": "severity",
+ * "severity": {
+ * "field": "code"
+ * }
+ * }
+ * </pre>
+ */
+final class LevelResolver implements EventResolver {
+
+ private static String[] SEVERITY_CODE_RESOLUTION_BY_STANDARD_LEVEL_ORDINAL;
+
+ static {
+ final int levelCount = Level.values().length;
+ final String[] severityCodeResolutionByStandardLevelOrdinal =
+ new String[levelCount + 1];
+ for (final Level level : Level.values()) {
+ final int standardLevelOrdinal = level.getStandardLevel().ordinal();
+ final int severityCode = Severity.getSeverity(level).getCode();
+ severityCodeResolutionByStandardLevelOrdinal[standardLevelOrdinal] =
+ String.valueOf(severityCode);
+ }
+ SEVERITY_CODE_RESOLUTION_BY_STANDARD_LEVEL_ORDINAL =
+ severityCodeResolutionByStandardLevelOrdinal;
+ }
+
+ private static final EventResolver SEVERITY_CODE_RESOLVER =
+ (final LogEvent logEvent, final JsonWriter jsonWriter) -> {
+ final int standardLevelOrdinal =
+ logEvent.getLevel().getStandardLevel().ordinal();
+ final String severityCodeResolution =
+ SEVERITY_CODE_RESOLUTION_BY_STANDARD_LEVEL_ORDINAL[
+ standardLevelOrdinal];
+ jsonWriter.writeRawString(severityCodeResolution);
+ };
+
+ private final EventResolver internalResolver;
+
+ LevelResolver(
+ final EventResolverContext context,
+ final TemplateResolverConfig config) {
+ this.internalResolver = createResolver(context, config);
+ }
+
+ private static EventResolver createResolver(
+ final EventResolverContext context,
+ final TemplateResolverConfig config) {
+ final JsonWriter jsonWriter = context.getJsonWriter();
+ final String fieldName = config.getString("field");
+ switch (fieldName) {
+ case "name": return createNameResolver(jsonWriter);
+ case "severity": {
+ final String severityFieldName =
+ config.getString(new String[]{"severity", "field"});
+ switch (severityFieldName) {
+ case "keyword": return createSeverityKeywordResolver(jsonWriter);
+ case "code": return SEVERITY_CODE_RESOLVER;
+ default:
+ throw new IllegalArgumentException(
+ "unknown severity field: " + config);
+ }
+ }
+ default: throw new IllegalArgumentException("unknown field: " + config);
+ }
+ }
+
+ private static EventResolver createNameResolver(
+ final JsonWriter contextJsonWriter) {
+ final Map<Level, String> resolutionByLevel = Arrays
+ .stream(Level.values())
+ .collect(Collectors.toMap(
+ Function.identity(),
+ (final Level level) -> contextJsonWriter.use(() -> {
+ final String name = level.name();
+ contextJsonWriter.writeString(name);
+ })));
+ return (final LogEvent logEvent, final JsonWriter jsonWriter) -> {
+ final String resolution = resolutionByLevel.get(logEvent.getLevel());
+ jsonWriter.writeRawString(resolution);
+ };
+ }
+
+ private static EventResolver createSeverityKeywordResolver(
+ final JsonWriter contextJsonWriter) {
+ final Map<Level, String> resolutionByLevel = Arrays
+ .stream(Level.values())
+ .collect(Collectors.toMap(
+ Function.identity(),
+ (final Level level) -> contextJsonWriter.use(() -> {
+ final String severityKeyword = Severity.getSeverity(level).name();
+ contextJsonWriter.writeString(severityKeyword);
+ })));
+ return (final LogEvent logEvent, final JsonWriter jsonWriter) -> {
+ final String resolution = resolutionByLevel.get(logEvent.getLevel());
+ jsonWriter.writeRawString(resolution);
+ };
+ }
+
+ static String getName() {
+ return "level";
+ }
+
+ @Override
+ public void resolve(
+ final LogEvent logEvent,
+ final JsonWriter jsonWriter) {
+ internalResolver.resolve(logEvent, jsonWriter);
+ }
+
+}
diff --git a/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/LevelResolverFactory.java b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/LevelResolverFactory.java
new file mode 100644
index 0000000..f5ee519
--- /dev/null
+++ b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/LevelResolverFactory.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.layout.json.template.resolver;
+
+final class LevelResolverFactory implements EventResolverFactory<LevelResolver> {
+
+ private static final LevelResolverFactory INSTANCE = new LevelResolverFactory();
+
+ private LevelResolverFactory() {}
+
+ static LevelResolverFactory getInstance() {
+ return INSTANCE;
+ }
+
+ @Override
+ public String getName() {
+ return LevelResolver.getName();
+ }
+
+ @Override
+ public LevelResolver create(
+ final EventResolverContext context,
+ final TemplateResolverConfig config) {
+ return new LevelResolver(context, config);
+ }
+
+}
diff --git a/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/LoggerResolver.java b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/LoggerResolver.java
new file mode 100644
index 0000000..66f1f87
--- /dev/null
+++ b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/LoggerResolver.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.layout.json.template.resolver;
+
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.layout.json.template.util.JsonWriter;
+
+/**
+ * Logger resolver.
+ *
+ * <h3>Configuration</h3>
+ *
+ * <pre>
+ * config = "field" -> ( "name" | "fqcn" )
+ * </pre>
+ *
+ * <h3>Examples</h3>
+ *
+ * Resolve the logger name:
+ *
+ * <pre>
+ * {
+ * "$resolver": "logger",
+ * "field": "name"
+ * }
+ * </pre>
+ *
+ * Resolve the logger's fully qualified class name:
+ *
+ * <pre>
+ * {
+ * "$resolver": "logger",
+ * "field": "fqcn"
+ * }
+ * </pre>
+ */
+final class LoggerResolver implements EventResolver {
+
+ private static final EventResolver NAME_RESOLVER =
+ (final LogEvent logEvent, final JsonWriter jsonWriter) -> {
+ final String loggerName = logEvent.getLoggerName();
+ jsonWriter.writeString(loggerName);
+ };
+
+ private static final EventResolver FQCN_RESOLVER =
+ (final LogEvent logEvent, final JsonWriter jsonWriter) -> {
+ final String loggerFqcn = logEvent.getLoggerFqcn();
+ jsonWriter.writeString(loggerFqcn);
+ };
+
+ private final EventResolver internalResolver;
+
+ LoggerResolver(final TemplateResolverConfig config) {
+ this.internalResolver = createInternalResolver(config);
+ }
+
+ private static EventResolver createInternalResolver(
+ final TemplateResolverConfig config) {
+ final String fieldName = config.getString("field");
+ switch (fieldName) {
+ case "name": return NAME_RESOLVER;
+ case "fqcn": return FQCN_RESOLVER;
+ }
+ throw new IllegalArgumentException("unknown field: " + config);
+ }
+
+ static String getName() {
+ return "logger";
+ }
+
+ @Override
+ public void resolve(
+ final LogEvent logEvent,
+ final JsonWriter jsonWriter) {
+ internalResolver.resolve(logEvent, jsonWriter);
+ }
+
+}
diff --git a/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/LoggerResolverFactory.java b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/LoggerResolverFactory.java
new file mode 100644
index 0000000..5539f6e
--- /dev/null
+++ b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/LoggerResolverFactory.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.layout.json.template.resolver;
+
+final class LoggerResolverFactory implements EventResolverFactory<LoggerResolver> {
+
+ private static final LoggerResolverFactory INSTANCE = new LoggerResolverFactory();
+
+ private LoggerResolverFactory() {}
+
+ static LoggerResolverFactory getInstance() {
+ return INSTANCE;
+ }
+
+ @Override
+ public String getName() {
+ return LoggerResolver.getName();
+ }
+
+ @Override
+ public LoggerResolver create(
+ final EventResolverContext context,
+ final TemplateResolverConfig config) {
+ return new LoggerResolver(config);
+ }
+
+}
diff --git a/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/MainMapResolver.java b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/MainMapResolver.java
new file mode 100644
index 0000000..b12821c
--- /dev/null
+++ b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/MainMapResolver.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.layout.json.template.resolver;
+
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.lookup.MainMapLookup;
+import org.apache.logging.log4j.layout.json.template.util.JsonWriter;
+
+/**
+ * An index-based resolver for the <tt>main()</tt> method arguments.
+ *
+ * <h3>Configuration</h3>
+ *
+ * <pre>
+ * config = index | key
+ * index = "index" -> number
+ * key = "key" -> string
+ * </pre>
+ *
+ * <h3>Examples</h3>
+ *
+ * Resolve the 1st <tt>main()</tt> method argument:
+ *
+ * <pre>
+ * {
+ * "$resolver": "main",
+ * "index": 0
+ * }
+ * </pre>
+ *
+ * Resolve the argument coming right after <tt>--userId</tt>:
+ *
+ * <pre>
+ * {
+ * "$resolver": "main",
+ * "key": "--userId"
+ * }
+ * </pre>
+ *
+ * @see MainMapResolver
+ */
+final class MainMapResolver implements EventResolver {
+
+ private static final MainMapLookup MAIN_MAP_LOOKUP = new MainMapLookup();
+
+ private final String key;
+
+ static String getName() {
+ return "main";
+ }
+
+ MainMapResolver(final TemplateResolverConfig config) {
+ final String key = config.getString("key");
+ final Integer index = config.getInteger("index");
+ if (key != null && index != null) {
+ throw new IllegalArgumentException(
+ "provided both key and index: " + config);
+ }
+ if (key == null && index == null) {
+ throw new IllegalArgumentException(
+ "either key or index must be provided: " + config);
+ }
+ this.key = index != null
+ ? String.valueOf(index)
+ : key;
+ }
+
+ @Override
+ public void resolve(
+ final LogEvent logEvent,
+ final JsonWriter jsonWriter) {
+ final String value = MAIN_MAP_LOOKUP.lookup(key);
+ jsonWriter.writeString(value);
+ }
+
+}
diff --git a/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/MainMapResolverFactory.java b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/MainMapResolverFactory.java
new file mode 100644
index 0000000..83b93a1
--- /dev/null
+++ b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/MainMapResolverFactory.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.layout.json.template.resolver;
+
+final class MainMapResolverFactory implements EventResolverFactory<MainMapResolver> {
+
+ private static final MainMapResolverFactory INSTANCE = new MainMapResolverFactory();
+
+ private MainMapResolverFactory() {}
+
+ static MainMapResolverFactory getInstance() {
+ return INSTANCE;
+ }
+
+ @Override
+ public String getName() {
+ return MainMapResolver.getName();
+ }
+
+ @Override
+ public MainMapResolver create(
+ final EventResolverContext context,
+ final TemplateResolverConfig config) {
+ return new MainMapResolver(config);
+ }
+
+}
diff --git a/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/MapResolver.java b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/MapResolver.java
new file mode 100644
index 0000000..21d125c
--- /dev/null
+++ b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/MapResolver.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.layout.json.template.resolver;
+
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.layout.json.template.util.JsonWriter;
+import org.apache.logging.log4j.message.MapMessage;
+import org.apache.logging.log4j.message.Message;
+import org.apache.logging.log4j.util.IndexedReadOnlyStringMap;
+
+/**
+ * {@link MapMessage} field resolver.
+ *
+ * <h3>Configuration</h3>
+ *
+ * <pre>
+ * config = key , [ stringified ]
+ * key = "key" -> string
+ * stringified = "stringified" -> boolean
+ * </pre>
+ *
+ * <h3>Examples</h3>
+ *
+ * Resolve the <tt>userRole</tt> field of the message:
+ *
+ * <pre>
+ * {
+ * "$resolver": "map",
+ * "key": "userRole"
+ * }
+ * </pre>
+ */
+final class MapResolver implements EventResolver {
+
+ private final String key;
+
+ private final boolean stringified;
+
+ static String getName() {
+ return "map";
+ }
+
+ MapResolver(final TemplateResolverConfig config) {
+ this.key = config.getString("key");
+ this.stringified = config.getBoolean("stringified", false);
+ if (key == null) {
+ throw new IllegalArgumentException("missing key: " + config);
+ }
+ }
+
+ @Override
+ public boolean isResolvable(final LogEvent logEvent) {
+ return logEvent.getMessage() instanceof MapMessage;
+ }
+
+ @Override
+ public void resolve(
+ final LogEvent logEvent,
+ final JsonWriter jsonWriter) {
+ final Message message = logEvent.getMessage();
+ if (!(message instanceof MapMessage)) {
+ jsonWriter.writeNull();
+ } else {
+ @SuppressWarnings("unchecked")
+ MapMessage<?, Object> mapMessage = (MapMessage<?, Object>) message;
+ final IndexedReadOnlyStringMap map = mapMessage.getIndexedReadOnlyStringMap();
+ final Object value = map.getValue(key);
+ if (stringified) {
+ final String stringifiedValue = String.valueOf(value);
+ jsonWriter.writeString(stringifiedValue);
+ } else {
+ jsonWriter.writeValue(value);
+ }
+ }
+ }
+
+}
diff --git a/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/MapResolverFactory.java b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/MapResolverFactory.java
new file mode 100644
index 0000000..df57601
--- /dev/null
+++ b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/MapResolverFactory.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.layout.json.template.resolver;
+
+final class MapResolverFactory implements EventResolverFactory<MapResolver> {
+
+ private static final MapResolverFactory INSTANCE = new MapResolverFactory();
+
+ private MapResolverFactory() {}
+
+ static MapResolverFactory getInstance() {
+ return INSTANCE;
+ }
+
+ @Override
+ public String getName() {
+ return MapResolver.getName();
+ }
+
+ @Override
+ public MapResolver create(
+ final EventResolverContext context,
+ final TemplateResolverConfig config) {
+ return new MapResolver(config);
+ }
+
+}
diff --git a/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/MarkerResolver.java b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/MarkerResolver.java
new file mode 100644
index 0000000..0bef3ff
--- /dev/null
+++ b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/MarkerResolver.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.layout.json.template.resolver;
+
+import org.apache.logging.log4j.Marker;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.layout.json.template.util.JsonWriter;
+
+/**
+ * A {@link Marker} resolver.
+ *
+ * <h3>Configuration</h3>
+ *
+ * <pre>
+ * config = "field" -> "name"
+ * </pre>
+ *
+ * <h3>Examples</h3>
+ *
+ * Resolve the marker name:
+ *
+ * <pre>
+ * {
+ * "$resolver": "marker",
+ * "field": "name"
+ * }
+ * </pre>
+ */
+final class MarkerResolver implements EventResolver {
+
+ private static final TemplateResolver<LogEvent> NAME_RESOLVER =
+ (final LogEvent logEvent, final JsonWriter jsonWriter) -> {
+ final Marker marker = logEvent.getMarker();
+ if (marker == null) {
+ jsonWriter.writeNull();
+ } else {
+ jsonWriter.writeString(marker.getName());
+ }
+ };
+
+ private final TemplateResolver<LogEvent> internalResolver;
+
+ MarkerResolver(final TemplateResolverConfig config) {
+ this.internalResolver = createInternalResolver(config);
+ }
+
+ private TemplateResolver<LogEvent> createInternalResolver(
+ final TemplateResolverConfig config) {
+ final String fieldName = config.getString("field");
+ if ("name".equals(fieldName)) {
+ return NAME_RESOLVER;
+ }
+ throw new IllegalArgumentException("unknown field: " + config);
+ }
+
+ static String getName() {
+ return "marker";
+ }
+
+ @Override
+ public boolean isResolvable(final LogEvent logEvent) {
+ return logEvent.getMarker() != null;
+ }
+
+ @Override
+ public void resolve(
+ final LogEvent logEvent,
+ final JsonWriter jsonWriter) {
+ internalResolver.resolve(logEvent, jsonWriter);
+ }
+
+}
diff --git a/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/MarkerResolverFactory.java b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/MarkerResolverFactory.java
new file mode 100644
index 0000000..2d4a2cb
--- /dev/null
+++ b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/MarkerResolverFactory.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.layout.json.template.resolver;
+
+final class MarkerResolverFactory implements EventResolverFactory<MarkerResolver> {
+
+ private static final MarkerResolverFactory INSTANCE = new MarkerResolverFactory();
+
+ static MarkerResolverFactory getInstance() {
+ return INSTANCE;
+ }
+
+ private MarkerResolverFactory() {}
+
+ @Override
+ public String getName() {
+ return MarkerResolver.getName();
+ }
+
+ @Override
+ public MarkerResolver create(
+ final EventResolverContext context,
+ final TemplateResolverConfig config) {
+ return new MarkerResolver(config);
+ }
+
+}
diff --git a/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/MessageResolver.java b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/MessageResolver.java
new file mode 100644
index 0000000..492d447
--- /dev/null
+++ b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/MessageResolver.java
@@ -0,0 +1,260 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.logging.log4j.layout.json.template.resolver;
+
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.config.Configuration;
+import org.apache.logging.log4j.core.layout.PatternLayout;
+import org.apache.logging.log4j.core.util.JsonUtils;
+import org.apache.logging.log4j.layout.json.template.util.JsonWriter;
+import org.apache.logging.log4j.message.MapMessage;
+import org.apache.logging.log4j.message.Message;
+import org.apache.logging.log4j.message.MultiformatMessage;
+import org.apache.logging.log4j.message.ObjectMessage;
+import org.apache.logging.log4j.message.SimpleMessage;
+import org.apache.logging.log4j.util.StringBuilderFormattable;
+
+/**
+ * {@link Message} resolver.
+ *
+ * <h3>Configuration</h3>
+ *
+ * <pre>
+ * config = [ stringified ] , [ fallbackKey ]
+ * stringified = "stringified" -> boolean
+ * fallbackKey = "fallbackKey" -> string
+ * </pre>
+ *
+ * <h3>Examples</h3>
+ *
+ * Resolve the message into a string:
+ *
+ * <pre>
+ * {
+ * "$resolver": "message",
+ * "stringified": true
+ * }
+ * </pre>
+ *
+ * Resolve the message such that if it is a {@link ObjectMessage} or {@link
+ * MultiformatMessage} with JSON support, its emitted JSON type (string, list,
+ * object, etc.) will be retained:
+ *
+ * <pre>
+ * {
+ * "$resolver": "message"
+ * }
+ * </pre>
+ *
+ * Given the above configuration, a {@link SimpleMessage} will generate a
+ * <tt>"sample log message"</tt>, whereas a {@link MapMessage} will generate a
+ * <tt>{"action": "login", "sessionId": "87asd97a"}</tt>. Certain indexed log
+ * storage systems (e.g., <a
+ * href="https://www.elastic.co/elasticsearch/">Elasticsearch</a>) will not
+ * allow both values to coexist due to type mismatch: one is a <tt>string</tt>
+ * while the other is an <tt>object</tt>. Here one can use a
+ * <tt>fallbackKey</tt> to work around the problem:
+ *
+ * <pre>
+ * {
+ * "$resolver": "message",
+ * "fallbackKey": "formattedMessage"
+ * }
+ * </pre>
+ *
+ * Using this configuration, a {@link SimpleMessage} will generate a
+ * <tt>{"formattedMessage": "sample log message"}</tt> and a {@link MapMessage}
+ * will generate a <tt>{"action": "login", "sessionId": "87asd97a"}</tt>. Note
+ * that both emitted JSONs are of type <tt>object</tt> and have no
+ * type-conflicting fields.
+ */
+final class MessageResolver implements EventResolver {
+
+ private static final String[] FORMATS = { "JSON" };
+ /**
+ * Default length for new StringBuilder instances: {@value} .
+ */
+ protected static final int DEFAULT_STRING_BUILDER_SIZE = 1024;
+
+ private final EventResolver internalResolver;
+
+ private PatternLayout patternLayout;
+
+ MessageResolver(final Configuration configuration, final TemplateResolverConfig config) {
+ this.internalResolver = createInternalResolver(configuration, config);
+ }
+
+ static String getName() {
+ return "message";
+ }
+
+ private EventResolver createInternalResolver(final Configuration configuration,
+ final TemplateResolverConfig config) {
+ final boolean stringified = config.getBoolean("stringified", false);
+ final String fallbackKey = config.getString("fallbackKey");
+ final String pattern = config.getString("pattern");
+ final boolean includeStacktrace = config.getBoolean("includeStacktrace", true);
+ if (pattern != null) {
+ patternLayout = PatternLayout.newBuilder().setPattern(pattern)
+ .setAlwaysWriteExceptions(includeStacktrace)
+ .setConfiguration(configuration)
+ .build();
+ } else {
+ patternLayout = null;
+ }
+ if (stringified && fallbackKey != null) {
+ throw new IllegalArgumentException(
+ "fallbackKey is not allowed when stringified is enable: " + config);
+ }
+ return stringified
+ ? createStringResolver(fallbackKey)
+ : createObjectResolver(fallbackKey);
+ }
+
+ @Override
+ public void resolve(
+ final LogEvent logEvent,
+ final JsonWriter jsonWriter) {
+ internalResolver.resolve(logEvent, jsonWriter);
+ }
+
+ private EventResolver createStringResolver(final String fallbackKey) {
+ return (final LogEvent logEvent, final JsonWriter jsonWriter) ->
+ resolveString(fallbackKey, logEvent, jsonWriter);
+ }
+
+ private void resolveString(
+ final String fallbackKey,
+ final LogEvent logEvent,
+ final JsonWriter jsonWriter) {
+ if (patternLayout != null) {
+ final StringBuilder messageBuffer = getMessageStringBuilder();
+ patternLayout.serialize(logEvent, messageBuffer);
+ jsonWriter.writeString(messageBuffer.toString());
+ } else {
+ final Message message = logEvent.getMessage();
+ resolveString(fallbackKey, message, jsonWriter);
+ }
+ }
+
+ private void resolveString(
+ final String fallbackKey,
+ final Message message,
+ final JsonWriter jsonWriter) {
+ if (fallbackKey != null) {
+ jsonWriter.writeObjectStart();
+ jsonWriter.writeObjectKey(fallbackKey);
+ }
+ if (message instanceof StringBuilderFormattable) {
+ final StringBuilderFormattable formattable =
+ (StringBuilderFormattable) message;
+ jsonWriter.writeString(formattable);
+ } else {
+ final String formattedMessage = message.getFormattedMessage();
+ jsonWriter.writeString(formattedMessage);
+ }
+ if (fallbackKey != null) {
+ jsonWriter.writeObjectEnd();
+ }
+ }
+
+ private EventResolver createObjectResolver(final String fallbackKey) {
+ return (final LogEvent logEvent, final JsonWriter jsonWriter) -> {
+
+ // Skip custom serializers for SimpleMessage.
+ final Message message = logEvent.getMessage();
+ final boolean simple = message instanceof SimpleMessage;
+ if (!simple) {
+
+ // Try MultiformatMessage serializer.
+ if (writeMultiformatMessage(jsonWriter, message)) {
+ return;
+ }
+
+ // Try ObjectMessage serializer.
+ if (writeObjectMessage(jsonWriter, message)) {
+ return;
+ }
+
+ }
+
+ // Fallback to plain String serializer.
+ resolveString(fallbackKey, logEvent, jsonWriter);
+
+ };
+ }
+
+ private boolean writeMultiformatMessage(
+ final JsonWriter jsonWriter,
+ final Message message) {
+
+ // Check type.
+ if (!(message instanceof MultiformatMessage)) {
+ return false;
+ }
+ final MultiformatMessage multiformatMessage = (MultiformatMessage) message;
+
+ // Check formatter's JSON support.
+ boolean jsonSupported = false;
+ final String[] formats = multiformatMessage.getFormats();
+ for (final String format : formats) {
+ if (FORMATS[0].equalsIgnoreCase(format)) {
+ jsonSupported = true;
+ break;
+ }
+ }
+ if (!jsonSupported) {
+ return false;
+ }
+
+ // Write the formatted JSON.
+ final String messageJson = multiformatMessage.getFormattedMessage(FORMATS);
+ jsonWriter.writeRawString(messageJson);
+ return true;
+
+ }
+
+ private boolean writeObjectMessage(
+ final JsonWriter jsonWriter,
+ final Message message) {
+
+ // Check type.
+ if (!(message instanceof ObjectMessage)) {
+ return false;
+ }
+
+ // Serialize object.
+ final ObjectMessage objectMessage = (ObjectMessage) message;
+ final Object object = objectMessage.getParameter();
+ jsonWriter.writeValue(object);
+ return true;
+
+ }
+
+ private static final ThreadLocal<StringBuilder> messageStringBuilder = new ThreadLocal<>();
+
+ private static StringBuilder getMessageStringBuilder() {
+ StringBuilder result = messageStringBuilder.get();
+ if (result == null) {
+ result = new StringBuilder(DEFAULT_STRING_BUILDER_SIZE);
+ messageStringBuilder.set(result);
+ }
+ result.setLength(0);
+ return result;
+ }
+
+}
diff --git a/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/MessageResolverFactory.java b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/MessageResolverFactory.java
new file mode 100644
index 0000000..59e07f9
--- /dev/null
+++ b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/MessageResolverFactory.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.layout.json.template.resolver;
+
+final class MessageResolverFactory implements EventResolverFactory<MessageResolver> {
+
+ private static final MessageResolverFactory INSTANCE = new MessageResolverFactory();
+
+ private MessageResolverFactory() {}
+
+ static MessageResolverFactory getInstance() {
+ return INSTANCE;
+ }
+
+ @Override
+ public String getName() {
+ return MessageResolver.getName();
+ }
+
+ @Override
+ public MessageResolver create(
+ final EventResolverContext context,
+ final TemplateResolverConfig config) {
+ return new MessageResolver(context.getConfiguration(), config);
+ }
+
+}
diff --git a/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/PatternResolver.java b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/PatternResolver.java
new file mode 100644
index 0000000..d5029e9
--- /dev/null
+++ b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/PatternResolver.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.layout.json.template.resolver;
+
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.layout.PatternLayout;
+import org.apache.logging.log4j.layout.json.template.util.JsonWriter;
+import org.apache.logging.log4j.util.BiConsumer;
+import org.apache.logging.log4j.util.Strings;
+
+import java.util.Optional;
+
+/**
+ * Resolver delegating to {@link PatternLayout}.
+ *
+ * <h3>Configuration</h3>
+ *
+ * <pre>
+ * config = pattern , [ stackTraceEnabled ]
+ * pattern = "pattern" -> string
+ * stackTraceEnabled = "stackTraceEnabled" -> boolean
+ * </pre>
+ *
+ * The default value of <tt>stackTraceEnabled</tt> is inherited from the parent
+ * {@link org.apache.logging.log4j.layout.json.template.JsonTemplateLayout}.
+ *
+ * <h3>Examples</h3>
+ *
+ * Resolve the string produced by <tt>%p %c{1.} [%t] %X{userId} %X %m%ex</tt>
+ * pattern:
+ *
+ * <pre>
+ * {
+ * "$resolver": "pattern",
+ * "pattern": "%p %c{1.} [%t] %X{userId} %X %m%ex"
+ * }
+ * </pre>
+ */
+final class PatternResolver implements EventResolver {
+
+ private final BiConsumer<StringBuilder, LogEvent> emitter;
+
+ PatternResolver(
+ final EventResolverContext context,
+ final TemplateResolverConfig config) {
+ final String pattern = config.getString("pattern");
+ if (Strings.isBlank(pattern)) {
+ throw new IllegalArgumentException("blank pattern: " + config);
+ }
+ final boolean stackTraceEnabled = Optional
+ .ofNullable(config.getBoolean("stackTraceEnabled"))
+ .orElse(context.isStackTraceEnabled());
+ final PatternLayout patternLayout = PatternLayout
+ .newBuilder()
+ .setConfiguration(context.getConfiguration())
+ .setCharset(context.getCharset())
+ .setPattern(pattern)
+ .setAlwaysWriteExceptions(stackTraceEnabled)
+ .build();
+ this.emitter = (final StringBuilder stringBuilder, final LogEvent logEvent) ->
+ patternLayout.serialize(logEvent, stringBuilder);
+ }
+
+ static String getName() {
+ return "pattern";
+ }
+
+ @Override
+ public void resolve(final LogEvent logEvent, final JsonWriter jsonWriter) {
+ jsonWriter.writeString(emitter, logEvent);
+ }
+
+}
diff --git a/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/PatternResolverFactory.java b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/PatternResolverFactory.java
new file mode 100644
index 0000000..e3ddaf9
--- /dev/null
+++ b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/PatternResolverFactory.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.layout.json.template.resolver;
+
+final class PatternResolverFactory implements EventResolverFactory<PatternResolver> {
+
+ private static final PatternResolverFactory INSTANCE = new PatternResolverFactory();
+
+ private PatternResolverFactory() {}
+
+ static PatternResolverFactory getInstance() {
+ return INSTANCE;
+ }
+
+ @Override
+ public String getName() {
+ return PatternResolver.getName();
+ }
+
+ @Override
+ public PatternResolver create(
+ final EventResolverContext context,
+ final TemplateResolverConfig config) {
+ return new PatternResolver(context, config);
+ }
+
+}
diff --git a/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/SourceResolver.java b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/SourceResolver.java
new file mode 100644
index 0000000..8f857d0
--- /dev/null
+++ b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/SourceResolver.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.layout.json.template.resolver;
+
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.layout.json.template.util.JsonWriter;
+
+/**
+ * Resolver for the {@link StackTraceElement} returned by {@link LogEvent#getSource()}.
+ *
+ * Note that this resolver is toggled by {@link
+ * org.apache.logging.log4j.layout.json.template.JsonTemplateLayout.Builder#setLocationInfoEnabled(boolean)}
+ * method.
+ *
+ * <h3>Configuration</h3>
+ *
+ * <pre>
+ * config = "field" -> (
+ * "className" |
+ * "fileName" |
+ * "methodName" |
+ * "lineNumber" )
+ * </pre>
+ *
+ * <h3>Examples</h3>
+ *
+ * Resolve the line number:
+ *
+ * <pre>
+ * {
+ * "$resolver": "source",
+ * "field": "lineNumber"
+ * }
+ * </pre>
+ */
+final class SourceResolver implements EventResolver {
+
+ private static final EventResolver NULL_RESOLVER =
+ (final LogEvent value, final JsonWriter jsonWriter) ->
+ jsonWriter.writeNull();
+
+ private static final EventResolver CLASS_NAME_RESOLVER =
+ (final LogEvent logEvent, final JsonWriter jsonWriter) -> {
+ final StackTraceElement logEventSource = logEvent.getSource();
+ if (logEventSource == null) {
+ jsonWriter.writeNull();
+ } else {
+ final String sourceClassName = logEventSource.getClassName();
+ jsonWriter.writeString(sourceClassName);
+ }
+ };
+
+ private static final EventResolver FILE_NAME_RESOLVER =
+ (final LogEvent logEvent, final JsonWriter jsonWriter) -> {
+ final StackTraceElement logEventSource = logEvent.getSource();
+ if (logEventSource == null) {
+ jsonWriter.writeNull();
+ } else {
+ final String sourceFileName = logEventSource.getFileName();
+ jsonWriter.writeString(sourceFileName);
+ }
+ };
+
+ private static final EventResolver LINE_NUMBER_RESOLVER =
+ (final LogEvent logEvent, final JsonWriter jsonWriter) -> {
+ final StackTraceElement logEventSource = logEvent.getSource();
+ if (logEventSource == null) {
+ jsonWriter.writeNull();
+ } else {
+ final int sourceLineNumber = logEventSource.getLineNumber();
+ jsonWriter.writeNumber(sourceLineNumber);
+ }
+ };
+
+ private static final EventResolver METHOD_NAME_RESOLVER =
+ (final LogEvent logEvent, final JsonWriter jsonWriter) -> {
+ final StackTraceElement logEventSource = logEvent.getSource();
+ if (logEventSource == null) {
+ jsonWriter.writeNull();
+ } else {
+ final String sourceMethodName = logEventSource.getMethodName();
+ jsonWriter.writeString(sourceMethodName);
+ }
+ };
+
+ private final boolean locationInfoEnabled;
+
+ private final EventResolver internalResolver;
+
+ SourceResolver(
+ final EventResolverContext context,
+ final TemplateResolverConfig config) {
+ this.locationInfoEnabled = context.isLocationInfoEnabled();
+ this.internalResolver = createInternalResolver(context, config);
+ }
+
+ private static EventResolver createInternalResolver(
+ final EventResolverContext context,
+ final TemplateResolverConfig config) {
+ if (!context.isLocationInfoEnabled()) {
+ return NULL_RESOLVER;
+ }
+ final String fieldName = config.getString("field");
+ switch (fieldName) {
+ case "className": return CLASS_NAME_RESOLVER;
+ case "fileName": return FILE_NAME_RESOLVER;
+ case "lineNumber": return LINE_NUMBER_RESOLVER;
+ case "methodName": return METHOD_NAME_RESOLVER;
+ }
+ throw new IllegalArgumentException("unknown field: " + config);
+ }
+
+ static String getName() {
+ return "source";
+ }
+
+ @Override
+ public boolean isResolvable() {
+ return locationInfoEnabled;
+ }
+
+ @Override
+ public boolean isResolvable(final LogEvent logEvent) {
+ return locationInfoEnabled && logEvent.getSource() != null;
+ }
+
+ @Override
+ public void resolve(
+ final LogEvent logEvent,
+ final JsonWriter jsonWriter) {
+ internalResolver.resolve(logEvent, jsonWriter);
+ }
+
+}
diff --git a/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/SourceResolverFactory.java b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/SourceResolverFactory.java
new file mode 100644
index 0000000..3f1e957
--- /dev/null
+++ b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/SourceResolverFactory.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.layout.json.template.resolver;
+
+final class SourceResolverFactory implements EventResolverFactory<SourceResolver> {
+
+ private static final SourceResolverFactory INSTANCE = new SourceResolverFactory();
+
+ private SourceResolverFactory() {}
+
+ static SourceResolverFactory getInstance() {
+ return INSTANCE;
+ }
+
+ @Override
+ public String getName() {
+ return SourceResolver.getName();
+ }
+
+ @Override
+ public SourceResolver create(
+ final EventResolverContext context,
+ final TemplateResolverConfig config) {
+ return new SourceResolver(context, config);
+ }
+
+}
diff --git a/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/StackTraceElementObjectResolver.java b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/StackTraceElementObjectResolver.java
new file mode 100644
index 0000000..1c8a483
--- /dev/null
+++ b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/StackTraceElementObjectResolver.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.layout.json.template.resolver;
+
+import org.apache.logging.log4j.layout.json.template.util.JsonWriter;
+
+/**
+ * {@link StackTraceElement} resolver.
+ *
+ * <h3>Configuration</h3>
+ *
+ * <pre>
+ * config = "field" -> (
+ * "className" |
+ * "fileName" |
+ * "methodName" |
+ * "lineNumber" )
+ * </pre>
+ *
+ * <h3>Examples</h3>
+ *
+ * Resolve the line number:
+ *
+ * <pre>
+ * {
+ * "$resolver": "source",
+ * "field": "lineNumber"
+ * }
+ * </pre>
+ */
+final class StackTraceElementObjectResolver implements TemplateResolver<StackTraceElement> {
+
+ private static final TemplateResolver<StackTraceElement> CLASS_NAME_RESOLVER =
+ (final StackTraceElement stackTraceElement, final JsonWriter jsonWriter) ->
+ jsonWriter.writeString(stackTraceElement.getClassName());
+
+ private static final TemplateResolver<StackTraceElement> METHOD_NAME_RESOLVER =
+ (final StackTraceElement stackTraceElement, final JsonWriter jsonWriter) ->
+ jsonWriter.writeString(stackTraceElement.getMethodName());
+
+ private static final TemplateResolver<StackTraceElement> FILE_NAME_RESOLVER =
+ (final StackTraceElement stackTraceElement, final JsonWriter jsonWriter) ->
+ jsonWriter.writeString(stackTraceElement.getFileName());
+
+ private static final TemplateResolver<StackTraceElement> LINE_NUMBER_RESOLVER =
+ (final StackTraceElement stackTraceElement, final JsonWriter jsonWriter) ->
+ jsonWriter.writeNumber(stackTraceElement.getLineNumber());
+
+ private final TemplateResolver<StackTraceElement> internalResolver;
+
+ StackTraceElementObjectResolver(final TemplateResolverConfig config) {
+ this.internalResolver = createInternalResolver(config);
+ }
+
+ private TemplateResolver<StackTraceElement> createInternalResolver(
+ final TemplateResolverConfig config) {
+ final String fieldName = config.getString("field");
+ switch (fieldName) {
+ case "className": return CLASS_NAME_RESOLVER;
+ case "methodName": return METHOD_NAME_RESOLVER;
+ case "fileName": return FILE_NAME_RESOLVER;
+ case "lineNumber": return LINE_NUMBER_RESOLVER;
+ }
+ throw new IllegalArgumentException("unknown field: " + config);
+ }
+
+ static String getName() {
+ return "stackTraceElement";
+ }
+
+ @Override
+ public void resolve(
+ final StackTraceElement stackTraceElement,
+ final JsonWriter jsonWriter) {
+ internalResolver.resolve(stackTraceElement, jsonWriter);
+ }
+
+}
diff --git a/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/StackTraceElementObjectResolverContext.java b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/StackTraceElementObjectResolverContext.java
new file mode 100644
index 0000000..6e42237
--- /dev/null
+++ b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/StackTraceElementObjectResolverContext.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.layout.json.template.resolver;
+
+import org.apache.logging.log4j.core.lookup.StrSubstitutor;
+import org.apache.logging.log4j.layout.json.template.util.JsonWriter;
+
+import java.util.Map;
+import java.util.Objects;
+
+public final class StackTraceElementObjectResolverContext
+ implements TemplateResolverContext<StackTraceElement, StackTraceElementObjectResolverContext> {
+
+ private final StrSubstitutor substitutor;
+
+ private final JsonWriter jsonWriter;
+
+ private StackTraceElementObjectResolverContext(final Builder builder) {
+ this.substitutor = builder.substitutor;
+ this.jsonWriter = builder.jsonWriter;
+ }
+
+ @Override
+ public Class<StackTraceElementObjectResolverContext> getContextClass() {
+ return StackTraceElementObjectResolverContext.class;
+ }
+
+ @Override
+ public Map<String, TemplateResolverFactory<StackTraceElement, StackTraceElementObjectResolverContext, ? extends TemplateResolver<StackTraceElement>>> getResolverFactoryByName() {
+ return StackTraceElementObjectResolverFactories.getResolverFactoryByName();
+ }
+
+ @Override
+ public StrSubstitutor getSubstitutor() {
+ return substitutor;
+ }
+
+ @Override
+ public JsonWriter getJsonWriter() {
+ return jsonWriter;
+ }
+
+ public static Builder newBuilder() {
+ return new Builder();
+ }
+
+ public static class Builder {
+
+ private StrSubstitutor substitutor;
+
+ private JsonWriter jsonWriter;
+
+ private Builder() {
+ // Do nothing.
+ }
+
+ public Builder setSubstitutor(final StrSubstitutor substitutor) {
+ this.substitutor = substitutor;
+ return this;
+ }
+
+ public Builder setJsonWriter(final JsonWriter jsonWriter) {
+ this.jsonWriter = jsonWriter;
+ return this;
+ }
+
+ public StackTraceElementObjectResolverContext build() {
+ validate();
+ return new StackTraceElementObjectResolverContext(this);
+ }
+
+ private void validate() {
+ Objects.requireNonNull(substitutor, "substitutor");
+ Objects.requireNonNull(jsonWriter, "jsonWriter");
+ }
+
+ }
+
+}
diff --git a/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/StackTraceElementObjectResolverFactories.java b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/StackTraceElementObjectResolverFactories.java
new file mode 100644
index 0000000..90af9ab
--- /dev/null
+++ b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/StackTraceElementObjectResolverFactories.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.layout.json.template.resolver;
+
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+enum StackTraceElementObjectResolverFactories {;
+
+ private static final Map<String, TemplateResolverFactory<StackTraceElement, StackTraceElementObjectResolverContext, ? extends TemplateResolver<StackTraceElement>>> RESOLVER_FACTORY_BY_NAME =
+ createResolverFactoryByName();
+
+ private static Map<String, TemplateResolverFactory<StackTraceElement, StackTraceElementObjectResolverContext, ? extends TemplateResolver<StackTraceElement>>> createResolverFactoryByName() {
+ final Map<String, TemplateResolverFactory<StackTraceElement, StackTraceElementObjectResolverContext, ? extends TemplateResolver<StackTraceElement>>> resolverFactoryByName = new LinkedHashMap<>();
+ final StackTraceElementObjectResolverFactory stackTraceElementObjectResolverFactory = StackTraceElementObjectResolverFactory.getInstance();
+ resolverFactoryByName.put(stackTraceElementObjectResolverFactory.getName(), stackTraceElementObjectResolverFactory);
+ return Collections.unmodifiableMap(resolverFactoryByName);
+ }
+
+ static Map<String, TemplateResolverFactory<StackTraceElement, StackTraceElementObjectResolverContext, ? extends TemplateResolver<StackTraceElement>>> getResolverFactoryByName() {
+ return RESOLVER_FACTORY_BY_NAME;
+ }
+
+}
diff --git a/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/StackTraceElementObjectResolverFactory.java b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/StackTraceElementObjectResolverFactory.java
new file mode 100644
index 0000000..a07694c
--- /dev/null
+++ b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/StackTraceElementObjectResolverFactory.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.layout.json.template.resolver;
+
+final class StackTraceElementObjectResolverFactory
+ implements TemplateResolverFactory<StackTraceElement, StackTraceElementObjectResolverContext, StackTraceElementObjectResolver> {
+
+ private static final StackTraceElementObjectResolverFactory INSTANCE =
+ new StackTraceElementObjectResolverFactory();
+
+ private StackTraceElementObjectResolverFactory() {}
+
+ public static StackTraceElementObjectResolverFactory getInstance() {
+ return INSTANCE;
+ }
+
+ @Override
+ public String getName() {
+ return StackTraceElementObjectResolver.getName();
+ }
+
+ @Override
+ public StackTraceElementObjectResolver create(
+ final StackTraceElementObjectResolverContext context,
+ final TemplateResolverConfig config) {
+ return new StackTraceElementObjectResolver(config);
+ }
+
+}
diff --git a/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/StackTraceObjectResolver.java b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/StackTraceObjectResolver.java
new file mode 100644
index 0000000..53a9ce4
--- /dev/null
+++ b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/StackTraceObjectResolver.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.layout.json.template.resolver;
+
+import org.apache.logging.log4j.layout.json.template.util.JsonWriter;
+
+final class StackTraceObjectResolver implements StackTraceResolver {
+
+ private final TemplateResolver<StackTraceElement> stackTraceElementResolver;
+
+ StackTraceObjectResolver(final TemplateResolver<StackTraceElement> stackTraceElementResolver) {
+ this.stackTraceElementResolver = stackTraceElementResolver;
+ }
+
+ @Override
+ public void resolve(
+ final Throwable throwable,
+ final JsonWriter jsonWriter) {
+ // Following check against the stacktrace element count is not
+ // implemented in isResolvable(), since Throwable#getStackTrace() incurs
+ // a significant cloning cost.
+ final StackTraceElement[] stackTraceElements = throwable.getStackTrace();
+ if (stackTraceElements.length == 0) {
+ jsonWriter.writeNull();
+ } else {
+ jsonWriter.writeArrayStart();
+ for (int stackTraceElementIndex = 0;
+ stackTraceElementIndex < stackTraceElements.length;
+ stackTraceElementIndex++) {
+ if (stackTraceElementIndex > 0) {
+ jsonWriter.writeSeparator();
+ }
+ final StackTraceElement stackTraceElement = stackTraceElements[stackTraceElementIndex];
+ stackTraceElementResolver.resolve(stackTraceElement, jsonWriter);
+ }
+ jsonWriter.writeArrayEnd();
+ }
+ }
+
+}
diff --git a/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/StackTraceResolver.java b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/StackTraceResolver.java
new file mode 100644
index 0000000..8275193
--- /dev/null
+++ b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/StackTraceResolver.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.layout.json.template.resolver;
+
+interface StackTraceResolver extends TemplateResolver<Throwable> {}
diff --git a/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/StackTraceStringResolver.java b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/StackTraceStringResolver.java
new file mode 100644
index 0000000..d744070
--- /dev/null
+++ b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/StackTraceStringResolver.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.layout.json.template.resolver;
+
+import org.apache.logging.log4j.layout.json.template.util.TruncatingBufferedPrintWriter;
+import org.apache.logging.log4j.layout.json.template.util.JsonWriter;
+import org.apache.logging.log4j.layout.json.template.util.Recycler;
+
+import java.util.function.Supplier;
+
+final class StackTraceStringResolver implements StackTraceResolver {
+
+ private final Recycler<TruncatingBufferedPrintWriter> writerRecycler;
+
+ StackTraceStringResolver(final EventResolverContext context) {
+ final Supplier<TruncatingBufferedPrintWriter> writerSupplier =
+ () -> TruncatingBufferedPrintWriter.ofCapacity(
+ context.getMaxStringByteCount());
+ this.writerRecycler = context
+ .getRecyclerFactory()
+ .create(writerSupplier, TruncatingBufferedPrintWriter::close);
+ }
+
+ @Override
+ public void resolve(
+ final Throwable throwable,
+ final JsonWriter jsonWriter) {
+ final TruncatingBufferedPrintWriter writer = writerRecycler.acquire();
+ try {
+ throwable.printStackTrace(writer);
+ jsonWriter.writeString(writer.getBuffer(), 0, writer.getPosition());
+ } finally {
+ writerRecycler.release(writer);
+ }
+ }
+
+}
diff --git a/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/TemplateResolver.java b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/TemplateResolver.java
new file mode 100644
index 0000000..a251075
--- /dev/null
+++ b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/TemplateResolver.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.layout.json.template.resolver;
+
+import org.apache.logging.log4j.layout.json.template.util.JsonWriter;
+
+@FunctionalInterface
+public interface TemplateResolver<V> {
+
+ default boolean isFlattening() {
+ return false;
+ }
+
+ default boolean isResolvable() {
+ return true;
+ }
+
+ default boolean isResolvable(V value) {
+ return true;
+ }
+
+ void resolve(V value, JsonWriter jsonWriter);
+
+ default void resolve(V value, JsonWriter jsonWriter, boolean succeedingEntry) {
+ resolve(value, jsonWriter);
+ }
+
+}
diff --git a/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/TemplateResolverConfig.java b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/TemplateResolverConfig.java
new file mode 100644
index 0000000..a83fffa
--- /dev/null
+++ b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/TemplateResolverConfig.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.layout.json.template.resolver;
+
+import org.apache.logging.log4j.layout.json.template.util.MapAccessor;
+
+import java.util.Map;
+
+class TemplateResolverConfig extends MapAccessor {
+
+ TemplateResolverConfig(final Map<String, Object> map) {
+ super(map);
+ }
+
+}
diff --git a/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/TemplateResolverContext.java b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/TemplateResolverContext.java
new file mode 100644
index 0000000..74687d2
--- /dev/null
+++ b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/TemplateResolverContext.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.layout.json.template.resolver;
+
+import org.apache.logging.log4j.core.lookup.StrSubstitutor;
+import org.apache.logging.log4j.layout.json.template.util.JsonWriter;
+
+import java.util.Map;
+
+interface TemplateResolverContext<V, C extends TemplateResolverContext<V, C>> {
+
+ Class<C> getContextClass();
+
+ Map<String, TemplateResolverFactory<V, C, ? extends TemplateResolver<V>>> getResolverFactoryByName();
+
+ StrSubstitutor getSubstitutor();
+
+ JsonWriter getJsonWriter();
+
+}
diff --git a/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/TemplateResolverFactory.java b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/TemplateResolverFactory.java
new file mode 100644
index 0000000..3e3c8ef
--- /dev/null
+++ b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/TemplateResolverFactory.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.layout.json.template.resolver;
+
+interface TemplateResolverFactory<V, C extends TemplateResolverContext<V, C>, R extends TemplateResolver<V>> {
+
+ String getName();
+
+ R create(C context, TemplateResolverConfig config);
+
+}
diff --git a/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/TemplateResolvers.java b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/TemplateResolvers.java
new file mode 100644
index 0000000..a4b1165
--- /dev/null
+++ b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/TemplateResolvers.java
@@ -0,0 +1,414 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.logging.log4j.layout.json.template.resolver;
+
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.layout.json.template.JsonTemplateLayout.EventTemplateAdditionalField;
+import org.apache.logging.log4j.layout.json.template.util.JsonReader;
+import org.apache.logging.log4j.layout.json.template.util.JsonWriter;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+public enum TemplateResolvers {;
+
+ private static final String RESOLVER_FIELD_NAME = "$resolver";
+
+ private static abstract class UnresolvableTemplateResolver
+ implements TemplateResolver<Object> {
+
+ @Override
+ public final boolean isResolvable() {
+ return false;
+ }
+
+ @Override
+ public final boolean isResolvable(Object value) {
+ return false;
+ }
+
+ }
+
+ private static final TemplateResolver<?> EMPTY_ARRAY_RESOLVER =
+ new UnresolvableTemplateResolver() {
+ @Override
+ public void resolve(final Object value, final JsonWriter jsonWriter) {
+ jsonWriter.writeArrayStart();
+ jsonWriter.writeArrayEnd();
+ }
+ };
+
+ private static final TemplateResolver<?> EMPTY_OBJECT_RESOLVER =
+ new UnresolvableTemplateResolver() {
+ @Override
+ public void resolve(final Object value, final JsonWriter jsonWriter) {
+ jsonWriter.writeObjectStart();
+ jsonWriter.writeObjectEnd();
+ }
+ };
+
+ private static final TemplateResolver<?> NULL_RESOLVER =
+ new UnresolvableTemplateResolver() {
+ @Override
+ public void resolve(final Object value, final JsonWriter jsonWriter) {
+ jsonWriter.writeNull();
+ }
+ };
+
+ public static <V, C extends TemplateResolverContext<V, C>> TemplateResolver<V> ofTemplate(
+ final C context,
+ final String template) {
+
+ // Read the template.
+ final Object node;
+ try {
+ node = JsonReader.read(template);
+ } catch (final Exception error) {
+ final String message = String.format("failed parsing template (template=%s)", template);
+ throw new RuntimeException(message, error);
+ }
+
+ // Append the additional fields.
+ if (context instanceof EventResolverContext) {
+ final EventResolverContext eventResolverContext = (EventResolverContext) context;
+ final EventTemplateAdditionalField[] additionalFields = eventResolverContext.getAdditionalFields();
+ appendAdditionalFields(node, additionalFields);
+ }
+
+ // Resolve the template.
+ return ofObject(context, node);
+
+ }
+
+ private static void appendAdditionalFields(
+ final Object node,
+ EventTemplateAdditionalField[] additionalFields) {
+ if (additionalFields.length > 0) {
+
+ // Check that the root is an object node.
+ final Map<String, Object> objectNode;
+ try {
+ @SuppressWarnings("unchecked")
+ final Map<String, Object> map = (Map<String, Object>) node;
+ objectNode = map;
+ } catch (final ClassCastException error) {
+ final String message = String.format(
+ "was expecting an object to merge additional fields: %s",
+ node.getClass().getName());
+ throw new IllegalArgumentException(message);
+ }
+
+ // Merge additional fields.
+ for (final EventTemplateAdditionalField additionalField : additionalFields) {
+ final String additionalFieldKey = additionalField.getKey();
+ final Object additionalFieldValue;
+ switch (additionalField.getType()) {
+ case STRING:
+ additionalFieldValue = additionalField.getValue();
+ break;
+ case JSON:
+ try {
+ additionalFieldValue = JsonReader.read(additionalField.getValue());
+ } catch (final Exception error) {
+ final String message = String.format(
+ "failed reading JSON provided by additional field: %s",
+ additionalFieldKey);
+ throw new IllegalArgumentException(message, error);
+ }
+ break;
+ default: {
+ final String message = String.format(
+ "unknown type %s for additional field: %s",
+ additionalFieldKey, additionalField.getType());
+ throw new IllegalArgumentException(message);
+ }
+ }
+ objectNode.put(additionalFieldKey, additionalFieldValue);
+ }
+
+ }
+ }
+
+ private static <V, C extends TemplateResolverContext<V, C>> TemplateResolver<V> ofObject(
+ final C context,
+ final Object object) {
+ if (object == null) {
+ @SuppressWarnings("unchecked")
+ final TemplateResolver<V> nullResolver = (TemplateResolver<V>) NULL_RESOLVER;
+ return nullResolver;
+ } else if (object instanceof List) {
+ @SuppressWarnings("unchecked")
+ final List<Object> list = (List<Object>) object;
+ return ofList(context, list);
+ } else if (object instanceof Map) {
+ @SuppressWarnings("unchecked")
+ final Map<String, Object> map = (Map<String, Object>) object;
+ return ofMap(context, map);
+ } else if (object instanceof String) {
+ final String string = (String) object;
+ return ofString(context, string);
+ } else if (object instanceof Number) {
+ final Number number = (Number) object;
+ return ofNumber(number);
+ } else if (object instanceof Boolean) {
+ final boolean value = (boolean) object;
+ return ofBoolean(value);
+ } else {
+ final String message = String.format(
+ "invalid JSON node type (class=%s)",
+ object.getClass().getName());
+ throw new IllegalArgumentException(message);
+ }
+ }
+
+ private static <V, C extends TemplateResolverContext<V, C>> TemplateResolver<V> ofList(
+ final C context,
+ final List<Object> list) {
+
+ // Create resolver for each children.
+ final List<TemplateResolver<V>> itemResolvers = list
+ .stream()
+ .map(item -> {
+ final TemplateResolver<V> itemResolver = ofObject(context, item);
+ if (itemResolver.isFlattening()) {
+ throw new IllegalArgumentException(
+ "flattening resolvers are not allowed in lists");
+ }
+ return itemResolver;
+ })
+ .collect(Collectors.toList());
+
+ // Short-circuit if the array is empty.
+ if (itemResolvers.isEmpty()) {
+ @SuppressWarnings("unchecked")
+ final TemplateResolver<V> emptyArrayResolver =
+ (TemplateResolver<V>) EMPTY_ARRAY_RESOLVER;
+ return emptyArrayResolver;
+ }
+
+ // Create a parent resolver collecting each child resolver execution.
+ return (final V value, final JsonWriter jsonWriter) -> {
+ jsonWriter.writeArrayStart();
+ for (int itemResolverIndex = 0;
+ itemResolverIndex < itemResolvers.size();
+ itemResolverIndex++) {
+ if (itemResolverIndex > 0) {
+ jsonWriter.writeSeparator();
+ }
+ final TemplateResolver<V> itemResolver = itemResolvers.get(itemResolverIndex);
+ itemResolver.resolve(value, jsonWriter);
+ }
+ jsonWriter.writeArrayEnd();
+ };
+
+ }
+
+ private static <V, C extends TemplateResolverContext<V, C>> TemplateResolver<V> ofMap(
+ final C context,
+ final Map<String, Object> map) {
+
+ // Check if this is a resolver request.
+ if (map.containsKey(RESOLVER_FIELD_NAME)) {
+ return ofResolver(context, map);
+ }
+
+ // Create resolver for each object field.
+ final List<String> fieldNames = new ArrayList<>();
+ final List<TemplateResolver<V>> fieldResolvers = new ArrayList<>();
+ map.forEach((fieldName, fieldValue) -> {
+ final TemplateResolver<V> fieldResolver = ofObject(context, fieldValue);
+ final boolean resolvable = fieldResolver.isResolvable();
+ if (resolvable) {
+ fieldNames.add(fieldName);
+ fieldResolvers.add(fieldResolver);
+ }
+ });
+
+ // Short-circuit if the object is empty.
+ final int fieldCount = fieldNames.size();
+ if (fieldCount == 0) {
+ @SuppressWarnings("unchecked")
+ final TemplateResolver<V> emptyObjectResolver =
+ (TemplateResolver<V>) EMPTY_OBJECT_RESOLVER;
+ return emptyObjectResolver;
+ }
+
+ // Prepare field names to avoid escape and truncation costs at runtime.
+ final List<String> fieldPrefixes = fieldNames
+ .stream()
+ .map(fieldName -> {
+ try (JsonWriter jsonWriter = context.getJsonWriter()) {
+ jsonWriter.writeString(fieldName);
+ jsonWriter.getStringBuilder().append(':');
+ return jsonWriter.getStringBuilder().toString();
+ }
+ })
+ .collect(Collectors.toList());
+
+ return new TemplateResolver<V>() {
+
+ @Override
+ public boolean isResolvable() {
+ // We have already excluded unresolvable ones while collecting
+ // the resolvers. Hence it is safe to return true here.
+ return true;
+ }
+
+ /**
+ * The parent resolver checking if each child is resolvable given
+ * the passed {@code value}.
+ *
+ * This is an optimization to skip the rendering of a parent if all
+ * its children are not resolvable given the passed {@code value}.
+ */
+ @Override
+ public boolean isResolvable(final V value) {
+ for (int fieldIndex = 0; fieldIndex < fieldCount; fieldIndex++) {
+ final TemplateResolver<V> fieldResolver = fieldResolvers.get(fieldIndex);
+ final boolean resolvable = fieldResolver.isResolvable(value);
+ if (resolvable) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * The parent resolver combining all child resolver executions.
+ */
+ @Override
+ public void resolve(final V value, final JsonWriter jsonWriter) {
+ final StringBuilder jsonWriterStringBuilder = jsonWriter.getStringBuilder();
+ jsonWriter.writeObjectStart();
+ for (int resolvedFieldCount = 0, fieldIndex = 0; fieldIndex < fieldCount; fieldIndex++) {
+ final TemplateResolver<V> fieldResolver = fieldResolvers.get(fieldIndex);
+ final boolean resolvable = fieldResolver.isResolvable(value);
+ if (!resolvable) {
+ continue;
+ }
+ final boolean succeedingEntry = resolvedFieldCount > 0;
+ final boolean flattening = fieldResolver.isFlattening();
+ if (flattening) {
+ final int initLength = jsonWriterStringBuilder.length();
+ fieldResolver.resolve(value, jsonWriter, succeedingEntry);
+ final boolean resolved = jsonWriterStringBuilder.length() > initLength;
+ if (resolved) {
+ resolvedFieldCount++;
+ }
+ } else {
+ if (succeedingEntry) {
+ jsonWriter.writeSeparator();
+ }
+ final String fieldPrefix = fieldPrefixes.get(fieldIndex);
+ jsonWriter.writeRawString(fieldPrefix);
+ fieldResolver.resolve(value, jsonWriter, succeedingEntry);
+ resolvedFieldCount++;
+ }
+ }
+ jsonWriter.writeObjectEnd();
+ }
+
+ };
+
+ }
+
+ private static <V, C extends TemplateResolverContext<V, C>> TemplateResolver<V> ofResolver(
+ final C context,
+ final Map<String, Object> map) {
+
+ // Extract the resolver name.
+ final Object resolverNameObject = map.get(RESOLVER_FIELD_NAME);
+ if (!(resolverNameObject instanceof String)) {
+ throw new IllegalArgumentException(
+ "invalid resolver name: " + resolverNameObject);
+ }
+ final String resolverName = (String) resolverNameObject;
+
+ // Retrieve the resolver.
+ final TemplateResolverFactory<V, C, ? extends TemplateResolver<V>> resolverFactory =
+ context.getResolverFactoryByName().get(resolverName);
+ if (resolverFactory == null) {
+ throw new IllegalArgumentException("unknown resolver: " + resolverName);
+ }
+ final TemplateResolverConfig resolverConfig = new TemplateResolverConfig(map);
+ return resolverFactory.create(context, resolverConfig);
+
+ }
+
+ private static <V, C extends TemplateResolverContext<V, C>> TemplateResolver<V> ofString(
+ final C context,
+ final String fieldValue) {
+
+ // Check if substitution needed at all. (Copied logic from
+ // AbstractJacksonLayout.valueNeedsLookup() method.)
+ final boolean substitutionNeeded = fieldValue.contains("${");
+ final JsonWriter contextJsonWriter = context.getJsonWriter();
+ if (substitutionNeeded) {
+
+ // Use Log4j substitutor with LogEvent.
+ if (EventResolverContext.class.isAssignableFrom(context.getContextClass())) {
+ return (final V value, final JsonWriter jsonWriter) -> {
+ final LogEvent logEvent = (LogEvent) value;
+ final String replacedText = context.getSubstitutor().replace(logEvent, fieldValue);
+ jsonWriter.writeString(replacedText);
+ };
+ }
+
+ // Use standalone Log4j substitutor.
+ else {
+ final String replacedText = context.getSubstitutor().replace(null, fieldValue);
+ if (replacedText == null) {
+ // noinspection unchecked
+ return (TemplateResolver<V>) NULL_RESOLVER;
+ } else {
+ // Prepare the escaped replacement first.
+ final String escapedReplacedText =
+ contextJsonWriter.use(() ->
+ contextJsonWriter.writeString(replacedText));
+ // Create a resolver dedicated to the escaped replacement.
+ return (final V value, final JsonWriter jsonWriter) ->
+ jsonWriter.writeRawString(escapedReplacedText);
+ }
+ }
+
+ }
+
+ // Write the field value as is.
+ else {
+ final String escapedFieldValue =
+ contextJsonWriter.use(() ->
+ contextJsonWriter.writeString(fieldValue));
+ return (final V value, final JsonWriter jsonWriter) ->
+ jsonWriter.writeRawString(escapedFieldValue);
+ }
+
+ }
+
+ private static <V> TemplateResolver<V> ofNumber(final Number number) {
+ final String numberString = String.valueOf(number);
+ return (final V ignored, final JsonWriter jsonWriter) ->
+ jsonWriter.writeRawString(numberString);
+ }
+
+ private static <V> TemplateResolver<V> ofBoolean(final boolean value) {
+ return (final V ignored, final JsonWriter jsonWriter) ->
+ jsonWriter.writeBoolean(value);
+ }
+
+}
diff --git a/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/ThreadContextDataResolver.java b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/ThreadContextDataResolver.java
new file mode 100644
index 0000000..66efe17
--- /dev/null
+++ b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/ThreadContextDataResolver.java
@@ -0,0 +1,357 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.logging.log4j.layout.json.template.resolver;
+
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.layout.json.template.util.JsonWriter;
+import org.apache.logging.log4j.layout.json.template.util.Recycler;
+import org.apache.logging.log4j.layout.json.template.util.RecyclerFactory;
+import org.apache.logging.log4j.util.ReadOnlyStringMap;
+import org.apache.logging.log4j.util.TriConsumer;
+
+import java.util.Map;
+import java.util.regex.Pattern;
+
+/**
+ * Mapped Diagnostic Context (MDC), aka. Thread Context Data, resolver.
+ *
+ * <h3>Configuration</h3>
+ *
+ * <pre>
+ * config = singleAccess | multiAccess
+ *
+ * singleAccess = key , [ stringified ]
+ * key = "key" -> string
+ * stringified = "stringified" -> boolean
+ *
+ * multiAccess = [ pattern ] , [ flatten ] , [ stringified ]
+ * pattern = "pattern" -> string
+ * flatten = "flatten" -> ( boolean | flattenConfig )
+ * flattenConfig = [ flattenPrefix ]
+ * flattenPrefix = "prefix" -> string
+ * </pre>
+ *
+ * Note that <tt>singleAccess</tt> resolves the MDC value as is, whilst
+ * <tt>multiAccess</tt> resolves a multitude of MDC values. If <tt>flatten</tt>
+ * is provided, <tt>multiAccess</tt> merges the values with the parent,
+ * otherwise creates a new JSON object containing the values.
+ *
+ * <h3>Examples</h3>
+ *
+ * Resolve the <tt>userRole</tt> MDC value:
+ *
+ * <pre>
+ * {
+ * "$resolver": "mdc",
+ * "key": "userRole"
+ * }
+ * </pre>
+ *
+ * Resolve the string representation of the <tt>userRank</tt> MDC value:
+ *
+ * <pre>
+ * {
+ * "$resolver": "mdc",
+ * "key": "userRank",
+ * "stringified": true
+ * }
+ * </pre>
+ *
+ * Resolve all MDC entries into an object:
+ *
+ * <pre>
+ * {
+ * "$resolver": "mdc"
+ * }
+ * </pre>
+ *
+ * Resolve all MDC entries into an object such that values are converted to
+ * string:
+ *
+ * <pre>
+ * {
+ * "$resolver": "mdc",
+ * "stringified": true
+ * }
+ * </pre>
+ *
+ * Merge all MDC entries whose keys are matching with the
+ * <tt>user(Role|Rank)</tt> regex into the parent:
+ *
+ * <pre>
+ * {
+ * "$resolver": "mdc",
+ * "flatten": true,
+ * "pattern": "user(Role|Rank)"
+ * }
+ * </pre>
+ *
+ * After converting the corresponding entries to string, merge all MDC entries
+ * to parent such that keys are prefixed with <tt>_</tt>:
+ *
+ * <pre>
+ * {
+ * "$resolver": "mdc",
+ * "stringified": true,
+ * "flatten": {
+ * "prefix": "_"
+ * }
+ * }
+ * </pre>
+ */
+final class ThreadContextDataResolver implements EventResolver {
+
+ private final EventResolver internalResolver;
+
+ ThreadContextDataResolver(
+ final EventResolverContext context,
+ final TemplateResolverConfig config) {
+ this.internalResolver = createResolver(context, config);
+ }
+
+ private static EventResolver createResolver(
+ final EventResolverContext context,
+ final TemplateResolverConfig config) {
+ final Object flattenObject = config.getObject("flatten");
+ final boolean flatten;
+ if (flattenObject == null) {
+ flatten = false;
+ } else if (flattenObject instanceof Boolean) {
+ flatten = (boolean) flattenObject;
+ } else if (flattenObject instanceof Map) {
+ flatten = true;
+ } else {
+ throw new IllegalArgumentException("invalid flatten option: " + config);
+ }
+ final String key = config.getString("key");
+ final String prefix = config.getString(new String[] {"flatten", "prefix"});
+ final String pattern = config.getString("pattern");
+ final boolean stringified = config.getBoolean("stringified", false);
+ if (key != null) {
+ if (flatten) {
+ throw new IllegalArgumentException(
+ "both key and flatten options cannot be supplied: " + config);
+ }
+ return createKeyResolver(key, stringified);
+ } else {
+ final RecyclerFactory recyclerFactory = context.getRecyclerFactory();
+ return createResolver(recyclerFactory, flatten, prefix, pattern, stringified);
+ }
+ }
+
+ private static EventResolver createKeyResolver(
+ final String key,
+ final boolean stringified) {
+ return new EventResolver() {
+
+ @Override
+ public boolean isResolvable(final LogEvent logEvent) {
+ final ReadOnlyStringMap contextData = logEvent.getContextData();
+ return contextData != null && contextData.containsKey(key);
+ }
+
+ @Override
+ public void resolve(final LogEvent logEvent, final JsonWriter jsonWriter) {
+ final ReadOnlyStringMap contextData = logEvent.getContextData();
+ final Object value = contextData == null ? null : contextData.getValue(key);
+ if (stringified) {
+ final String valueString = String.valueOf(value);
+ jsonWriter.writeString(valueString);
+ } else {
+ jsonWriter.writeValue(value);
+ }
+ }
+
+ };
+ }
+
+ private static EventResolver createResolver(
+ final RecyclerFactory recyclerFactory,
+ final boolean flatten,
+ final String prefix,
+ final String pattern,
+ final boolean stringified) {
+
+ // Compile the pattern.
+ final Pattern compiledPattern =
+ pattern == null
+ ? null
+ : Pattern.compile(pattern);
+
+ // Create the recycler for the loop context.
+ final Recycler<LoopContext> loopContextRecycler =
+ recyclerFactory.create(() -> {
+ final LoopContext loopContext = new LoopContext();
+ if (prefix != null) {
+ loopContext.prefix = prefix;
+ loopContext.prefixedKey = new StringBuilder(prefix);
+ }
+ loopContext.pattern = compiledPattern;
+ loopContext.stringified = stringified;
+ return loopContext;
+ });
+
+ // Create the resolver.
+ return createResolver(flatten, loopContextRecycler);
+
+ }
+
+ private static EventResolver createResolver(
+ final boolean flatten,
+ final Recycler<LoopContext> loopContextRecycler) {
+ return new EventResolver() {
+
+ @Override
+ public boolean isFlattening() {
+ return flatten;
+ }
+
+ @Override
+ public boolean isResolvable(final LogEvent logEvent) {
+ final ReadOnlyStringMap contextData = logEvent.getContextData();
+ return contextData != null && !contextData.isEmpty();
+ }
+
+ @Override
+ public void resolve(final LogEvent value, final JsonWriter jsonWriter) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void resolve(
+ final LogEvent logEvent,
+ final JsonWriter jsonWriter,
+ final boolean succeedingEntry) {
+
+ // Retrieve the context data.
+ final ReadOnlyStringMap contextData = logEvent.getContextData();
+ if (contextData == null || contextData.isEmpty()) {
+ if (!flatten) {
+ jsonWriter.writeNull();
+ }
+ return;
+ }
+
+ // Resolve the context data.
+ if (!flatten) {
+ jsonWriter.writeObjectStart();
+ }
+ final LoopContext loopContext = loopContextRecycler.acquire();
+ loopContext.jsonWriter = jsonWriter;
+ loopContext.initJsonWriterStringBuilderLength = jsonWriter.getStringBuilder().length();
+ loopContext.succeedingEntry = flatten && succeedingEntry;
+ try {
+ contextData.forEach(LoopMethod.INSTANCE, loopContext);
+ } finally {
+ loopContextRecycler.release(loopContext);
+ }
+ if (!flatten) {
+ jsonWriter.writeObjectEnd();
+ }
+
+ }
+
+ };
+ }
+
+ private static final class LoopContext {
+
+ private String prefix;
+
+ private StringBuilder prefixedKey;
+
+ private Pattern pattern;
+
+ private boolean stringified;
+
+ private JsonWriter jsonWriter;
+
+ private int initJsonWriterStringBuilderLength;
+
+ private boolean succeedingEntry;
+
+ }
+
+ private static final class LoopMethod implements TriConsumer<String, Object, LoopContext> {
+
+ private static final LoopMethod INSTANCE = new LoopMethod();
+
+ @Override
+ public void accept(
+ final String key,
+ final Object value,
+ final LoopContext loopContext) {
+ final boolean keyMatched =
+ loopContext.pattern == null ||
+ loopContext.pattern.matcher(key).matches();
+ if (keyMatched) {
+ final boolean succeedingEntry =
+ loopContext.succeedingEntry ||
+ loopContext.initJsonWriterStringBuilderLength <
+ loopContext.jsonWriter.getStringBuilder().length();
+ if (succeedingEntry) {
+ loopContext.jsonWriter.writeSeparator();
+ }
+ if (loopContext.prefix == null) {
+ loopContext.jsonWriter.writeObjectKey(key);
+ } else {
+ loopContext.prefixedKey.setLength(loopContext.prefix.length());
+ loopContext.prefixedKey.append(key);
+ loopContext.jsonWriter.writeObjectKey(loopContext.prefixedKey);
+ }
+ if (loopContext.stringified && !(value instanceof String)) {
+ final String valueString = String.valueOf(value);
+ loopContext.jsonWriter.writeString(valueString);
+ } else {
+ loopContext.jsonWriter.writeValue(value);
+ }
+ }
+ }
+
+ }
+
+ static String getName() {
+ return "mdc";
+ }
+
+ @Override
+ public boolean isFlattening() {
+ return internalResolver.isFlattening();
+ }
+
+ @Override
+ public boolean isResolvable(final LogEvent logEvent) {
+ final ReadOnlyStringMap contextData = logEvent.getContextData();
+ return contextData != null && !contextData.isEmpty();
+ }
+
+ @Override
+ public void resolve(
+ final LogEvent logEvent,
+ final JsonWriter jsonWriter) {
+ internalResolver.resolve(logEvent, jsonWriter);
+ }
+
+ @Override
+ public void resolve(
+ final LogEvent logEvent,
+ final JsonWriter jsonWriter,
+ final boolean succeedingEntry) {
+ internalResolver.resolve(logEvent, jsonWriter, succeedingEntry);
+ }
+
+}
diff --git a/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/ThreadContextDataResolverFactory.java b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/ThreadContextDataResolverFactory.java
new file mode 100644
index 0000000..3ef164d
--- /dev/null
+++ b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/ThreadContextDataResolverFactory.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.layout.json.template.resolver;
+
+final class ThreadContextDataResolverFactory
+ implements EventResolverFactory<ThreadContextDataResolver> {
+
+ private static final ThreadContextDataResolverFactory INSTANCE =
+ new ThreadContextDataResolverFactory();
+
+ private ThreadContextDataResolverFactory() {}
+
+ static ThreadContextDataResolverFactory getInstance() {
+ return INSTANCE;
+ }
+
+ @Override
+ public String getName() {
+ return ThreadContextDataResolver.getName();
+ }
+
+ @Override
+ public ThreadContextDataResolver create(
+ final EventResolverContext context,
+ final TemplateResolverConfig config) {
+ return new ThreadContextDataResolver(context, config);
+ }
+
+}
diff --git a/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/ThreadContextStackResolver.java b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/ThreadContextStackResolver.java
new file mode 100644
index 0000000..6a9af12
--- /dev/null
+++ b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/ThreadContextStackResolver.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.layout.json.template.resolver;
+
+import org.apache.logging.log4j.ThreadContext;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.layout.json.template.util.JsonWriter;
+
+import java.util.Optional;
+import java.util.regex.Pattern;
+
+/**
+ * Nested Diagnostic Context (NDC), aka. Thread Context Stack, resolver.
+ *
+ * <h3>Configuration</h3>
+ *
+ * <pre>
+ * config = [ pattern ]
+ * pattern = "pattern" -> string
+ * </pre>
+ *
+ * <h3>Examples</h3>
+ *
+ * Resolve all NDC values into a list:
+ *
+ * <pre>
+ * {
+ * "$resolver": "ndc"
+ * }
+ * </pre>
+ *
+ * Resolve all NDC values matching with the <tt>pattern</tt> regex:
+ *
+ * <pre>
+ * {
+ * "$resolver": "ndc",
+ * "pattern": "user(Role|Rank):\\w+"
+ * }
+ * </pre>
+ */
+final class ThreadContextStackResolver implements EventResolver {
+
+ private final Pattern itemPattern;
+
+ ThreadContextStackResolver(final TemplateResolverConfig config) {
+ this.itemPattern = Optional
+ .ofNullable(config.getString("pattern"))
+ .map(Pattern::compile)
+ .orElse(null);
+ }
+
+ static String getName() {
+ return "ndc";
+ }
+
+ @Override
+ public boolean isResolvable(final LogEvent logEvent) {
+ final ThreadContext.ContextStack contextStack = logEvent.getContextStack();
+ return contextStack.getDepth() > 0;
+ }
+
+ @Override
+ public void resolve(
+ final LogEvent logEvent,
+ final JsonWriter jsonWriter) {
+ final ThreadContext.ContextStack contextStack = logEvent.getContextStack();
+ if (contextStack.getDepth() == 0) {
+ jsonWriter.writeNull();
+ return;
+ }
+ boolean arrayStarted = false;
+ for (final String contextStackItem : contextStack.asList()) {
+ final boolean matched =
+ itemPattern == null ||
+ itemPattern.matcher(contextStackItem).matches();
+ if (matched) {
+ if (arrayStarted) {
+ jsonWriter.writeSeparator();
+ } else {
+ jsonWriter.writeArrayStart();
+ arrayStarted = true;
+ }
+ jsonWriter.writeString(contextStackItem);
+ }
+ }
+ if (arrayStarted) {
+ jsonWriter.writeArrayEnd();
+ } else {
+ jsonWriter.writeNull();
+ }
+ }
+
+}
diff --git a/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/ThreadContextStackResolverFactory.java b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/ThreadContextStackResolverFactory.java
new file mode 100644
index 0000000..82a5c23
--- /dev/null
+++ b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/ThreadContextStackResolverFactory.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.layout.json.template.resolver;
+
+final class ThreadContextStackResolverFactory
+ implements EventResolverFactory<ThreadContextStackResolver> {
+
+ private static final ThreadContextStackResolverFactory INSTANCE
+ = new ThreadContextStackResolverFactory();
+
+ private ThreadContextStackResolverFactory() {}
+
+ static ThreadContextStackResolverFactory getInstance() {
+ return INSTANCE;
+ }
+
+ @Override
+ public String getName() {
+ return ThreadContextStackResolver.getName();
+ }
+
+ @Override
+ public ThreadContextStackResolver create(
+ final EventResolverContext context,
+ final TemplateResolverConfig config) {
+ return new ThreadContextStackResolver(config);
+ }
+
+}
diff --git a/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/ThreadResolver.java b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/ThreadResolver.java
new file mode 100644
index 0000000..a316afe
--- /dev/null
+++ b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/ThreadResolver.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.layout.json.template.resolver;
+
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.layout.json.template.util.JsonWriter;
+
+/**
+ * Thread resolver.
+ *
+ * <h3>Configuration</h3>
+ *
+ * <pre>
+ * config = "field" -> ( "name" | "id" | "priority" )
+ * </pre>
+ *
+ * <h3>Examples</h3>
+ *
+ * Resolve the thread name:
+ *
+ * <pre>
+ * {
+ * "$resolver": "thread",
+ * "field": "name"
+ * }
+ * </pre>
+ */
+final class ThreadResolver implements EventResolver {
+
+ private static final EventResolver NAME_RESOLVER =
+ (final LogEvent logEvent, final JsonWriter jsonWriter) -> {
+ final String threadName = logEvent.getThreadName();
+ jsonWriter.writeString(threadName);
+ };
+
+ private static final EventResolver ID_RESOLVER =
+ (final LogEvent logEvent, final JsonWriter jsonWriter) -> {
+ final long threadId = logEvent.getThreadId();
+ jsonWriter.writeNumber(threadId);
+ };
+
+ private static final EventResolver PRIORITY_RESOLVER =
+ (final LogEvent logEvent, final JsonWriter jsonWriter) -> {
+ final int threadPriority = logEvent.getThreadPriority();
+ jsonWriter.writeNumber(threadPriority);
+ };
+
+ private final EventResolver internalResolver;
+
+ ThreadResolver(final TemplateResolverConfig config) {
+ this.internalResolver = createInternalResolver(config);
+ }
+
+ private static EventResolver createInternalResolver(
+ final TemplateResolverConfig config) {
+ final String fieldName = config.getString("field");
+ switch (fieldName) {
+ case "name": return NAME_RESOLVER;
+ case "id": return ID_RESOLVER;
+ case "priority": return PRIORITY_RESOLVER;
+ }
+ throw new IllegalArgumentException("unknown field: " + config);
+ }
+
+ static String getName() {
+ return "thread";
+ }
+
+ @Override
+ public void resolve(
+ final LogEvent logEvent,
+ final JsonWriter jsonWriter) {
+ internalResolver.resolve(logEvent, jsonWriter);
+ }
+
+}
diff --git a/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/ThreadResolverFactory.java b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/ThreadResolverFactory.java
new file mode 100644
index 0000000..75df1e3
--- /dev/null
+++ b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/ThreadResolverFactory.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.layout.json.template.resolver;
+
+final class ThreadResolverFactory implements EventResolverFactory<ThreadResolver> {
+
+ private static final ThreadResolverFactory INSTANCE = new ThreadResolverFactory();
+
+ private ThreadResolverFactory() {}
+
+ static ThreadResolverFactory getInstance() {
+ return INSTANCE;
+ }
+
+ @Override
+ public String getName() {
+ return ThreadResolver.getName();
+ }
+
+ @Override
+ public ThreadResolver create(
+ final EventResolverContext context,
+ final TemplateResolverConfig config) {
+ return new ThreadResolver(config);
+ }
+
+}
diff --git a/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/TimestampResolver.java b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/TimestampResolver.java
new file mode 100644
index 0000000..3d023b3
--- /dev/null
+++ b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/TimestampResolver.java
@@ -0,0 +1,505 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.logging.log4j.layout.json.template.resolver;
+
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.time.Instant;
+import org.apache.logging.log4j.core.time.internal.format.FastDateFormat;
+import org.apache.logging.log4j.layout.json.template.JsonTemplateLayoutDefaults;
+import org.apache.logging.log4j.layout.json.template.util.JsonWriter;
+
+import java.util.Calendar;
+import java.util.Locale;
+import java.util.TimeZone;
+
+/**
+ * Timestamp resolver.
+ *
+ * <h3>Configuration</h3>
+ *
+ * <pre>
+ * config = [ patternConfig | epochConfig ]
+ *
+ * patternConfig = "pattern" -> ( [ format ] , [ timeZone ] , [ locale ] )
+ * format = "format" -> string
+ * timeZone = "timeZone" -> string
+ * locale = "locale" -> (
+ * language |
+ * ( language , "_" , country ) |
+ * ( language , "_" , country , "_" , variant )
+ * )
+ *
+ * epochConfig = "epoch" -> ( unit , [ rounded ] )
+ * unit = "unit" -> (
+ * "nanos" |
+ * "millis" |
+ * "secs" |
+ * "millis.nanos" |
+ * "secs.nanos" |
+ * )
+ * rounded = "rounded" -> boolean
+ * </pre>
+ *
+ * If no configuration options are provided, <tt>pattern-config</tt> is
+ * employed. There {@link
+ * JsonTemplateLayoutDefaults#getTimestampFormatPattern()}, {@link
+ * JsonTemplateLayoutDefaults#getTimeZone()}, {@link
+ * JsonTemplateLayoutDefaults#getLocale()} are used as defaults for
+ * <tt>pattern</tt>, <tt>timeZone</tt>, and <tt>locale</tt>, respectively.
+ *
+ * In <tt>epoch-config</tt>, <tt>millis.nanos</tt>, <tt>secs.nanos</tt> stand
+ * for the fractional component in nanoseconds.
+ *
+ * <h3>Examples</h3>
+ *
+ * <table>
+ * <tr>
+ * <td>Configuration</td>
+ * <td>Output</td>
+ * </tr>
+ * <tr>
+ * <td><pre>
+ * {
+ * "$resolver": "timestamp"
+ * }
+ * </pre></td>
+ * <td><pre>
+ * 2020-02-07T13:38:47.098+02:00
+ * </pre></td>
+ * </tr>
+ * <tr>
+ * <td><pre>
+ * {
+ * "$resolver": "timestamp",
+ * "pattern": {
+ * "format": "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'",
+ * "timeZone": "UTC",
+ * "locale": "en_US"
+ * }
+ * }
+ * </pre></td>
+ * <td><pre>
+ * 2020-02-07T13:38:47.098Z
+ * </pre></td>
+ * </tr>
+ * <tr>
+ * <td><pre>
+ * {
+ * "$resolver": "timestamp",
+ * "epoch": {
+ * "unit": "secs"
+ * }
+ * }
+ * </pre></td>
+ * <td><pre>
+ * 1581082727.982123456
+ * </pre></td>
+ * </tr>
+ * <tr>
+ * <td><pre>
+ * {
+ * "$resolver": "timestamp",
+ * "epoch": {
+ * "unit": "secs",
+ * "rounded": true
+ * }
+ * }
+ * </pre></td>
+ * <td><pre>
+ * 1581082727
+ * </pre></td>
+ * </tr>
+ * <tr>
+ * <td><pre>
+ * {
+ * "$resolver": "timestamp",
+ * "epoch": {
+ * "unit": "secs.nanos"
+ * }
+ * }
+ * </pre></td>
+ * <td><pre>
+ * 982123456
+ * </pre></td>
+ * </tr>
+ * <tr>
+ * <td><pre>
+ * {
+ * "$resolver": "timestamp",
+ * "epoch": {
+ * "unit": "millis"
+ * }
+ * }
+ * </pre></td>
+ * <td><pre>
+ * 1581082727982.123456
+ * </pre></td>
+ * </tr>
+ * <tr>
+ * <td><pre>
+ * {
+ * "$resolver": "timestamp",
+ * "epoch": {
+ * "unit": "millis",
+ * "rounded": true
+ * }
+ * }
+ * </pre></td>
+ * <td><pre>
+ * 1581082727982
+ * </pre></td>
+ * </tr>
+ * <tr>
+ * <td><pre>
+ * {
+ * "$resolver": "timestamp",
+ * "epoch": {
+ * "unit": "millis.nanos"
+ * }
+ * }
+ * </pre></td>
+ * <td><pre>
+ * 123456
+ * </pre></td>
+ * </tr>
+ * <tr>
+ * <td><pre>
+ * {
+ * "$resolver": "timestamp",
+ * "epoch": {
+ * "unit": "nanos"
+ * }
+ * }
+ * </pre></td>
+ * <td><pre>
+ * 1581082727982123456
+ * </pre></td>
+ * </tr>
+ * </table>
+ */
+final class TimestampResolver implements EventResolver {
+
+ private final EventResolver internalResolver;
+
+ TimestampResolver(final TemplateResolverConfig config) {
+ this.internalResolver = createResolver(config);
+ }
+
+ private static EventResolver createResolver(
+ final TemplateResolverConfig config) {
+ final boolean patternProvided = config.exists("pattern");
+ final boolean epochProvided = config.exists("epoch");
+ if (patternProvided && epochProvided) {
+ throw new IllegalArgumentException(
+ "conflicting configuration options are provided: " + config);
+ }
+ return epochProvided
+ ? createEpochResolver(config)
+ : createFormatResolver(config);
+ }
+
+ /**
+ * Context for GC-free formatted timestamp resolver.
+ */
+ private static final class FormatResolverContext {
+
+ private final FastDateFormat timestampFormat;
+
+ private final Calendar calendar;
+
+ private final StringBuilder formattedTimestampBuilder;
+
+ private FormatResolverContext(
+ final TimeZone timeZone,
+ final Locale locale,
+ final FastDateFormat timestampFormat) {
+ this.timestampFormat = timestampFormat;
+ this.formattedTimestampBuilder = new StringBuilder();
+ this.calendar = Calendar.getInstance(timeZone, locale);
+ timestampFormat.format(calendar, formattedTimestampBuilder);
+ }
+
+ private static FormatResolverContext fromConfig(
+ final TemplateResolverConfig config) {
+ final String format = readFormat(config);
+ final TimeZone timeZone = readTimeZone(config);
+ final Locale locale = readLocale(config);
+ final FastDateFormat fastDateFormat =
+ FastDateFormat.getInstance(format, timeZone, locale);
+ return new FormatResolverContext(timeZone, locale, fastDateFormat);
+ }
+
+ private static String readFormat(final TemplateResolverConfig config) {
+ final String format = config.getString(new String[]{"pattern", "format"});
+ if (format == null) {
+ return JsonTemplateLayoutDefaults.getTimestampFormatPattern();
+ }
+ try {
+ FastDateFormat.getInstance(format);
+ } catch (final IllegalArgumentException error) {
+ throw new IllegalArgumentException(
+ "invalid timestamp format: " + config,
+ error);
+ }
+ return format;
+ }
+
+ private static TimeZone readTimeZone(final TemplateResolverConfig config) {
+ final String timeZoneId = config.getString(new String[]{"pattern", "timeZone"});
+ if (timeZoneId == null) {
+ return JsonTemplateLayoutDefaults.getTimeZone();
+ }
+ boolean found = false;
+ for (final String availableTimeZone : TimeZone.getAvailableIDs()) {
+ if (availableTimeZone.equalsIgnoreCase(timeZoneId)) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ throw new IllegalArgumentException(
+ "invalid timestamp time zone: " + config);
+ }
+ return TimeZone.getTimeZone(timeZoneId);
+ }
+
+ private static Locale readLocale(final TemplateResolverConfig config) {
+ final String locale = config.getString(new String[]{"pattern", "locale"});
+ if (locale == null) {
+ return JsonTemplateLayoutDefaults.getLocale();
+ }
+ final String[] localeFields = locale.split("_", 3);
+ switch (localeFields.length) {
+ case 1: return new Locale(localeFields[0]);
+ case 2: return new Locale(localeFields[0], localeFields[1]);
+ case 3: return new Locale(localeFields[0], localeFields[1], localeFields[2]);
+ }
+ throw new IllegalArgumentException("invalid timestamp locale: " + config);
+ }
+
+ }
+
+ /**
+ * GC-free formatted timestamp resolver.
+ */
+ private static final class FormatResolver implements EventResolver {
+
+ private final FormatResolverContext formatResolverContext;
+
+ private FormatResolver(final FormatResolverContext formatResolverContext) {
+ this.formatResolverContext = formatResolverContext;
+ }
+
+ @Override
+ public synchronized void resolve(
+ final LogEvent logEvent,
+ final JsonWriter jsonWriter) {
+
+ // Format timestamp if it doesn't match the last cached one.
+ final long timestampMillis = logEvent.getTimeMillis();
+ if (formatResolverContext.calendar.getTimeInMillis() != timestampMillis) {
+
+ // Format the timestamp.
+ formatResolverContext.formattedTimestampBuilder.setLength(0);
+ formatResolverContext.calendar.setTimeInMillis(timestampMillis);
+ formatResolverContext.timestampFormat.format(
+ formatResolverContext.calendar,
+ formatResolverContext.formattedTimestampBuilder);
+
+ // Write the formatted timestamp.
+ final StringBuilder jsonWriterStringBuilder = jsonWriter.getStringBuilder();
+ final int startIndex = jsonWriterStringBuilder.length();
+ jsonWriter.writeString(formatResolverContext.formattedTimestampBuilder);
+
+ // Cache the written value.
+ formatResolverContext.formattedTimestampBuilder.setLength(0);
+ formatResolverContext.formattedTimestampBuilder.append(
+ jsonWriterStringBuilder,
+ startIndex,
+ jsonWriterStringBuilder.length());
+
+ }
+
+ // Write the cached formatted timestamp.
+ else {
+ jsonWriter.writeRawString(
+ formatResolverContext.formattedTimestampBuilder);
+ }
+
+ }
+
+ }
+
+ private static EventResolver createFormatResolver(
+ final TemplateResolverConfig config) {
+ final FormatResolverContext formatResolverContext =
+ FormatResolverContext.fromConfig(config);
+ return new FormatResolver(formatResolverContext);
+ }
+
+ private static EventResolver createEpochResolver(
+ final TemplateResolverConfig config) {
+ final String unit = config.getString(new String[]{"epoch", "unit"});
+ final Boolean rounded = config.getBoolean(new String[]{"epoch", "rounded"});
+ if ("nanos".equals(unit) && !Boolean.FALSE.equals(rounded)) {
+ return EPOCH_NANOS_RESOLVER;
+ } else if ("millis".equals(unit)) {
+ return !Boolean.TRUE.equals(rounded)
+ ? EPOCH_MILLIS_RESOLVER
+ : EPOCH_MILLIS_ROUNDED_RESOLVER;
+ } else if ("millis.nanos".equals(unit) && rounded == null) {
+ return EPOCH_MILLIS_NANOS_RESOLVER;
+ } else if ("secs".equals(unit)) {
+ return !Boolean.TRUE.equals(rounded)
+ ? EPOCH_SECS_RESOLVER
+ : EPOCH_SECS_ROUNDED_RESOLVER;
+ } else if ("secs.nanos".equals(unit) && rounded == null) {
+ return EPOCH_SECS_NANOS_RESOLVER;
+ }
+ throw new IllegalArgumentException(
+ "invalid epoch configuration: " + config);
+ }
+
+ private static final class EpochResolutionRecord {
+
+ private static final int MAX_LONG_LENGTH =
+ String.valueOf(Long.MAX_VALUE).length();
+
+ private Instant instant;
+
+ private char[] resolution = new char[/* integral: */MAX_LONG_LENGTH + /* dot: */1 + /* fractional: */MAX_LONG_LENGTH ];
+
+ private int resolutionLength;
+
+ private EpochResolutionRecord() {}
+
+ }
+
+ private static abstract class EpochResolver implements EventResolver {
+
+ private final EpochResolutionRecord resolutionRecord =
+ new EpochResolutionRecord();
+
+ @Override
+ public synchronized void resolve(
+ final LogEvent logEvent,
+ final JsonWriter jsonWriter) {
+ final Instant logEventInstant = logEvent.getInstant();
+ if (logEventInstant.equals(resolutionRecord.instant)) {
+ jsonWriter.writeRawString(
+ resolutionRecord.resolution,
+ 0,
+ resolutionRecord.resolutionLength);
+ } else {
+ resolutionRecord.instant = logEventInstant;
+ final StringBuilder stringBuilder = jsonWriter.getStringBuilder();
+ final int startIndex = stringBuilder.length();
+ resolve(logEventInstant, jsonWriter);
+ resolutionRecord.resolutionLength = stringBuilder.length() - startIndex;
+ stringBuilder.getChars(
+ startIndex,
+ stringBuilder.length(),
+ resolutionRecord.resolution,
+ 0);
+ }
+ }
+
+ abstract void resolve(Instant logEventInstant, JsonWriter jsonWriter);
+
+ }
+
+ private static final EventResolver EPOCH_NANOS_RESOLVER =
+ new EpochResolver() {
+ @Override
+ void resolve(final Instant logEventInstant, final JsonWriter jsonWriter) {
+ final long nanos = epochNanos(logEventInstant);
+ jsonWriter.writeNumber(nanos);
+ }
+ };
+
+ private static final EventResolver EPOCH_MILLIS_RESOLVER =
+ new EpochResolver() {
+ @Override
+ void resolve(final Instant logEventInstant, final JsonWriter jsonWriter) {
+ final StringBuilder jsonWriterStringBuilder = jsonWriter.getStringBuilder();
+ final long nanos = epochNanos(logEventInstant);
+ jsonWriterStringBuilder.append(nanos);
+ jsonWriterStringBuilder.insert(jsonWriterStringBuilder.length() - 6, '.');
+ }
+ };
+
+ private static final EventResolver EPOCH_MILLIS_ROUNDED_RESOLVER =
+ new EpochResolver() {
+ @Override
+ void resolve(final Instant logEventInstant, final JsonWriter jsonWriter) {
+ jsonWriter.writeNumber(logEventInstant.getEpochMillisecond());
+ }
+ };
+
+ private static final EventResolver EPOCH_MILLIS_NANOS_RESOLVER =
+ new EpochResolver() {
+ @Override
+ void resolve(final Instant logEventInstant, final JsonWriter jsonWriter) {
+ final long nanos = epochNanos(logEventInstant);
+ final long fraction = nanos % 1_000_000L;
+ jsonWriter.writeNumber(fraction);
+ }
+ };
+
+ private static final EventResolver EPOCH_SECS_RESOLVER =
+ new EpochResolver() {
+ @Override
+ void resolve(final Instant logEventInstant, final JsonWriter jsonWriter) {
+ final StringBuilder jsonWriterStringBuilder = jsonWriter.getStringBuilder();
+ final long nanos = epochNanos(logEventInstant);
+ jsonWriterStringBuilder.append(nanos);
+ jsonWriterStringBuilder.insert(jsonWriterStringBuilder.length() - 9, '.');
+ }
+ };
+
+ private static final EventResolver EPOCH_SECS_ROUNDED_RESOLVER =
+ new EpochResolver() {
+ @Override
+ void resolve(final Instant logEventInstant, final JsonWriter jsonWriter) {
+ jsonWriter.writeNumber(logEventInstant.getEpochSecond());
+ }
+ };
+
+ private static final EventResolver EPOCH_SECS_NANOS_RESOLVER =
+ new EpochResolver() {
+ @Override
+ void resolve(final Instant logEventInstant, final JsonWriter jsonWriter) {
+ jsonWriter.writeNumber(logEventInstant.getNanoOfSecond());
+ }
+ };
+
+ private static long epochNanos(Instant instant) {
+ return 1_000_000_000L * instant.getEpochSecond() + instant.getNanoOfSecond();
+ }
+
+ static String getName() {
+ return "timestamp";
+ }
+
+ @Override
+ public void resolve(
+ final LogEvent logEvent,
+ final JsonWriter jsonWriter) {
+ internalResolver.resolve(logEvent, jsonWriter);
+ }
+
+}
diff --git a/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/TimestampResolverFactory.java b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/TimestampResolverFactory.java
new file mode 100644
index 0000000..f1547f2
--- /dev/null
+++ b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/resolver/TimestampResolverFactory.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.layout.json.template.resolver;
+
+final class TimestampResolverFactory implements EventResolverFactory<TimestampResolver> {
+
+ private static final TimestampResolverFactory INSTANCE = new TimestampResolverFactory();
+
+ private TimestampResolverFactory() {}
+
+ static TimestampResolverFactory getInstance() {
+ return INSTANCE;
+ }
+
+ @Override
+ public String getName() {
+ return TimestampResolver.getName();
+ }
+
+ @Override
+ public TimestampResolver create(
+ final EventResolverContext context,
+ final TemplateResolverConfig config) {
+ return new TimestampResolver(config);
+ }
+
+}
diff --git a/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/util/DummyRecycler.java b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/util/DummyRecycler.java
new file mode 100644
index 0000000..2aae11f
--- /dev/null
+++ b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/util/DummyRecycler.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.layout.json.template.util;
+
+import java.util.function.Supplier;
+
+public class DummyRecycler<V> implements Recycler<V> {
+
+ private final Supplier<V> supplier;
+
+ public DummyRecycler(final Supplier<V> supplier) {
+ this.supplier = supplier;
+ }
+
+ @Override
+ public V acquire() {
+ return supplier.get();
+ }
+
+ @Override
+ public void release(final V value) {}
+
+}
diff --git a/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/util/DummyRecyclerFactory.java b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/util/DummyRecyclerFactory.java
new file mode 100644
index 0000000..dc3a8a1
--- /dev/null
+++ b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/util/DummyRecyclerFactory.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.layout.json.template.util;
+
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+public class DummyRecyclerFactory implements RecyclerFactory {
+
+ private static final DummyRecyclerFactory INSTANCE = new DummyRecyclerFactory();
+
+ private DummyRecyclerFactory() {}
+
+ public static DummyRecyclerFactory getInstance() {
+ return INSTANCE;
+ }
+
+ @Override
+ public <V> Recycler<V> create(
+ final Supplier<V> supplier,
+ final Consumer<V> cleaner) {
+ return new DummyRecycler<V>(supplier);
+ }
+
+}
diff --git a/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/util/JsonReader.java b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/util/JsonReader.java
new file mode 100644
index 0000000..1a9f43e
--- /dev/null
+++ b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/util/JsonReader.java
@@ -0,0 +1,447 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.logging.log4j.layout.json.template.util;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.text.CharacterIterator;
+import java.text.StringCharacterIterator;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A simple JSON parser mapping tokens to basic Java types.
+ * <p>
+ * The type mapping is as follows:
+ * <p>
+ * <ul>
+ * <li><tt>object</tt>s are mapped to {@link LinkedHashMap LinkedHashMap<String,Object>}
+ * <li><tt>array</tt>s are mapped to {@link LinkedList}
+ * <li><tt>string</tt>s are mapped to {@link String} with proper Unicode and
+ * escape character conversion
+ * <li><tt>true</tt>, <tt>false</tt>, and <tt>null</tt> are mapped to their Java
+ * counterparts
+ * <li>floating point <tt>number</tt>s are mapped to {@link BigDecimal}
+ * <li>integral <tt>number</tt>s are mapped to either primitive types
+ * (<tt>int</tt>, <tt>long</tt>) or {@link BigInteger}
+ * </ul>
+ * <p>
+ * This code is heavily influenced by the reader of
+ * <a href="https://github.com/bolerio/mjson/blob/e7a4da2daa6e17a63ec057948bc30818e8f44686/src/java/mjson/Json.java#L2684">mjson</a>.
+ */
+public final class JsonReader {
+
+ private enum Delimiter {
+
+ OBJECT_START("{"),
+
+ OBJECT_END("}"),
+
+ ARRAY_START("["),
+
+ ARRAY_END("]"),
+
+ COLON(":"),
+
+ COMMA(",");
+
+ private final String string;
+
+ Delimiter(final String string) {
+ this.string = string;
+ }
+
+ private static boolean exists(final Object token) {
+ for (Delimiter delimiter : values()) {
+ if (delimiter.string.equals(token)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ }
+
+ private CharacterIterator it;
+
+ private int readCharIndex = -1;
+
+ private char readChar;
+
+ private int readTokenStartIndex = -1;
+
+ private Object readToken;
+
+ private StringBuilder buffer = new StringBuilder();
+
+ private JsonReader() {}
+
+ public static Object read(final String string) {
+ final JsonReader reader = new JsonReader();
+ return reader.read(new StringCharacterIterator(string));
+ }
+
+ private Object read(final CharacterIterator ci) {
+ it = ci;
+ readCharIndex = 0;
+ readChar = it.first();
+ final Object token = readToken();
+ if (token instanceof Delimiter) {
+ final String message = String.format(
+ "was not expecting %s at index %d",
+ readToken, readTokenStartIndex);
+ throw new IllegalArgumentException(message);
+ }
+ skipWhiteSpace();
+ if (it.getIndex() != it.getEndIndex()) {
+ final String message = String.format(
+ "was not expecting input at index %d: %c",
+ readCharIndex, readChar);
+ throw new IllegalArgumentException(message);
+ }
+ return token;
+ }
+
+ private Object readToken() {
+ skipWhiteSpace();
+ readTokenStartIndex = readCharIndex;
+ final char prevChar = readChar;
+ readChar();
+ switch (prevChar) {
+
+ case '"':
+ readToken = readString();
+ break;
+
+ case '[':
+ readToken = readArray();
+ break;
+
+ case ']':
+ readToken = Delimiter.ARRAY_END;
+ break;
+
+ case ',':
+ readToken = Delimiter.COMMA;
+ break;
+
+ case '{':
+ readToken = readObject();
+ break;
+
+ case '}':
+ readToken = Delimiter.OBJECT_END;
+ break;
+
+ case ':':
+ readToken = Delimiter.COLON;
+ break;
+
+ case 't':
+ readToken = readTrue();
+ break;
+
+ case 'f':
+ readToken = readFalse();
+ break;
+
+ case 'n':
+ readToken = readNull();
+ break;
+
+ default:
+ unreadChar();
+ if (Character.isDigit(readChar) || readChar == '-') {
+ readToken = readNumber();
+ } else {
+ String message = String.format(
+ "invalid character at index %d: %c",
+ readCharIndex, readChar);
+ throw new IllegalArgumentException(message);
+ }
+
+ }
+ return readToken;
+ }
+
+ private void skipWhiteSpace() {
+ do {
+ if (!Character.isWhitespace(readChar)) {
+ break;
+ }
+ } while (readChar() != CharacterIterator.DONE);
+ }
+
+ private char readChar() {
+ if (it.getIndex() == it.getEndIndex()) {
+ throw new IllegalArgumentException("premature end of input");
+ }
+ readChar = it.next();
+ readCharIndex = it.getIndex();
+ return readChar;
+ }
+
+ private void unreadChar() {
+ readChar = it.previous();
+ readCharIndex = it.getIndex();
+ }
+
+ private String readString() {
+ buffer.setLength(0);
+ while (readChar != '"') {
+ if (readChar == '\\') {
+ readChar();
+ if (readChar == 'u') {
+ final char unicodeChar = readUnicodeChar();
+ bufferChar(unicodeChar);
+ } else {
+ switch (readChar) {
+ case '"':
+ case '\\':
+ bufferReadChar();
+ break;
+ case 'b':
+ bufferChar('\b');
+ break;
+ case 'f':
+ bufferChar('\f');
+ break;
+ case 'n':
+ bufferChar('\n');
+ break;
+ case 'r':
+ bufferChar('\r');
+ break;
+ case 't':
+ bufferChar('\t');
+ break;
+ default: {
+ final String message = String.format(
+ "was expecting an escape character at index %d: %c",
+ readCharIndex, readChar);
+ throw new IllegalArgumentException(message);
+ }
+ }
+ }
+ } else {
+ bufferReadChar();
+ }
+ }
+ readChar();
+ return buffer.toString();
+ }
+
+ private void bufferReadChar() {
+ bufferChar(readChar);
+ }
+
+ private void bufferChar(final char c) {
+ buffer.append(c);
+ readChar();
+ }
+
+ private char readUnicodeChar() {
+ int value = 0;
+ for (int i = 0; i < 4; i++) {
+ readChar();
+ if (readChar >= '0' && readChar <= '9') {
+ value = (value << 4) + readChar - '0';
+ } else if (readChar >= 'a' && readChar <= 'f') {
+ value = (value << 4) + (readChar - 'a') + 10;
+ } else if (readChar >= 'A' && readChar <= 'F') {
+ value = (value << 4) + (readChar - 'A') + 10;
+ } else {
+ final String message = String.format(
+ "was expecting a unicode character at index %d: %c",
+ readCharIndex, readChar);
+ throw new IllegalArgumentException(message);
+ }
+ }
+ return (char) value;
+ }
+
+ private Map<String, Object> readObject() {
+ final Map<String, Object> object = new LinkedHashMap<>();
+ String key = readObjectKey();
+ while (readToken != Delimiter.OBJECT_END) {
+ expectDelimiter(Delimiter.COLON, readToken());
+ if (readToken != Delimiter.OBJECT_END) {
+ Object value = readToken();
+ object.put(key, value);
+ if (readToken() == Delimiter.COMMA) {
+ key = readObjectKey();
+ if (key == null || Delimiter.exists(key)) {
+ String message = String.format(
+ "was expecting an object key at index %d: %s",
+ readTokenStartIndex, readToken);
+ throw new IllegalArgumentException(message);
+ }
+ } else {
+ expectDelimiter(Delimiter.OBJECT_END, readToken);
+ }
+ }
+ }
+ return object;
+ }
+
+ private List<Object> readArray() {
+ @SuppressWarnings("JdkObsolete")
+ final List<Object> array = new LinkedList<>();
+ readToken();
+ while (readToken != Delimiter.ARRAY_END) {
+ if (readToken instanceof Delimiter) {
+ final String message = String.format(
+ "was expecting an array element at index %d: %s",
+ readTokenStartIndex, readToken);
+ throw new IllegalArgumentException(message);
+ }
+ array.add(readToken);
+ if (readToken() == Delimiter.COMMA) {
+ if (readToken() == Delimiter.ARRAY_END) {
+ final String message = String.format(
+ "was expecting an array element at index %d: %s",
+ readTokenStartIndex, readToken);
+ throw new IllegalArgumentException(message);
+ }
+ } else {
+ expectDelimiter(Delimiter.ARRAY_END, readToken);
+ }
+ }
+ return array;
+ }
+
+ private String readObjectKey() {
+ readToken();
+ if (readToken == Delimiter.OBJECT_END) {
+ return null;
+ } else if (readToken instanceof String) {
+ return (String) readToken;
+ } else {
+ final String message = String.format(
+ "was expecting an object key at index %d: %s",
+ readTokenStartIndex, readToken);
+ throw new IllegalArgumentException(message);
+ }
+ }
+
+ private void expectDelimiter(
+ final Delimiter expectedDelimiter,
+ final Object actualToken) {
+ if (!expectedDelimiter.equals(actualToken)) {
+ String message = String.format(
+ "was expecting %s at index %d: %s",
+ expectedDelimiter, readTokenStartIndex, actualToken);
+ throw new IllegalArgumentException(message);
+ }
+ }
+
+ private boolean readTrue() {
+ if (readChar != 'r' || readChar() != 'u' || readChar() != 'e') {
+ String message = String.format(
+ "was expecting keyword 'true' at index %d: %s",
+ readCharIndex, readChar);
+ throw new IllegalArgumentException(message);
+ }
+ readChar();
+ return true;
+ }
+
+ private boolean readFalse() {
+ if (readChar != 'a' || readChar() != 'l' || readChar() != 's' || readChar() != 'e') {
+ String message = String.format(
+ "was expecting keyword 'false' at index %d: %s",
+ readCharIndex, readChar);
+ throw new IllegalArgumentException(message);
+ }
+ readChar();
+ return false;
+ }
+
+ private Object readNull() {
+ if (readChar != 'u' || readChar() != 'l' || readChar() != 'l') {
+ String message = String.format(
+ "was expecting keyword 'null' at index %d: %s",
+ readCharIndex, readChar);
+ throw new IllegalArgumentException(message);
+ }
+ readChar();
+ return null;
+ }
+
+ private Number readNumber() {
+
+ // Read sign.
+ buffer.setLength(0);
+ if (readChar == '-') {
+ bufferReadChar();
+ }
+
+ // Read fraction.
+ boolean floatingPoint = false;
+ bufferDigits();
+ if (readChar == '.') {
+ bufferReadChar();
+ bufferDigits();
+ floatingPoint = true;
+ }
+
+ // Read exponent.
+ if (readChar == 'e' || readChar == 'E') {
+ floatingPoint = true;
+ bufferReadChar();
+ if (readChar == '+' || readChar == '-') {
+ bufferReadChar();
+ }
+ bufferDigits();
+ }
+
+ // Convert the read number.
+ final String string = buffer.toString();
+ if (floatingPoint) {
+ return new BigDecimal(string);
+ } else {
+ final BigInteger bigInteger = new BigInteger(string);
+ try {
+ return bigInteger.intValueExact();
+ } catch (ArithmeticException ignoredIntOverflow) {
+ try {
+ return bigInteger.longValueExact();
+ } catch (ArithmeticException ignoredLongOverflow) {
+ return bigInteger;
+ }
+ }
+ }
+
+ }
+
+ private void bufferDigits() {
+ boolean found = false;
+ while (Character.isDigit(readChar)) {
+ found = true;
+ bufferReadChar();
+ }
+ if (!found) {
+ final String message = String.format(
+ "was expecting a digit at index %d: %c",
+ readCharIndex, readChar);
+ throw new IllegalArgumentException(message);
+ }
+ }
+
+}
diff --git a/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/util/JsonWriter.java b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/util/JsonWriter.java
new file mode 100644
index 0000000..a0dad93
--- /dev/null
+++ b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/util/JsonWriter.java
@@ -0,0 +1,889 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.logging.log4j.layout.json.template.util;
+
+import org.apache.logging.log4j.util.BiConsumer;
+import org.apache.logging.log4j.util.IndexedReadOnlyStringMap;
+import org.apache.logging.log4j.util.StringBuilderFormattable;
+import org.apache.logging.log4j.util.StringMap;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * A simple JSON writer with support for common Java data types.
+ * <p>
+ * The following types have specific handlers:
+ * <p>
+ * <ul>
+ * <li> <tt>null</tt> input
+ * <li>{@link Map}, {@link IndexedReadOnlyStringMap}, {@link StringMap}
+ * <li>{@link Collection} and {@link List}
+ * <li>{@link Number} ({@link BigDecimal}, {@link BigInteger}, {@link Float},
+ * {@link Double}, {@link Byte}, {@link Short}, {@link Integer}, and
+ * {@link Long})
+ * <li>{@link Boolean}
+ * <li>{@link StringBuilderFormattable}
+ * <li>arrays of primitve types
+ * <tt>char/boolean/byte/short/int/long/float/double</tt> and {@link Object}
+ * <li>{@link CharSequence} and <tt>char[]</tt> with necessary escaping
+ * </ul>
+ * <p>
+ * JSON standard quoting routines are borrowed from
+ * <a href="https://github.com/FasterXML/jackson-core">Jackson</a>.
+ */
+public final class JsonWriter implements AutoCloseable, Cloneable {
+
+ private final static char[] HEX_CHARS = "0123456789ABCDEF".toCharArray();
+
+ /**
+ * Lookup table used for determining which output characters in 7-bit ASCII
+ * range (i.e., first 128 Unicode code points, single-byte UTF-8 characters)
+ * need to be quoted.
+ *<p>
+ * Value of 0 means "no escaping"; other positive values, that value is
+ * character to use after backslash; and negative values, that generic
+ * (backslash - u) escaping is to be used.
+ */
+ private final static int[] ESC_CODES;
+ static {
+ int[] table = new int[128];
+ // Control chars need generic escape sequence
+ for (int i = 0; i < 32; ++i) {
+ // 04-Mar-2011, tatu: Used to use "-(i + 1)", replaced with constant
+ table[i] = -1;
+ }
+ // Others (and some within that range too) have explicit shorter sequences
+ table['"'] = '"';
+ table['\\'] = '\\';
+ // Escaping of slash is optional, so let's not add it
+ table[0x08] = 'b';
+ table[0x09] = 't';
+ table[0x0C] = 'f';
+ table[0x0A] = 'n';
+ table[0x0D] = 'r';
+ ESC_CODES = table;
+ }
+
+ private final char[] quoteBuffer;
+
+ private final StringBuilder stringBuilder;
+
+ private final StringBuilder formattableBuffer;
+
+ private final int maxStringLength;
+
+ private final String truncatedStringSuffix;
+
+ private final String quotedTruncatedStringSuffix;
+
+ private JsonWriter(final Builder builder) {
+ this.quoteBuffer = new char[]{'\\', '-', '0', '0', '-', '-'};
+ this.stringBuilder = new StringBuilder();
+ this.formattableBuffer = new StringBuilder();
+ this.maxStringLength = builder.maxStringLength;
+ this.truncatedStringSuffix = builder.truncatedStringSuffix;
+ this.quotedTruncatedStringSuffix = quoteString(builder.truncatedStringSuffix);
+ }
+
+ private String quoteString(final String string) {
+ final int startIndex = stringBuilder.length();
+ quoteString(string, 0, string.length());
+ final StringBuilder quotedStringBuilder = new StringBuilder();
+ quotedStringBuilder.append(stringBuilder, startIndex, stringBuilder.length());
+ final String quotedString = quotedStringBuilder.toString();
+ stringBuilder.setLength(startIndex);
+ return quotedString;
+ }
+
+ public String use(Runnable runnable) {
+ final int startIndex = stringBuilder.length();
+ runnable.run();
+ final StringBuilder sliceStringBuilder = new StringBuilder();
+ sliceStringBuilder.append(stringBuilder, startIndex, stringBuilder.length());
+ stringBuilder.setLength(startIndex);
+ return sliceStringBuilder.toString();
+ }
+
+ public StringBuilder getStringBuilder() {
+ return stringBuilder;
+ }
+
+ public int getMaxStringLength() {
+ return maxStringLength;
+ }
+
+ public String getTruncatedStringSuffix() {
+ return truncatedStringSuffix;
+ }
+
+ public void writeValue(final Object value) {
+
+ // null
+ if (value == null) {
+ writeNull();
+ }
+
+ // map
+ else if (value instanceof IndexedReadOnlyStringMap) {
+ final IndexedReadOnlyStringMap map = (IndexedReadOnlyStringMap) value;
+ writeObject(map);
+ } else if (value instanceof StringMap) {
+ final StringMap map = (StringMap) value;
+ writeObject(map);
+ } else if (value instanceof Map) {
+ @SuppressWarnings("unchecked")
+ final Map<String, Object> map = (Map<String, Object>) value;
+ writeObject(map);
+ }
+
+ // list & collection
+ else if (value instanceof List) {
+ @SuppressWarnings("unchecked")
+ final List<Object> list = (List<Object>) value;
+ writeArray(list);
+ } else if (value instanceof Collection) {
+ @SuppressWarnings("unchecked")
+ final Collection<Object> collection = (Collection<Object>) value;
+ writeArray(collection);
+ }
+
+ // number & boolean
+ else if (value instanceof Number) {
+ final Number number = (Number) value;
+ writeNumber(number);
+ } else if (value instanceof Boolean) {
+ final boolean booleanValue = (boolean) value;
+ writeBoolean(booleanValue);
+ }
+
+ // formattable
+ else if (value instanceof StringBuilderFormattable) {
+ final StringBuilderFormattable formattable = (StringBuilderFormattable) value;
+ writeString(formattable);
+ }
+
+ // arrays
+ else if (value instanceof char[]) {
+ final char[] charValues = (char[]) value;
+ writeArray(charValues);
+ } else if (value instanceof boolean[]) {
+ final boolean[] booleanValues = (boolean[]) value;
+ writeArray(booleanValues);
+ } else if (value instanceof byte[]) {
+ final byte[] byteValues = (byte[]) value;
+ writeArray(byteValues);
+ } else if (value instanceof short[]) {
+ final short[] shortValues = (short[]) value;
+ writeArray(shortValues);
+ } else if (value instanceof int[]) {
+ final int[] intValues = (int[]) value;
+ writeArray(intValues);
+ } else if (value instanceof long[]) {
+ final long[] longValues = (long[]) value;
+ writeArray(longValues);
+ } else if (value instanceof float[]) {
+ final float[] floatValues = (float[]) value;
+ writeArray(floatValues);
+ } else if (value instanceof double[]) {
+ final double[] doubleValues = (double[]) value;
+ writeArray(doubleValues);
+ } else if (value instanceof Object[]) {
+ final Object[] values = (Object[]) value;
+ writeArray(values);
+ }
+
+ // string
+ else {
+ final String stringValue = value instanceof String
+ ? (String) value
+ : String.valueOf(value);
+ writeString(stringValue);
+ }
+
+ }
+
+ public void writeObject(final StringMap map) {
+ if (map == null) {
+ writeNull();
+ } else {
+ writeObjectStart();
+ final boolean[] firstEntry = {true};
+ map.forEach((final String key, final Object value) -> {
+ if (key == null) {
+ throw new IllegalArgumentException("null keys are not allowed");
+ }
+ if (firstEntry[0]) {
+ firstEntry[0] = false;
+ } else {
+ writeSeparator();
+ }
+ writeObjectKey(key);
+ writeValue(value);
+ });
+ writeObjectEnd();
+ }
+ }
+
+ public void writeObject(final IndexedReadOnlyStringMap map) {
+ if (map == null) {
+ writeNull();
+ } else {
+ writeObjectStart();
+ for (int entryIndex = 0; entryIndex < map.size(); entryIndex++) {
+ final String key = map.getKeyAt(entryIndex);
+ final Object value = map.getValueAt(entryIndex);
+ if (entryIndex > 0) {
+ writeSeparator();
+ }
+ writeObjectKey(key);
+ writeValue(value);
+ }
+ writeObjectEnd();
+ }
+ }
+
+ public void writeObject(final Map<String, Object> map) {
+ if (map == null) {
+ writeNull();
+ } else {
+ writeObjectStart();
+ final boolean[] firstEntry = {true};
+ map.forEach((final String key, final Object value) -> {
+ if (key == null) {
+ throw new IllegalArgumentException("null keys are not allowed");
+ }
+ if (firstEntry[0]) {
+ firstEntry[0] = false;
+ } else {
+ writeSeparator();
+ }
+ writeObjectKey(key);
+ writeValue(value);
+ });
+ writeObjectEnd();
+ }
+ }
+
+ public void writeObjectStart() {
+ stringBuilder.append('{');
+ }
+
+ public void writeObjectEnd() {
+ stringBuilder.append('}');
+ }
+
+ public void writeObjectKey(final CharSequence key) {
+ writeString(key);
+ stringBuilder.append(':');
+ }
+
+ public void writeArray(final List<Object> items) {
+ if (items == null) {
+ writeNull();
+ } else {
+ writeArrayStart();
+ for (int itemIndex = 0; itemIndex < items.size(); itemIndex++) {
+ if (itemIndex > 0) {
+ writeSeparator();
+ }
+ final Object item = items.get(itemIndex);
+ writeValue(item);
+ }
+ writeArrayEnd();
+ }
+ }
+
+ public void writeArray(final Collection<Object> items) {
+ if (items == null) {
+ writeNull();
+ } else {
+ writeArrayStart();
+ final boolean[] firstItem = {true};
+ items.forEach((final Object item) -> {
+ if (firstItem[0]) {
+ firstItem[0] = false;
+ } else {
+ writeSeparator();
+ }
+ writeValue(item);
+ });
+ writeArrayEnd();
+ }
+ }
+
+ public void writeArray(final char[] items) {
+ if (items == null) {
+ writeNull();
+ } else {
+ writeArrayStart();
+ for (int itemIndex = 0; itemIndex < items.length; itemIndex++) {
+ if (itemIndex > 0) {
+ writeSeparator();
+ }
+ stringBuilder.append('"');
+ quoteString(items, itemIndex, 1);
+ stringBuilder.append('"');
+ }
+ writeArrayEnd();
+ }
+ }
+
+ public void writeArray(final boolean[] items) {
+ if (items == null) {
+ writeNull();
+ } else {
+ writeArrayStart();
+ for (int itemIndex = 0; itemIndex < items.length; itemIndex++) {
+ if (itemIndex > 0) {
+ writeSeparator();
+ }
+ final boolean item = items[itemIndex];
+ writeBoolean(item);
+ }
+ writeArrayEnd();
+ }
+ }
+
+ public void writeArray(final byte[] items) {
+ if (items == null) {
+ writeNull();
+ } else {
+ writeArrayStart();
+ for (int itemIndex = 0; itemIndex < items.length; itemIndex++) {
+ if (itemIndex > 0) {
+ writeSeparator();
+ }
+ final byte item = items[itemIndex];
+ writeNumber(item);
+ }
+ writeArrayEnd();
+ }
+ }
+
+ public void writeArray(final short[] items) {
+ if (items == null) {
+ writeNull();
+ } else {
+ writeArrayStart();
+ for (int itemIndex = 0; itemIndex < items.length; itemIndex++) {
+ if (itemIndex > 0) {
+ writeSeparator();
+ }
+ final short item = items[itemIndex];
+ writeNumber(item);
+ }
+ writeArrayEnd();
+ }
+ }
+
+ public void writeArray(final int[] items) {
+ if (items == null) {
+ writeNull();
+ } else {
+ writeArrayStart();
+ for (int itemIndex = 0; itemIndex < items.length; itemIndex++) {
+ if (itemIndex > 0) {
+ writeSeparator();
+ }
+ final int item = items[itemIndex];
+ writeNumber(item);
+ }
+ writeArrayEnd();
+ }
+ }
+
+ public void writeArray(final long[] items) {
+ if (items == null) {
+ writeNull();
+ } else {
+ writeArrayStart();
+ for (int itemIndex = 0; itemIndex < items.length; itemIndex++) {
+ if (itemIndex > 0) {
+ writeSeparator();
+ }
+ final long item = items[itemIndex];
+ writeNumber(item);
+ }
+ writeArrayEnd();
+ }
+ }
+
+ public void writeArray(final float[] items) {
+ if (items == null) {
+ writeNull();
+ } else {
+ writeArrayStart();
+ for (int itemIndex = 0; itemIndex < items.length; itemIndex++) {
+ if (itemIndex > 0) {
+ writeSeparator();
+ }
+ final float item = items[itemIndex];
+ writeNumber(item);
+ }
+ writeArrayEnd();
+ }
+ }
+
+ public void writeArray(final double[] items) {
+ if (items == null) {
+ writeNull();
+ } else {
+ writeArrayStart();
+ for (int itemIndex = 0; itemIndex < items.length; itemIndex++) {
+ if (itemIndex > 0) {
+ writeSeparator();
+ }
+ final double item = items[itemIndex];
+ writeNumber(item);
+ }
+ writeArrayEnd();
+ }
+ }
+
+ public void writeArray(final Object[] items) {
+ if (items == null) {
+ writeNull();
+ } else {
+ writeArrayStart();
+ for (int itemIndex = 0; itemIndex < items.length; itemIndex++) {
+ if (itemIndex > 0) {
+ writeSeparator();
+ }
+ final Object item = items[itemIndex];
+ writeValue(item);
+ }
+ writeArrayEnd();
+ }
+ }
+
+ public void writeArrayStart() {
+ stringBuilder.append('[');
+ }
+
+ public void writeArrayEnd() {
+ stringBuilder.append(']');
+ }
+
+ public void writeSeparator() {
+ stringBuilder.append(',');
+ }
+
+ public <S> void writeString(
+ final BiConsumer<StringBuilder, S> emitter,
+ final S state) {
+ Objects.requireNonNull(emitter, "emitter");
+ stringBuilder.append('"');
+ formattableBuffer.setLength(0);
+ emitter.accept(formattableBuffer, state);
+ final int length = formattableBuffer.length();
+ // Handle max. string length complying input.
+ if (length <= maxStringLength) {
+ quoteString(formattableBuffer, 0, length);
+ }
+ // Handle max. string length violating input.
+ else {
+ quoteString(formattableBuffer, 0, maxStringLength);
+ stringBuilder.append(quotedTruncatedStringSuffix);
+ }
+ stringBuilder.append('"');
+ }
+
+ public void writeString(final StringBuilderFormattable formattable) {
+ if (formattable == null) {
+ writeNull();
+ } else {
+ stringBuilder.append('"');
+ formattableBuffer.setLength(0);
+ formattable.formatTo(formattableBuffer);
+ final int length = formattableBuffer.length();
+ // Handle max. string length complying input.
+ if (length <= maxStringLength) {
+ quoteString(formattableBuffer, 0, length);
+ }
+ // Handle max. string length violating input.
+ else {
+ quoteString(formattableBuffer, 0, maxStringLength);
+ stringBuilder.append(quotedTruncatedStringSuffix);
+ }
+ stringBuilder.append('"');
+ }
+ }
+
+ public void writeString(final CharSequence seq) {
+ if (seq == null) {
+ writeNull();
+ } else {
+ writeString(seq, 0, seq.length());
+ }
+ }
+
+ public void writeString(
+ final CharSequence seq,
+ final int offset,
+ final int length) {
+
+ // Handle null input.
+ if (seq == null) {
+ writeNull();
+ return;
+ }
+
+ // Check arguments.
+ if (offset < 0) {
+ throw new IllegalArgumentException(
+ "was expecting a positive offset: " + offset);
+ }
+ if (length < 0) {
+ throw new IllegalArgumentException(
+ "was expecting a positive length: " + length);
+ }
+
+ stringBuilder.append('"');
+ // Handle max. string length complying input.
+ if (length <= maxStringLength) {
+ quoteString(seq, offset, length);
+ }
+ // Handle max. string length violating input.
+ else {
+ quoteString(seq, offset, maxStringLength);
+ stringBuilder.append(quotedTruncatedStringSuffix);
+ }
+ stringBuilder.append('"');
+
+ }
+
+ /**
+ * Quote text contents using JSON standard quoting.
+ */
+ private void quoteString(
+ final CharSequence seq,
+ final int offset,
+ final int length) {
+ final int limit = offset + length;
+ int i = offset;
+ outer:
+ while (i < limit) {
+ while (true) {
+ final char c = seq.charAt(i);
+ if (c < ESC_CODES.length && ESC_CODES[c] != 0) {
+ break;
+ }
+ stringBuilder.append(c);
+ if (++i >= limit) {
+ break outer;
+ }
+ }
+ final char d = seq.charAt(i++);
+ final int escCode = ESC_CODES[d];
+ final int quoteBufferLength = escCode < 0
+ ? quoteNumeric(d)
+ : quoteNamed(escCode);
+ stringBuilder.append(quoteBuffer, 0, quoteBufferLength);
+ }
+ }
+
+ public void writeString(final char[] buffer) {
+ if (buffer == null) {
+ writeNull();
+ } else {
+ writeString(buffer, 0, buffer.length);
+ }
+ }
+
+ public void writeString(
+ final char[] buffer,
+ final int offset,
+ final int length) {
+
+ // Handle null input.
+ if (buffer == null) {
+ writeNull();
+ return;
+ }
+
+ // Check arguments.
+ if (offset < 0) {
+ throw new IllegalArgumentException(
+ "was expecting a positive offset: " + offset);
+ }
+ if (length < 0) {
+ throw new IllegalArgumentException(
+ "was expecting a positive length: " + length);
+ }
+
+ stringBuilder.append('"');
+ // Handle max. string length complying input.
+ if (length <= maxStringLength) {
+ quoteString(buffer, offset, length);
+ }
+ // Handle max. string length violating input.
+ else {
+ quoteString(buffer, offset, maxStringLength);
+ stringBuilder.append(quotedTruncatedStringSuffix);
+ }
+ stringBuilder.append('"');
+
+ }
+
+ /**
+ * Quote text contents using JSON standard quoting.
+ */
+ private void quoteString(
+ final char[] buffer,
+ final int offset,
+ final int length) {
+ final int limit = offset + length;
+ int i = offset;
+ outer:
+ while (i < limit) {
+ while (true) {
+ final char c = buffer[i];
+ if (c < ESC_CODES.length && ESC_CODES[c] != 0) {
+ break;
+ }
+ stringBuilder.append(c);
+ if (++i >= limit) {
+ break outer;
+ }
+ }
+ final char d = buffer[i++];
+ final int escCode = ESC_CODES[d];
+ final int quoteBufferLength = escCode < 0
+ ? quoteNumeric(d)
+ : quoteNamed(escCode);
+ stringBuilder.append(quoteBuffer, 0, quoteBufferLength);
+ }
+ }
+
+ private int quoteNumeric(final int value) {
+ quoteBuffer[1] = 'u';
+ // We know it's a control char, so only the last 2 chars are non-0
+ quoteBuffer[4] = HEX_CHARS[value >> 4];
+ quoteBuffer[5] = HEX_CHARS[value & 0xF];
+ return 6;
+ }
+
+ private int quoteNamed(final int esc) {
+ quoteBuffer[1] = (char) esc;
+ return 2;
+ }
+
+ private void writeNumber(final Number number) {
+ if (number instanceof BigDecimal) {
+ final BigDecimal decimalNumber = (BigDecimal) number;
+ writeNumber(decimalNumber);
+ } else if (number instanceof BigInteger) {
+ final BigInteger integerNumber = (BigInteger) number;
+ writeNumber(integerNumber);
+ } else if (number instanceof Double) {
+ final double doubleNumber = (Double) number;
+ writeNumber(doubleNumber);
+ } else if (number instanceof Float) {
+ final float floatNumber = (float) number;
+ writeNumber(floatNumber);
+ } else if (number instanceof Byte ||
+ number instanceof Short ||
+ number instanceof Integer ||
+ number instanceof Long) {
+ final long longNumber = number.longValue();
+ writeNumber(longNumber);
+ } else {
+ final long longNumber = number.longValue();
+ final double doubleValue = number.doubleValue();
+ if (Double.compare(longNumber, doubleValue) == 0) {
+ writeNumber(longNumber);
+ } else {
+ writeNumber(doubleValue);
+ }
+ }
+ }
+
+ public void writeNumber(final BigDecimal number) {
+ if (number == null) {
+ writeNull();
+ } else {
+ stringBuilder.append(number);
+ }
+ }
+
+ public void writeNumber(final BigInteger number) {
+ if (number == null) {
+ writeNull();
+ } else {
+ stringBuilder.append(number);
+ }
+ }
+
+ public void writeNumber(final float number) {
+ stringBuilder.append(number);
+ }
+
+ public void writeNumber(final double number) {
+ stringBuilder.append(number);
+ }
+
+ public void writeNumber(final short number) {
+ stringBuilder.append(number);
+ }
+
+ public void writeNumber(final int number) {
+ stringBuilder.append(number);
+ }
+
+ public void writeNumber(final long number) {
+ stringBuilder.append(number);
+ }
+
+ public void writeNumber(final long integralPart, final long fractionalPart) {
+ if (fractionalPart < 0) {
+ throw new IllegalArgumentException(
+ "was expecting a positive fraction: " + fractionalPart);
+ }
+ stringBuilder.append(integralPart);
+ if (fractionalPart != 0) {
+ stringBuilder.append('.');
+ stringBuilder.append(fractionalPart);
+ }
+ }
+
+ public void writeBoolean(final boolean value) {
+ writeRawString(value ? "true" : "false");
+ }
+
+ public void writeNull() {
+ writeRawString("null");
+ }
+
+ public void writeRawString(final CharSequence seq) {
+ Objects.requireNonNull(seq, "seq");
+ writeRawString(seq, 0, seq.length());
+ }
+
+ public void writeRawString(
+ final CharSequence seq,
+ final int offset,
+ final int length) {
+
+ // Check arguments.
+ Objects.requireNonNull(seq, "seq");
+ if (offset < 0) {
+ throw new IllegalArgumentException(
+ "was expecting a positive offset: " + offset);
+ }
+ if (length < 0) {
+ throw new IllegalArgumentException(
+ "was expecting a positive length: " + length);
+ }
+
+ // Write characters.
+ final int limit = offset + length;
+ stringBuilder.append(seq, offset, limit);
+
+ }
+
+ public void writeRawString(final char[] buffer) {
+ Objects.requireNonNull(buffer, "buffer");
+ writeRawString(buffer, 0, buffer.length);
+ }
+
+ public void writeRawString(
+ final char[] buffer,
+ final int offset,
+ final int length) {
+
+ // Check arguments.
+ Objects.requireNonNull(buffer, "buffer");
+ if (offset < 0) {
+ throw new IllegalArgumentException(
+ "was expecting a positive offset: " + offset);
+ }
+ if (length < 0) {
+ throw new IllegalArgumentException(
+ "was expecting a positive length: " + length);
+ }
+
+ // Write characters.
+ stringBuilder.append(buffer, offset, length);
+
+ }
+
+ @Override
+ public void close() {
+ stringBuilder.setLength(0);
+ }
+
+ @Override
+ @SuppressWarnings("MethodDoesntCallSuperMethod")
+ public JsonWriter clone() {
+ final JsonWriter jsonWriter = newBuilder()
+ .setMaxStringLength(maxStringLength)
+ .setTruncatedStringSuffix(truncatedStringSuffix)
+ .build();
+ jsonWriter.stringBuilder.append(stringBuilder);
+ return jsonWriter;
+ }
+
+ public static Builder newBuilder() {
+ return new Builder();
+ }
+
+ public static final class Builder {
+
+ private int maxStringLength;
+
+ private String truncatedStringSuffix;
+
+ public int getMaxStringLength() {
+ return maxStringLength;
+ }
+
+ public Builder setMaxStringLength(final int maxStringLength) {
+ this.maxStringLength = maxStringLength;
+ return this;
+ }
+
+ public String getTruncatedStringSuffix() {
+ return truncatedStringSuffix;
+ }
+
+ public Builder setTruncatedStringSuffix(final String truncatedStringSuffix) {
+ this.truncatedStringSuffix = truncatedStringSuffix;
+ return this;
+ }
+
+ public JsonWriter build() {
+ validate();
+ return new JsonWriter(this);
+ }
+
+ private void validate() {
+ if (maxStringLength <= 0) {
+ throw new IllegalArgumentException(
+ "was expecting maxStringLength > 0: " +
+ maxStringLength);
+ }
+ Objects.requireNonNull(truncatedStringSuffix, "truncatedStringSuffix");
+ }
+
+ }
+
+}
diff --git a/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/util/MapAccessor.java b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/util/MapAccessor.java
new file mode 100644
index 0000000..a4c140f
--- /dev/null
+++ b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/util/MapAccessor.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.layout.json.template.util;
+
+import java.util.Arrays;
+import java.util.Map;
+import java.util.Objects;
+
+public class MapAccessor {
+
+ private final Map<String, Object> map;
+
+ public MapAccessor(final Map<String, Object> map) {
+ this.map = Objects.requireNonNull(map, "map");
+ }
+
+ public String getString(final String key) {
+ final String[] path = {key};
+ return getObject(path, String.class);
+ }
+
+ public String getString(final String[] path) {
+ return getObject(path, String.class);
+ }
+
+ public boolean getBoolean(final String key, final boolean defaultValue) {
+ final String[] path = {key};
+ return getBoolean(path, defaultValue);
+ }
+
+ public boolean getBoolean(final String[] path, final boolean defaultValue) {
+ final Boolean value = getObject(path, Boolean.class);
+ return value == null ? defaultValue : value;
+ }
+
+ public Boolean getBoolean(final String key) {
+ final String[] path = {key};
+ return getObject(path, Boolean.class);
+ }
+
+ public Boolean getBoolean(final String[] path) {
+ return getObject(path, Boolean.class);
+ }
+
+ public Integer getInteger(final String key) {
+ final String[] path = {key};
+ return getInteger(path);
+ }
+
+ public Integer getInteger(final String[] path) {
+ return getObject(path, Integer.class);
+ }
+
+ public boolean exists(final String key) {
+ final String[] path = {key};
+ return exists(path);
+ }
+
+ public boolean exists(final String[] path) {
+ final Object value = getObject(path, Object.class);
+ return value != null;
+ }
+
+ public Object getObject(final String key) {
+ final String[] path = {key};
+ return getObject(path, Object.class);
+ }
+
+ public <T> T getObject(final String key, final Class<T> clazz) {
+ final String[] path = {key};
+ return getObject(path, clazz);
+ }
+
+ public Object getObject(final String[] path) {
+ return getObject(path, Object.class);
+ }
+
+ public <T> T getObject(final String[] path, final Class<T> clazz) {
+ Objects.requireNonNull(path, "path");
+ Objects.requireNonNull(clazz, "clazz");
+ if (path.length == 0) {
+ throw new IllegalArgumentException("empty path");
+ }
+ Object parent = map;
+ for (final String key : path) {
+ if (!(parent instanceof Map)) {
+ return null;
+ }
+ @SuppressWarnings("unchecked")
+ final Map<String, Object> parentMap = (Map<String, Object>) parent;
+ parent = parentMap.get(key);
+ }
+ if (parent != null && !clazz.isInstance(parent)) {
+ final String message = String.format(
+ "was expecting %s at path %s: %s (of type %s)",
+ clazz.getSimpleName(),
+ Arrays.asList(path),
+ parent,
+ parent.getClass().getCanonicalName());
+ throw new IllegalArgumentException(message);
+ }
+ @SuppressWarnings("unchecked")
+ final T typedValue = (T) parent;
+ return typedValue;
+ }
+
+ @Override
+ public boolean equals(final Object instance) {
+ if (this == instance) return true;
+ if (instance == null || getClass() != instance.getClass()) return false;
+ final MapAccessor that = (MapAccessor) instance;
+ return map.equals(that.map);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(map);
+ }
+
+ @Override
+ public String toString() {
+ return map.toString();
+ }
+
+}
diff --git a/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/util/QueueingRecycler.java b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/util/QueueingRecycler.java
new file mode 100644
index 0000000..5f091bd
--- /dev/null
+++ b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/util/QueueingRecycler.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.layout.json.template.util;
+
+import java.util.Queue;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+public class QueueingRecycler<V> implements Recycler<V> {
+
+ private final Supplier<V> supplier;
+
+ private final Consumer<V> cleaner;
+
+ private final Queue<V> queue;
+
+ public QueueingRecycler(
+ final Supplier<V> supplier,
+ final Consumer<V> cleaner,
+ final Queue<V> queue) {
+ this.supplier = supplier;
+ this.cleaner = cleaner;
+ this.queue = queue;
+ }
+
+ // Visible for tests.
+ Queue<V> getQueue() {
+ return queue;
+ }
+
+ @Override
+ public V acquire() {
+ final V value = queue.poll();
+ if (value == null) {
+ return supplier.get();
+ } else {
+ cleaner.accept(value);
+ return value;
+ }
+ }
+
+ @Override
+ public void release(final V value) {
+ queue.offer(value);
+ }
+
+}
diff --git a/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/util/QueueingRecyclerFactory.java b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/util/QueueingRecyclerFactory.java
new file mode 100644
index 0000000..c549522
--- /dev/null
+++ b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/util/QueueingRecyclerFactory.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.layout.json.template.util;
+
+import java.util.Queue;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+public class QueueingRecyclerFactory implements RecyclerFactory {
+
+ private final Supplier<Queue<Object>> queueSupplier;
+
+ public QueueingRecyclerFactory(final Supplier<Queue<Object>> queueSupplier) {
+ this.queueSupplier = queueSupplier;
+ }
+
+ @Override
+ public <V> Recycler<V> create(
+ final Supplier<V> supplier,
+ final Consumer<V> cleaner) {
+ @SuppressWarnings("unchecked")
+ final Queue<V> queue = (Queue<V>) queueSupplier.get();
+ return new QueueingRecycler<V>(supplier, cleaner, queue);
+ }
+
+}
diff --git a/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/util/Recycler.java b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/util/Recycler.java
new file mode 100644
index 0000000..b6a0c89
--- /dev/null
+++ b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/util/Recycler.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.layout.json.template.util;
+
+public interface Recycler<V> {
+
+ V acquire();
+
+ void release(V value);
+
+}
diff --git a/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/util/RecyclerFactories.java b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/util/RecyclerFactories.java
new file mode 100644
index 0000000..a6937c6
--- /dev/null
+++ b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/util/RecyclerFactories.java
@@ -0,0 +1,205 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.logging.log4j.layout.json.template.util;
+
+import org.apache.logging.log4j.core.util.Constants;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.convert.TypeConverter;
+import org.apache.logging.log4j.plugins.convert.TypeConverters;
+import org.apache.logging.log4j.util.LoaderUtil;
+import org.jctools.queues.MpmcArrayQueue;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Queue;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.function.Supplier;
+
+public enum RecyclerFactories {;
+
+ private static final String JCTOOLS_QUEUE_CLASS_SUPPLIER_PATH =
+ "org.jctools.queues.MpmcArrayQueue.new";
+
+ private static final boolean JCTOOLS_QUEUE_CLASS_AVAILABLE =
+ isJctoolsQueueClassAvailable();
+
+ private static boolean isJctoolsQueueClassAvailable() {
+ try {
+ final String className = JCTOOLS_QUEUE_CLASS_SUPPLIER_PATH
+ .replaceAll("\\.new$", "");
+ LoaderUtil.loadClass(className);
+ return true;
+ } catch (final ClassNotFoundException ignored) {
+ return false;
+ }
+ }
+
+ @Plugin(name = "RecyclerFactory", category = TypeConverters.CATEGORY)
+ public static final class RecyclerFactoryConverter implements TypeConverter<RecyclerFactory> {
+ @Override
+ public RecyclerFactory convert(final String recyclerFactorySpec) {
+ return ofSpec(recyclerFactorySpec);
+ }
+ }
+
+ public static RecyclerFactory ofSpec(final String recyclerFactorySpec) {
+
+ // Determine the default capacity.
+ int defaultCapacity = Math.max(
+ 2 * Runtime.getRuntime().availableProcessors() + 1,
+ 8);
+
+ // TLA-, MPMC-, or ABQ-based queueing factory -- if nothing is specified.
+ if (recyclerFactorySpec == null) {
+ if (Constants.ENABLE_THREADLOCALS) {
+ return ThreadLocalRecyclerFactory.getInstance();
+ } else {
+ final Supplier<Queue<Object>> queueSupplier =
+ JCTOOLS_QUEUE_CLASS_AVAILABLE
+ ? () -> new MpmcArrayQueue<>(defaultCapacity)
+ : () -> new ArrayBlockingQueue<>(defaultCapacity);
+ return new QueueingRecyclerFactory(queueSupplier);
+ }
+ }
+
+ // Is a dummy factory requested?
+ else if (recyclerFactorySpec.equals("dummy")) {
+ return DummyRecyclerFactory.getInstance();
+ }
+
+ // Is a TLA factory requested?
+ else if (recyclerFactorySpec.equals("threadLocal")) {
+ return ThreadLocalRecyclerFactory.getInstance();
+ }
+
+ // Is a queueing factory requested?
+ else if (recyclerFactorySpec.startsWith("queue")) {
+ return readQueueingRecyclerFactory(recyclerFactorySpec, defaultCapacity);
+ }
+
+ // Bogus input, bail out.
+ else {
+ throw new IllegalArgumentException(
+ "invalid recycler factory: " + recyclerFactorySpec);
+ }
+
+ }
+
+ private static RecyclerFactory readQueueingRecyclerFactory(
+ final String recyclerFactorySpec,
+ final int defaultCapacity) {
+
+ // Parse the spec.
+ final String queueFactorySpec = recyclerFactorySpec.substring(
+ "queue".length() +
+ (recyclerFactorySpec.startsWith("queue:")
+ ? 1
+ : 0));
+ final Map<String, StringParameterParser.Value> parsedValues =
+ StringParameterParser.parse(
+ queueFactorySpec,
+ new LinkedHashSet<>(Arrays.asList("supplier", "capacity")));
+
+ // Read the supplier path.
+ final StringParameterParser.Value supplierValue = parsedValues.get("supplier");
+ final String supplierPath;
+ if (supplierValue == null || supplierValue instanceof StringParameterParser.NullValue) {
+ supplierPath = JCTOOLS_QUEUE_CLASS_AVAILABLE
+ ? JCTOOLS_QUEUE_CLASS_SUPPLIER_PATH
+ : "java.util.concurrent.ArrayBlockingQueue.new";
+ } else {
+ supplierPath = supplierValue.toString();
+ }
+
+ // Read the capacity.
+ final StringParameterParser.Value capacityValue = parsedValues.get("capacity");
+ final int capacity;
+ if (capacityValue == null || capacityValue instanceof StringParameterParser.NullValue) {
+ capacity = defaultCapacity;
+ } else {
+ try {
+ capacity = Integer.parseInt(capacityValue.toString());
+ } catch (final NumberFormatException error) {
+ throw new IllegalArgumentException(
+ "failed reading capacity in queueing recycler " +
+ "factory: " + queueFactorySpec, error);
+ }
+ }
+
+ // Execute the read spec.
+ return createRecyclerFactory(queueFactorySpec, supplierPath, capacity);
+
+ }
+
+ private static RecyclerFactory createRecyclerFactory(
+ final String queueFactorySpec,
+ final String supplierPath,
+ final int capacity) {
+ final int supplierPathSplitterIndex = supplierPath.lastIndexOf('.');
+ if (supplierPathSplitterIndex < 0) {
+ throw new IllegalArgumentException(
+ "invalid supplier in queueing recycler factory: " +
+ queueFactorySpec);
+ }
+ final String supplierClassName = supplierPath.substring(0, supplierPathSplitterIndex);
+ final String supplierMethodName = supplierPath.substring(supplierPathSplitterIndex + 1);
+ try {
+ final Class<?> supplierClass = LoaderUtil.loadClass(supplierClassName);
+ final Supplier<Queue<Object>> queueSupplier;
+ if ("new".equals(supplierMethodName)) {
+ final Constructor<?> supplierCtor =
+ supplierClass.getDeclaredConstructor(int.class);
+ queueSupplier = () -> {
+ try {
+ @SuppressWarnings("unchecked")
+ final Queue<Object> typedQueue =
+ (Queue<Object>) supplierCtor.newInstance(capacity);
+ return typedQueue;
+ } catch (final Exception error) {
+ throw new RuntimeException(
+ "recycler queue construction failed for factory: " +
+ queueFactorySpec, error);
+ }
+ };
+ } else {
+ final Method supplierMethod =
+ supplierClass.getMethod(supplierMethodName, int.class);
+ queueSupplier = () -> {
+ try {
+ @SuppressWarnings("unchecked")
+ final Queue<Object> typedQueue =
+ (Queue<Object>) supplierMethod.invoke(null, capacity);
+ return typedQueue;
+ } catch (final Exception error) {
+ throw new RuntimeException(
+ "recycler queue construction failed for factory: " +
+ queueFactorySpec, error);
+ }
+ };
+ }
+ return new QueueingRecyclerFactory(queueSupplier);
+ } catch (final Exception error) {
+ throw new RuntimeException(
+ "failed executing queueing recycler factory: " +
+ queueFactorySpec, error);
+ }
+ }
+
+}
diff --git a/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/util/RecyclerFactory.java b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/util/RecyclerFactory.java
new file mode 100644
index 0000000..3b7737c
--- /dev/null
+++ b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/util/RecyclerFactory.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.layout.json.template.util;
+
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+@FunctionalInterface
+public interface RecyclerFactory {
+
+ default <V> Recycler<V> create(Supplier<V> supplier) {
+ return create(supplier, ignored -> {});
+ }
+
+ <V> Recycler<V> create(Supplier<V> supplier, Consumer<V> cleaner);
+
+}
diff --git a/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/util/StringParameterParser.java b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/util/StringParameterParser.java
new file mode 100644
index 0000000..018f6b7
--- /dev/null
+++ b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/util/StringParameterParser.java
@@ -0,0 +1,292 @@
+package org.apache.logging.log4j.layout.json.template.util;
+
+import org.apache.logging.log4j.util.Strings;
+
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.Callable;
+
+public enum StringParameterParser {;
+
+ public enum Values {;
+
+ static NullValue nullValue() {
+ return NullValue.INSTANCE;
+ }
+
+ static StringValue stringValue(final String string) {
+ return new StringValue(string);
+ }
+
+ static DoubleQuotedStringValue doubleQuotedStringValue(
+ final String doubleQuotedString) {
+ return new DoubleQuotedStringValue(doubleQuotedString);
+ }
+
+ }
+
+ public interface Value {}
+
+ public static final class NullValue implements Value {
+
+ private static final NullValue INSTANCE = new NullValue();
+
+ private NullValue() {}
+
+ @Override
+ public String toString() {
+ return null;
+ }
+
+ }
+
+ public static final class StringValue implements Value {
+
+ private final String string;
+
+ private StringValue(String string) {
+ this.string = string;
+ }
+
+ public String getString() {
+ return string;
+ }
+
+ @Override
+ public boolean equals(Object object) {
+ if (this == object) return true;
+ if (object == null || getClass() != object.getClass()) return false;
+ StringValue that = (StringValue) object;
+ return string.equals(that.string);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(string);
+ }
+
+ @Override
+ public String toString() {
+ return string;
+ }
+
+ }
+
+ public static final class DoubleQuotedStringValue implements Value {
+
+ private final String doubleQuotedString;
+
+ private DoubleQuotedStringValue(String doubleQuotedString) {
+ this.doubleQuotedString = doubleQuotedString;
+ }
+
+ public String getDoubleQuotedString() {
+ return doubleQuotedString;
+ }
+
+ @Override
+ public boolean equals(Object object) {
+ if (this == object) return true;
+ if (object == null || getClass() != object.getClass()) return false;
+ DoubleQuotedStringValue that = (DoubleQuotedStringValue) object;
+ return doubleQuotedString.equals(that.doubleQuotedString);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(doubleQuotedString);
+ }
+
+ @Override
+ public String toString() {
+ return doubleQuotedString.replaceAll("\\\\\"", "\"");
+ }
+
+ }
+
+ private enum State { READING_KEY, READING_VALUE }
+
+ private static final class Parser implements Callable<Map<String, Value>> {
+
+ private final String input;
+
+ private final Map<String, Value> map;
+
+ private State state;
+
+ private int i;
+
+ private String key;
+
+ private Parser(final String input) {
+ this.input = Objects.requireNonNull(input, "input");
+ this.map = new LinkedHashMap<>();
+ this.state = State.READING_KEY;
+ this.i = 0;
+ this.key = null;
+ }
+
+ @Override
+ public Map<String, Value> call() {
+ while (true) {
+ skipWhitespace();
+ if (i >= input.length()) {
+ break;
+ }
+ switch (state) {
+ case READING_KEY:
+ readKey();
+ break;
+ case READING_VALUE:
+ readValue();
+ break;
+ default:
+ throw new IllegalStateException("unknown state: " + state);
+ }
+ }
+ if (state == State.READING_VALUE) {
+ map.put(key, Values.nullValue());
+ }
+ return map;
+ }
+
+ private void readKey() {
+ final int eq = input.indexOf('=', i);
+ final int co = input.indexOf(',', i);
+ final int j;
+ final int nextI;
+ if (eq < 0 && co < 0) {
+ // Neither '=', nor ',' was found.
+ j = nextI = input.length();
+ } else if (eq < 0) {
+ // Found ','.
+ j = nextI = co;
+ } else if (co < 0) {
+ // Found '='.
+ j = eq;
+ nextI = eq + 1;
+ } else if (eq < co) {
+ // Found '=...,'.
+ j = eq;
+ nextI = eq + 1;
+ } else {
+ // Found ',...='.
+ j = co;
+ nextI = co;
+ }
+ key = input.substring(i, j).trim();
+ if (Strings.isEmpty(key)) {
+ final String message = String.format(
+ "failed to locate key at index %d: %s",
+ i, input);
+ throw new IllegalArgumentException(message);
+ }
+ if (map.containsKey(key)) {
+ final String message = String.format(
+ "conflicting key at index %d: %s",
+ i, input);
+ throw new IllegalArgumentException(message);
+ }
+ state = State.READING_VALUE;
+ i = nextI;
+ }
+
+ private void readValue() {
+ final boolean doubleQuoted = input.charAt(i) == '"';
+ if (doubleQuoted) {
+ readDoubleQuotedStringValue();
+ } else {
+ readStringValue();
+ }
+ key = null;
+ state = State.READING_KEY;
+ }
+
+ private void readDoubleQuotedStringValue() {
+ int j = i + 1;
+ while (j < input.length()) {
+ if (input.charAt(j) == '"' && input.charAt(j - 1) != '\\') {
+ break;
+ } else {
+ j++;
+ }
+ }
+ if (j >= input.length()) {
+ final String message = String.format(
+ "failed to locate the end of double-quoted content starting at index %d: %s",
+ i, input);
+ throw new IllegalArgumentException(message);
+ }
+ final String content = input
+ .substring(i + 1, j)
+ .replaceAll("\\\\\"", "\"");
+ final Value value = Values.doubleQuotedStringValue(content);
+ map.put(key, value);
+ i = j + 1;
+ skipWhitespace();
+ if (i < input.length()) {
+ if (input.charAt(i) != ',') {
+ final String message = String.format(
+ "was expecting comma at index %d: %s",
+ i, input);
+ throw new IllegalArgumentException(message);
+ }
+ i++;
+ }
+ }
+
+ private void skipWhitespace() {
+ while (i < input.length()) {
+ final char c = input.charAt(i);
+ if (!Character.isWhitespace(c)) {
+ break;
+ } else {
+ i++;
+ }
+ }
+ }
+
+ private void readStringValue() {
+ int j = input.indexOf(',', i/* + 1*/);
+ if (j < 0) {
+ j = input.length();
+ }
+ final String content = input.substring(i, j);
+ final String trimmedContent = content.trim();
+ final Value value = trimmedContent.isEmpty()
+ ? Values.nullValue()
+ : Values.stringValue(trimmedContent);
+ map.put(key, value);
+ i += content.length() + 1;
+ }
+
+ }
+
+ public static Map<String, Value> parse(final String input) {
+ return parse(input, null);
+ }
+
+ public static Map<String, Value> parse(
+ final String input,
+ final Set<String> allowedKeys) {
+ if (Strings.isBlank(input)) {
+ return Collections.emptyMap();
+ }
+ final Map<String, Value> map = new Parser(input).call();
+ final Set<String> actualKeys = map.keySet();
+ for (final String actualKey : actualKeys) {
+ final boolean allowed = allowedKeys == null || allowedKeys.contains(actualKey);
+ if (!allowed) {
+ final String message = String.format(
+ "unknown key \"%s\" is found in input: %s",
+ actualKey, input);
+ throw new IllegalArgumentException(message);
+ }
+ }
+ return map;
+ }
+
+}
diff --git a/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/util/ThreadLocalRecycler.java b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/util/ThreadLocalRecycler.java
new file mode 100644
index 0000000..1c58d4f
--- /dev/null
+++ b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/util/ThreadLocalRecycler.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.layout.json.template.util;
+
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+public class ThreadLocalRecycler<V> implements Recycler<V> {
+
+ private final Consumer<V> cleaner;
+
+ private final ThreadLocal<V> holder;
+
+ public ThreadLocalRecycler(
+ final Supplier<V> supplier,
+ final Consumer<V> cleaner) {
+ this.cleaner = cleaner;
+ this.holder = ThreadLocal.withInitial(supplier);
+ }
+
+ @Override
+ public V acquire() {
+ final V value = holder.get();
+ cleaner.accept(value);
+ return value;
+ }
+
+ @Override
+ public void release(final V value) {}
+
+}
diff --git a/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/util/ThreadLocalRecyclerFactory.java b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/util/ThreadLocalRecyclerFactory.java
new file mode 100644
index 0000000..8ea6c61
--- /dev/null
+++ b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/util/ThreadLocalRecyclerFactory.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.layout.json.template.util;
+
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+public class ThreadLocalRecyclerFactory implements RecyclerFactory {
+
+ private static final ThreadLocalRecyclerFactory INSTANCE =
+ new ThreadLocalRecyclerFactory();
+
+ private ThreadLocalRecyclerFactory() {}
+
+ public static ThreadLocalRecyclerFactory getInstance() {
+ return INSTANCE;
+ }
+
+ @Override
+ public <V> Recycler<V> create(
+ final Supplier<V> supplier,
+ final Consumer<V> cleaner) {
+ return new ThreadLocalRecycler<>(supplier, cleaner);
+ }
+
+}
diff --git a/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/util/TruncatingBufferedPrintWriter.java b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/util/TruncatingBufferedPrintWriter.java
new file mode 100644
index 0000000..37338e6
--- /dev/null
+++ b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/util/TruncatingBufferedPrintWriter.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.layout.json.template.util;
+
+import java.io.PrintWriter;
+
+public final class TruncatingBufferedPrintWriter extends PrintWriter {
+
+ private final TruncatingBufferedWriter writer;
+
+ private TruncatingBufferedPrintWriter(final TruncatingBufferedWriter writer) {
+ super(writer, false);
+ this.writer = writer;
+ }
+
+ public static TruncatingBufferedPrintWriter ofCapacity(final int capacity) {
+ if (capacity < 0) {
+ throw new IllegalArgumentException(
+ "was expecting a non-negative capacity: " + capacity);
+ }
+ final TruncatingBufferedWriter writer = new TruncatingBufferedWriter(capacity);
+ return new TruncatingBufferedPrintWriter(writer);
+ }
+
+ public char[] getBuffer() {
+ return writer.getBuffer();
+ }
+
+ public int getPosition() {
+ return writer.getPosition();
+ }
+
+ public int getCapacity() {
+ return writer.getCapacity();
+ }
+
+ public boolean isTruncated() {
+ return writer.isTruncated();
+ }
+
+ @Override
+ public void close() {
+ writer.close();
+ }
+
+}
diff --git a/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/util/TruncatingBufferedWriter.java b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/util/TruncatingBufferedWriter.java
new file mode 100644
index 0000000..e31f21c
--- /dev/null
+++ b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/util/TruncatingBufferedWriter.java
@@ -0,0 +1,208 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.logging.log4j.layout.json.template.util;
+
+import java.io.Writer;
+import java.util.Objects;
+
+public final class TruncatingBufferedWriter extends Writer {
+
+ private final char[] buffer;
+
+ private int position;
+
+ private boolean truncated;
+
+ TruncatingBufferedWriter(final int capacity) {
+ this.buffer = new char[capacity];
+ this.position = 0;
+ this.truncated = false;
+ }
+
+ char[] getBuffer() {
+ return buffer;
+ }
+
+ int getPosition() {
+ return position;
+ }
+
+ int getCapacity() {
+ return buffer.length;
+ }
+
+ boolean isTruncated() {
+ return truncated;
+ }
+
+ @Override
+ public void write(final int c) {
+ if (position < buffer.length) {
+ buffer[position++] = (char) c;
+ } else {
+ truncated = true;
+ }
+ }
+
+ @Override
+ public void write(final char[] source) {
+ Objects.requireNonNull(source, "source");
+ write(source, 0, source.length);
+ }
+
+ @Override
+ public void write(final char[] source, final int offset, final int length) {
+
+ // Check arguments.
+ Objects.requireNonNull(source, "source");
+ if (offset < 0 || offset >= source.length) {
+ throw new IndexOutOfBoundsException("invalid offset: " + offset);
+ }
+ if (length < 0 || Math.addExact(offset, length) > source.length) {
+ throw new IndexOutOfBoundsException("invalid length: " + length);
+ }
+
+ // If input fits as is.
+ final int maxLength = buffer.length - position;
+ if (length < maxLength) {
+ System.arraycopy(source, offset, buffer, position, length);
+ position += length;
+ }
+
+ // If truncation is possible.
+ else if (maxLength > 0) {
+ System.arraycopy(source, offset, buffer, position, maxLength);
+ position += maxLength;
+ truncated = true;
+ }
+
+ }
+
+ @Override
+ public void write(final String string) {
+
+ // Check arguments.
+ Objects.requireNonNull(string, "string");
+ final int length = string.length();
+ final int maxLength = buffer.length - position;
+
+ // If input fits as is.
+ if (length < maxLength) {
+ string.getChars(0, length, buffer, position);
+ position += length;
+ }
+
+ // If truncation is possible.
+ else if (maxLength > 0) {
+ string.getChars(0, maxLength, buffer, position);
+ position += maxLength;
+ truncated = true;
+ }
+
+ }
+
+ @Override
+ public void write(final String string, final int offset, final int length) {
+
+ // Check arguments.
+ Objects.requireNonNull(string, "string");
+ if (offset < 0 || offset >= string.length()) {
+ throw new IndexOutOfBoundsException("invalid offset: " + offset);
+ }
+ if (length < 0 || Math.addExact(offset, length) > string.length()) {
+ throw new IndexOutOfBoundsException("invalid length: " + length);
+ }
+
+ // If input fits as is.
+ final int maxLength = buffer.length - position;
+ if (length < maxLength) {
+ string.getChars(offset, offset + length, buffer, position);
+ position += length;
+ }
+
+ // If truncation is possible.
+ else if (maxLength > 0) {
+ string.getChars(offset, offset + maxLength, buffer, position);
+ position += maxLength;
+ truncated = true;
+ }
+
+ }
+
+ @Override
+ public Writer append(final char c) {
+ write(c);
+ return this;
+ }
+
+ @Override
+ public Writer append(final CharSequence seq) {
+ return seq == null
+ ? append("null", 0, 4)
+ : append(seq, 0, seq.length());
+ }
+
+ @Override
+ public Writer append(final CharSequence seq, final int start, final int end) {
+
+ // Short-circuit on null sequence.
+ if (seq == null) {
+ write("null");
+ return this;
+ }
+
+ // Check arguments.
+ if (start < 0 || start >= seq.length()) {
+ throw new IndexOutOfBoundsException("invalid start: " + start);
+ }
+ if (end < start || end > seq.length()) {
+ throw new IndexOutOfBoundsException("invalid end: " + end);
+ }
+
+ // If input fits as is.
+ final int length = end - start;
+ final int maxLength = buffer.length - position;
+ if (length < maxLength) {
+ for (int i = start; i < end; i++) {
+ final char c = seq.charAt(i);
+ buffer[position++] = c;
+ }
+ }
+
+ // If truncation is possible.
+ else if (maxLength > 0) {
+ final int truncatedEnd = start + maxLength;
+ for (int i = start; i < truncatedEnd; i++) {
+ final char c = seq.charAt(i);
+ buffer[position++] = c;
+ }
+ truncated = true;
+ }
+ return this;
+
+ }
+
+ @Override
+ public void flush() {}
+
+ @Override
+ public void close() {
+ position = 0;
+ truncated = false;
+ }
+
+}
diff --git a/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/util/Uris.java b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/util/Uris.java
new file mode 100644
index 0000000..65cd863
--- /dev/null
+++ b/log4j-layout-json-template/src/main/java/org/apache/logging/log4j/layout/json/template/util/Uris.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.layout.json.template.util;
+
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.status.StatusLogger;
+import org.apache.logging.log4j.util.LoaderUtil;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.URI;
+import java.net.URL;
+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.List;
+import java.util.Objects;
+
+public enum Uris {;
+
+ private static final Logger LOGGER = StatusLogger.getLogger();
+
+ /**
+ * Reads {@link URI} specs of scheme <tt>classpath</tt> and <tt>file</tt>.
+ *
+ * @param spec the {@link URI} spec, e.g., <tt>file:/holy/cow.txt</tt> or
+ * <tt>classpath:/holy/cat.txt</tt>
+ * @param charset used {@link Charset} for decoding the file
+ */
+ public static String readUri(final String spec, final Charset charset) {
+ Objects.requireNonNull(spec, "spec");
+ Objects.requireNonNull(charset, "charset");
+ try {
+ final URI uri = new URI(spec);
+ return unsafeReadUri(uri, charset);
+ } catch (final Exception error) {
+ throw new RuntimeException("failed reading URI: " + spec, error);
+ }
+ }
+
+ /**
+ * Reads {@link URI}s of scheme <tt>classpath</tt> and <tt>file</tt>.
+ *
+ * @param uri the {@link URI}, e.g., <tt>file:/holy/cow.txt</tt> or
+ * <tt>classpath:/holy/cat.txt</tt>
+ * @param charset used {@link Charset} for decoding the file
+ */
+ public static String readUri(final URI uri, final Charset charset) {
+ Objects.requireNonNull(uri, "uri");
+ Objects.requireNonNull(charset, "charset");
+ try {
+ return unsafeReadUri(uri, charset);
+ } catch (final Exception error) {
+ throw new RuntimeException("failed reading URI: " + uri, error);
+ }
+ }
+
+ private static String unsafeReadUri(
+ final URI uri,
+ final Charset charset)
+ throws Exception {
+ final String uriScheme = uri.getScheme().toLowerCase();
+ switch (uriScheme) {
+ case "classpath":
+ return readClassPathUri(uri, charset);
+ case "file":
+ return readFileUri(uri, charset);
+ default: {
+ throw new IllegalArgumentException("unknown scheme in URI: " + uri);
+ }
+ }
+ }
+
+ private static String readFileUri(
+ final URI uri,
+ final Charset charset)
+ throws IOException {
+ final Path path = Paths.get(uri);
+ try (final BufferedReader fileReader = Files.newBufferedReader(path, charset)) {
+ return consumeReader(fileReader);
+ }
+ }
+
+ private static String readClassPathUri(
+ final URI uri,
+ final Charset charset)
+ throws IOException {
+ final String spec = uri.toString();
+ final String path = spec.substring("classpath:".length());
+ final List<URL> resources = new ArrayList<>(LoaderUtil.findResources(path));
+ if (resources.isEmpty()) {
+ final String message = String.format(
+ "could not locate classpath resource (path=%s)", path);
+ throw new RuntimeException(message);
+ }
+ final URL resource = resources.get(0);
+ if (resources.size() > 1) {
+ final String message = String.format(
+ "for URI %s found %d resources, using the first one: %s",
+ uri, resources.size(), resource);
+ LOGGER.warn(message);
+ }
+ try (final InputStream inputStream = resource.openStream()) {
+ try (final InputStreamReader reader = new InputStreamReader(inputStream, charset);
+ final BufferedReader bufferedReader = new BufferedReader(reader)) {
+ return consumeReader(bufferedReader);
+ }
+ }
+ }
+
+ private static String consumeReader(final BufferedReader reader) throws IOException {
+ final StringBuilder builder = new StringBuilder();
+ String line;
+ while ((line = reader.readLine()) != null) {
+ builder.append(line);
+ }
+ return builder.toString();
+ }
+
+}
diff --git a/log4j-layout-json-template/src/main/resources/EcsLayout.json b/log4j-layout-json-template/src/main/resources/EcsLayout.json
new file mode 100644
index 0000000..dee7a84
--- /dev/null
+++ b/log4j-layout-json-template/src/main/resources/EcsLayout.json
@@ -0,0 +1,46 @@
+{
+ "@timestamp": {
+ "$resolver": "timestamp",
+ "pattern": {
+ "format": "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'",
+ "timeZone": "UTC"
+ }
+ },
+ "log.level": {
+ "$resolver": "level",
+ "field": "name"
+ },
+ "message": {
+ "$resolver": "message",
+ "stringified": true
+ },
+ "process.thread.name": {
+ "$resolver": "thread",
+ "field": "name"
+ },
+ "log.logger": {
+ "$resolver": "logger",
+ "field": "name"
+ },
+ "labels": {
+ "$resolver": "mdc",
+ "flatten": true,
+ "stringified": true
+ },
+ "tags": {
+ "$resolver": "ndc"
+ },
+ "error.type": {
+ "$resolver": "exception",
+ "field": "className"
+ },
+ "error.message": {
+ "$resolver": "exception",
+ "field": "message"
+ },
+ "error.stack_trace": {
+ "$resolver": "exception",
+ "field": "stackTrace",
+ "stringified": true
+ }
+}
diff --git a/log4j-layout-json-template/src/main/resources/GelfLayout.json b/log4j-layout-json-template/src/main/resources/GelfLayout.json
new file mode 100644
index 0000000..dd43cc8
--- /dev/null
+++ b/log4j-layout-json-template/src/main/resources/GelfLayout.json
@@ -0,0 +1,41 @@
+{
+ "version": "1.1",
+ "host": "${hostName}",
+ "short_message": {
+ "$resolver": "message",
+ "stringified": true
+ },
+ "full_message": {
+ "$resolver": "exception",
+ "field": "stackTrace",
+ "stringified": true
+ },
+ "timestamp": {
+ "$resolver": "timestamp",
+ "epoch": {
+ "unit": "secs"
+ }
+ },
+ "level": {
+ "$resolver": "level",
+ "field": "severity",
+ "severity": {
+ "field": "code"
+ }
+ },
+ "_logger": {
+ "$resolver": "logger",
+ "field": "name"
+ },
+ "_thread": {
+ "$resolver": "thread",
+ "field": "name"
+ },
+ "_mdc": {
+ "$resolver": "mdc",
+ "flatten": {
+ "prefix": "_"
+ },
+ "stringified": true
+ }
+}
diff --git a/log4j-layout-json-template/src/main/resources/JsonLayout.json b/log4j-layout-json-template/src/main/resources/JsonLayout.json
new file mode 100644
index 0000000..503e2cd
--- /dev/null
+++ b/log4j-layout-json-template/src/main/resources/JsonLayout.json
@@ -0,0 +1,83 @@
+{
+ "instant": {
+ "epochSecond": {
+ "$resolver": "timestamp",
+ "epoch": {
+ "unit": "secs",
+ "rounded": true
+ }
+ },
+ "nanoOfSecond": {
+ "$resolver": "timestamp",
+ "epoch": {
+ "unit": "secs.nanos"
+ }
+ }
+ },
+ "thread": {
+ "$resolver": "thread",
+ "field": "name"
+ },
+ "level": {
+ "$resolver": "level",
+ "field": "name"
+ },
+ "loggerName": {
+ "$resolver": "logger",
+ "field": "name"
+ },
+ "message": {
+ "$resolver": "message",
+ "stringified": true
+ },
+ "thrown": {
+ "message": {
+ "$resolver": "exception",
+ "field": "message"
+ },
+ "name": {
+ "$resolver": "exception",
+ "field": "className"
+ },
+ "extendedStackTrace": {
+ "$resolver": "exception",
+ "field": "stackTrace"
+ }
+ },
+ "contextStack": {
+ "$resolver": "ndc"
+ },
+ "endOfBatch": {
+ "$resolver": "endOfBatch"
+ },
+ "loggerFqcn": {
+ "$resolver": "logger",
+ "field": "fqcn"
+ },
+ "threadId": {
+ "$resolver": "thread",
+ "field": "id"
+ },
+ "threadPriority": {
+ "$resolver": "thread",
+ "field": "priority"
+ },
+ "source": {
+ "class": {
+ "$resolver": "source",
+ "field": "className"
+ },
+ "method": {
+ "$resolver": "source",
+ "field": "methodName"
+ },
+ "file": {
+ "$resolver": "source",
+ "field": "fileName"
+ },
+ "line": {
+ "$resolver": "source",
+ "field": "lineNumber"
+ }
+ }
+}
diff --git a/log4j-layout-json-template/src/main/resources/LogstashJsonEventLayoutV1.json b/log4j-layout-json-template/src/main/resources/LogstashJsonEventLayoutV1.json
new file mode 100644
index 0000000..3225930
--- /dev/null
+++ b/log4j-layout-json-template/src/main/resources/LogstashJsonEventLayoutV1.json
@@ -0,0 +1,58 @@
+{
+ "mdc": {
+ "$resolver": "mdc"
+ },
+ "exception": {
+ "exception_class": {
+ "$resolver": "exception",
+ "field": "className"
+ },
+ "exception_message": {
+ "$resolver": "exception",
+ "field": "message",
+ "stringified": true
+ },
+ "stacktrace": {
+ "$resolver": "exception",
+ "field": "stackTrace",
+ "stringified": true
+ }
+ },
+ "line_number": {
+ "$resolver": "source",
+ "field": "lineNumber"
+ },
+ "class": {
+ "$resolver": "source",
+ "field": "className"
+ },
+ "@version": 1,
+ "source_host": "${hostName}",
+ "message": {
+ "$resolver": "message",
+ "stringified": true
+ },
+ "thread_name": {
+ "$resolver": "thread",
+ "field": "name"
+ },
+ "@timestamp": {
+ "$resolver": "timestamp"
+ },
+ "level": {
+ "$resolver": "level",
+ "field": "name"
+ },
+ "file": {
+ "$resolver": "source",
+ "field": "fileName"
+ },
+ "method": {
+ "$resolver": "source",
+ "field": "methodName"
+ },
+ "logger_name": {
+ "$resolver": "logger",
+ "field": "name"
+ }
+}
diff --git a/log4j-layout-json-template/src/main/resources/StackTraceElementLayout.json b/log4j-layout-json-template/src/main/resources/StackTraceElementLayout.json
new file mode 100644
index 0000000..218a01a
--- /dev/null
+++ b/log4j-layout-json-template/src/main/resources/StackTraceElementLayout.json
@@ -0,0 +1,18 @@
+{
+ "class": {
+ "$resolver": "stackTraceElement",
+ "field": "className"
+ },
+ "method": {
+ "$resolver": "stackTraceElement",
+ "field": "methodName"
+ },
+ "file": {
+ "$resolver": "stackTraceElement",
+ "field": "fileName"
+ },
+ "line": {
+ "$resolver": "stackTraceElement",
+ "field": "lineNumber"
+ }
+}
diff --git a/log4j-layout-json-template/src/site/manual/index.md b/log4j-layout-json-template/src/site/manual/index.md
new file mode 100644
index 0000000..b8cb6e3
--- /dev/null
+++ b/log4j-layout-json-template/src/site/manual/index.md
@@ -0,0 +1,32 @@
+<!-- vim: set syn=markdown : -->
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+# Apache Log4j JSON Template Layout module
+
+This module provides a customizable and efficient JSON layout.
+
+## Requirements
+
+This module was introduced in Log4j 3.0.0 and requires Jackson.
+
+Some features may require optional [dependencies](../runtime-dependencies.html).
+These dependencies are specified in the documentation for those features.
+
+Some Log4j features require external dependencies. See the
+[Dependency Tree](dependencies.html#Dependency_Tree) for the exact list of JAR
+files needed for these features.
diff --git a/log4j-layout-json-template/src/site/site.xml b/log4j-layout-json-template/src/site/site.xml
new file mode 100644
index 0000000..962392e
--- /dev/null
+++ b/log4j-layout-json-template/src/site/site.xml
@@ -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.
+-->
+<project name="Log4j Core"
+ xmlns="http://maven.apache.org/DECORATION/1.4.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/DECORATION/1.4.0 http://maven.apache.org/xsd/decoration-1.4.0.xsd">
+
+ <body>
+
+ <links>
+ <item name="Apache" href="http://www.apache.org/" />
+ <item name="Logging Services" href="http://logging.apache.org/"/>
+ <item name="Log4j" href="../index.html"/>
+ </links>
+
+ <!-- Component-specific reports -->
+ <menu ref="reports"/>
+
+ <!-- Overall Project Info -->
+ <menu name="Log4j Project Information" img="icon-info-sign">
+ <item name="Dependencies" href="../dependencies.html" />
+ <item name="Dependency Convergence" href="../dependency-convergence.html" />
+ <item name="Dependency Management" href="../dependency-management.html" />
+ <item name="Project Team" href="../team-list.html" />
+ <item name="Mailing Lists" href="../mail-lists.html" />
+ <item name="Issue Tracking" href="../issue-tracking.html" />
+ <item name="Project License" href="../license.html" />
+ <item name="Source Repository" href="../source-repository.html" />
+ <item name="Project Summary" href="../project-summary.html" />
+ </menu>
+
+ <menu name="Log4j Project Reports" img="icon-cog">
+ <item name="Changes Report" href="../changes-report.html" />
+ <item name="JIRA Report" href="../jira-report.html" />
+ <item name="Surefire Report" href="../surefire-report.html" />
+ <item name="RAT Report" href="../rat-report.html" />
+ </menu>
+
+ </body>
+
+</project>
diff --git a/log4j-layout-json-template/src/test/java/org/apache/logging/log4j/layout/json/template/BlackHoleByteBufferDestination.java b/log4j-layout-json-template/src/test/java/org/apache/logging/log4j/layout/json/template/BlackHoleByteBufferDestination.java
new file mode 100644
index 0000000..01b3d4a
--- /dev/null
+++ b/log4j-layout-json-template/src/test/java/org/apache/logging/log4j/layout/json/template/BlackHoleByteBufferDestination.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.layout.json.template;
+
+import org.apache.logging.log4j.core.layout.ByteBufferDestination;
+
+import java.nio.ByteBuffer;
+
+class BlackHoleByteBufferDestination implements ByteBufferDestination {
+
+ private final ByteBuffer byteBuffer;
+
+ BlackHoleByteBufferDestination(final int maxByteCount) {
+ this.byteBuffer = ByteBuffer.allocate(maxByteCount);
+ }
+
+ @Override
+ public ByteBuffer getByteBuffer() {
+ return byteBuffer;
+ }
+
+ @Override
+ public ByteBuffer drain(final ByteBuffer byteBuffer) {
+ byteBuffer.clear();
+ return byteBuffer;
+ }
+
+ @Override
+ public void writeBytes(final ByteBuffer byteBuffer) {
+ byteBuffer.clear();
+ }
+
+ @Override
+ public void writeBytes(final byte[] buffer, final int offset, final int length) {}
+
+}
diff --git a/log4j-layout-json-template/src/test/java/org/apache/logging/log4j/layout/json/template/EcsLayoutTest.java b/log4j-layout-json-template/src/test/java/org/apache/logging/log4j/layout/json/template/EcsLayoutTest.java
new file mode 100644
index 0000000..8f5593d
--- /dev/null
+++ b/log4j-layout-json-template/src/test/java/org/apache/logging/log4j/layout/json/template/EcsLayoutTest.java
@@ -0,0 +1,90 @@
+package org.apache.logging.log4j.layout.json.template;
+
+import co.elastic.logging.log4j2.EcsLayout;
+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.layout.json.template.JsonTemplateLayout.EventTemplateAdditionalField;
+import org.assertj.core.api.Assertions;
+import org.junit.Test;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import static org.apache.logging.log4j.layout.json.template.LayoutComparisonHelpers.renderUsing;
+
+public class EcsLayoutTest {
+
+ private static final Configuration CONFIGURATION = new DefaultConfiguration();
+
+ private static final String SERVICE_NAME = "test";
+
+ private static final String EVENT_DATASET = SERVICE_NAME + ".log";
+
+ private static final JsonTemplateLayout JSON_TEMPLATE_LAYOUT = JsonTemplateLayout
+ .newBuilder()
+ .setConfiguration(CONFIGURATION)
+ .setEventTemplateUri("classpath:EcsLayout.json")
+ .setEventTemplateAdditionalFields(
+ JsonTemplateLayout
+ .EventTemplateAdditionalFields
+ .newBuilder()
+ .setAdditionalFields(
+ new EventTemplateAdditionalField[]{
+ EventTemplateAdditionalField
+ .newBuilder()
+ .setKey("service.name")
+ .setValue(SERVICE_NAME)
+ .build(),
+ EventTemplateAdditionalField
+ .newBuilder()
+ .setKey("event.dataset")
+ .setValue(EVENT_DATASET)
+ .build()
+ })
+ .build())
+ .build();
+
+ private static final EcsLayout ECS_LAYOUT = EcsLayout
+ .newBuilder()
+ .setConfiguration(CONFIGURATION)
+ .setServiceName(SERVICE_NAME)
+ .setEventDataset(EVENT_DATASET)
+ .build();
+
+ @Test
+ public void test_lite_log_events() {
+ final List<LogEvent> logEvents = LogEventFixture.createLiteLogEvents(1_000);
+ test(logEvents);
+ }
+
+ @Test
+ public void test_full_log_events() {
+ final List<LogEvent> logEvents = LogEventFixture.createFullLogEvents(1_000);
+ test(logEvents);
+ }
+
+ private static void test(final Collection<LogEvent> logEvents) {
+ for (final LogEvent logEvent : logEvents) {
+ test(logEvent);
+ }
+ }
+
+ private static void test(final LogEvent logEvent) {
+ final Map<String, Object> jsonTemplateLayoutMap = renderUsingJsonTemplateLayout(logEvent);
+ final Map<String, Object> ecsLayoutMap = renderUsingEcsLayout(logEvent);
+ Assertions.assertThat(jsonTemplateLayoutMap).isEqualTo(ecsLayoutMap);
+ }
+
+ private static Map<String, Object> renderUsingJsonTemplateLayout(
+ final LogEvent logEvent) {
+ return renderUsing(logEvent, JSON_TEMPLATE_LAYOUT);
+ }
+
+ private static Map<String, Object> renderUsingEcsLayout(
+ final LogEvent logEvent) {
+ return renderUsing(logEvent, ECS_LAYOUT);
+ }
+
+}
diff --git a/log4j-layout-json-template/src/test/java/org/apache/logging/log4j/layout/json/template/GelfLayoutTest.java b/log4j-layout-json-template/src/test/java/org/apache/logging/log4j/layout/json/template/GelfLayoutTest.java
new file mode 100644
index 0000000..795546b
--- /dev/null
+++ b/log4j-layout-json-template/src/test/java/org/apache/logging/log4j/layout/json/template/GelfLayoutTest.java
@@ -0,0 +1,109 @@
+package org.apache.logging.log4j.layout.json.template;
+
+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.layout.GelfLayout;
+import org.apache.logging.log4j.core.time.Instant;
+import org.apache.logging.log4j.layout.json.template.JsonTemplateLayout.EventTemplateAdditionalField;
+import org.assertj.core.api.Assertions;
+import org.junit.Test;
+
+import java.math.BigDecimal;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import static org.apache.logging.log4j.layout.json.template.LayoutComparisonHelpers.renderUsing;
+
+public class GelfLayoutTest {
+
+ private static final Configuration CONFIGURATION = new DefaultConfiguration();
+
+ private static final String HOST_NAME = "localhost";
+
+ private static final JsonTemplateLayout JSON_TEMPLATE_LAYOUT = JsonTemplateLayout
+ .newBuilder()
+ .setConfiguration(CONFIGURATION)
+ .setEventTemplateUri("classpath:GelfLayout.json")
+ .setEventTemplateAdditionalFields(
+ JsonTemplateLayout
+ .EventTemplateAdditionalFields
+ .newBuilder()
+ .setAdditionalFields(
+ new EventTemplateAdditionalField[]{
+ EventTemplateAdditionalField
+ .newBuilder()
+ .setKey("host")
+ .setValue(HOST_NAME)
+ .build()
+ })
+ .build())
+ .build();
+
+ private static final GelfLayout GELF_LAYOUT = GelfLayout
+ .newBuilder()
+ .setConfiguration(CONFIGURATION)
+ .setHost(HOST_NAME)
+ .setCompressionType(GelfLayout.CompressionType.OFF)
+ .build();
+
+ @Test
+ public void test_lite_log_events() {
+ final List<LogEvent> logEvents = LogEventFixture.createLiteLogEvents(1_000);
+ test(logEvents);
+ }
+
+ @Test
+ public void test_full_log_events() {
+ final List<LogEvent> logEvents = LogEventFixture.createFullLogEvents(1_000);
+ test(logEvents);
+ }
+
+ private static void test(final Collection<LogEvent> logEvents) {
+ for (final LogEvent logEvent : logEvents) {
+ test(logEvent);
+ }
+ }
+
+ private static void test(final LogEvent logEvent) {
+ final Map<String, Object> jsonTemplateLayoutMap = renderUsingJsonTemplateLayout(logEvent);
+ final Map<String, Object> gelfLayoutMap = renderUsingGelfLayout(logEvent);
+ verifyTimestamp(logEvent.getInstant(), jsonTemplateLayoutMap, gelfLayoutMap);
+ Assertions.assertThat(jsonTemplateLayoutMap).isEqualTo(gelfLayoutMap);
+ }
+
+ private static Map<String, Object> renderUsingJsonTemplateLayout(
+ final LogEvent logEvent) {
+ return renderUsing(logEvent, JSON_TEMPLATE_LAYOUT);
+ }
+
+ private static Map<String, Object> renderUsingGelfLayout(
+ final LogEvent logEvent) {
+ return renderUsing(logEvent, GELF_LAYOUT);
+ }
+
+ /**
+ * Handle timestamps individually to avoid floating-point comparison hiccups.
+ */
+ private static void verifyTimestamp(
+ final Instant logEventInstant,
+ final Map<String, Object> jsonTemplateLayoutMap,
+ final Map<String, Object> gelfLayoutMap) {
+ final BigDecimal jsonTemplateLayoutTimestamp =
+ (BigDecimal) jsonTemplateLayoutMap.remove("timestamp");
+ final BigDecimal gelfLayoutTimestamp =
+ (BigDecimal) gelfLayoutMap.remove("timestamp");
+ final String description = String.format(
+ "instantEpochSecs=%d.%d, jsonTemplateLayoutTimestamp=%s, gelfLayoutTimestamp=%s",
+ logEventInstant.getEpochSecond(),
+ logEventInstant.getNanoOfSecond(),
+ jsonTemplateLayoutTimestamp,
+ gelfLayoutTimestamp);
+ Assertions
+ .assertThat(jsonTemplateLayoutTimestamp.compareTo(gelfLayoutTimestamp))
+ .as(description)
+ .isEqualTo(0);
+ }
+
+}
diff --git a/log4j-layout-json-template/src/test/java/org/apache/logging/log4j/layout/json/template/JacksonFixture.java b/log4j-layout-json-template/src/test/java/org/apache/logging/log4j/layout/json/template/JacksonFixture.java
new file mode 100644
index 0000000..a2ebe6f
--- /dev/null
+++ b/log4j-layout-json-template/src/test/java/org/apache/logging/log4j/layout/json/template/JacksonFixture.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.layout.json.template;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+public enum JacksonFixture {;
+
+ private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
+
+ public static ObjectMapper getObjectMapper() {
+ return OBJECT_MAPPER;
+ }
+
+}
diff --git a/log4j-layout-json-template/src/test/java/org/apache/logging/log4j/layout/json/template/JsonLayoutTest.java b/log4j-layout-json-template/src/test/java/org/apache/logging/log4j/layout/json/template/JsonLayoutTest.java
new file mode 100644
index 0000000..6996ea5
--- /dev/null
+++ b/log4j-layout-json-template/src/test/java/org/apache/logging/log4j/layout/json/template/JsonLayoutTest.java
@@ -0,0 +1,71 @@
+package org.apache.logging.log4j.layout.json.template;
+
+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.jackson.json.layout.JsonLayout;
+import org.assertj.core.api.Assertions;
+import org.junit.Test;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import static org.apache.logging.log4j.layout.json.template.LayoutComparisonHelpers.renderUsing;
+
+public class JsonLayoutTest {
+
+ private static final Configuration CONFIGURATION = new DefaultConfiguration();
+
+ private static final JsonTemplateLayout JSON_TEMPLATE_LAYOUT = JsonTemplateLayout
+ .newBuilder()
+ .setConfiguration(CONFIGURATION)
+ .setEventTemplateUri("classpath:JsonLayout.json")
+ .build();
+
+ private static final JsonLayout JSON_LAYOUT = JsonLayout
+ .newBuilder()
+ .setConfiguration(CONFIGURATION)
+ .build();
+
+ @Test
+ public void test_lite_log_events() {
+ final List<LogEvent> logEvents = LogEventFixture.createLiteLogEvents(1_000);
+ test(logEvents);
+ }
+
+ @Test
+ public void test_full_log_events() {
+ final List<LogEvent> logEvents = LogEventFixture.createFullLogEvents(1_000);
+ test(logEvents);
+ }
+
+ private static void test(final Collection<LogEvent> logEvents) {
+ for (final LogEvent logEvent : logEvents) {
+ test(logEvent);
+ }
+ }
+
+ private static void test(final LogEvent logEvent) {
+ final Map<String, Object> jsonTemplateLayoutMap = renderUsingJsonTemplateLayout(logEvent);
+ final Map<String, Object> jsonLayoutMap = renderUsingJsonLayout(logEvent);
+ // JsonLayout blindly serializes the Throwable as a POJO, this is,
+ // to say the least, quite wrong, and I ain't gonna try to emulate
+ // this behaviour in JsonTemplateLayout. Hence, discarding the "thrown"
+ // field.
+ jsonTemplateLayoutMap.remove("thrown");
+ jsonLayoutMap.remove("thrown");
+ Assertions.assertThat(jsonTemplateLayoutMap).isEqualTo(jsonLayoutMap);
+ }
+
+ private static Map<String, Object> renderUsingJsonTemplateLayout(
+ final LogEvent logEvent) {
+ return renderUsing(logEvent, JSON_TEMPLATE_LAYOUT);
+ }
+
+ private static Map<String, Object> renderUsingJsonLayout(
+ final LogEvent logEvent) {
+ return renderUsing(logEvent, JSON_LAYOUT);
+ }
+
+}
diff --git a/log4j-layout-json-template/src/test/java/org/apache/logging/log4j/layout/json/template/JsonTemplateLayoutConcurrentEncodeTest.java b/log4j-layout-json-template/src/test/java/org/apache/logging/log4j/layout/json/template/JsonTemplateLayoutConcurrentEncodeTest.java
new file mode 100644
index 0000000..d17a8be
--- /dev/null
+++ b/log4j-layout-json-template/src/test/java/org/apache/logging/log4j/layout/json/template/JsonTemplateLayoutConcurrentEncodeTest.java
@@ -0,0 +1,192 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.logging.log4j.layout.json.template;
+
+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.layout.ByteBufferDestination;
+import org.assertj.core.api.Assertions;
+import org.junit.Test;
+
+import java.nio.ByteBuffer;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+public class JsonTemplateLayoutConcurrentEncodeTest {
+
+ private static class ConcurrentAccessError extends RuntimeException {
+
+ public static final long serialVersionUID = 0;
+
+ private ConcurrentAccessError(final int concurrentAccessCount) {
+ super("concurrentAccessCount=" + concurrentAccessCount);
+ }
+
+ }
+
+ private static class ConcurrentAccessDetectingByteBufferDestination
+ extends BlackHoleByteBufferDestination {
+
+ private final AtomicInteger concurrentAccessCounter = new AtomicInteger(0);
+
+ ConcurrentAccessDetectingByteBufferDestination() {
+ super(2_000);
+ }
+
+ @Override
+ public ByteBuffer getByteBuffer() {
+ final int concurrentAccessCount = concurrentAccessCounter.incrementAndGet();
+ if (concurrentAccessCount > 1) {
+ throw new ConcurrentAccessError(concurrentAccessCount);
+ }
+ try {
+ return super.getByteBuffer();
+ } finally {
+ concurrentAccessCounter.decrementAndGet();
+ }
+ }
+
+ @Override
+ public ByteBuffer drain(final ByteBuffer byteBuffer) {
+ final int concurrentAccessCount = concurrentAccessCounter.incrementAndGet();
+ if (concurrentAccessCount > 1) {
+ throw new ConcurrentAccessError(concurrentAccessCount);
+ }
+ try {
+ return super.drain(byteBuffer);
+ } finally {
+ concurrentAccessCounter.decrementAndGet();
+ }
+ }
+
+ @Override
+ public void writeBytes(final ByteBuffer byteBuffer) {
+ final int concurrentAccessCount = concurrentAccessCounter.incrementAndGet();
+ if (concurrentAccessCount > 1) {
+ throw new ConcurrentAccessError(concurrentAccessCount);
+ }
+ try {
+ super.writeBytes(byteBuffer);
+ } finally {
+ concurrentAccessCounter.decrementAndGet();
+ }
+ }
+
+ @Override
+ public void writeBytes(final byte[] buffer, final int offset, final int length) {
+ int concurrentAccessCount = concurrentAccessCounter.incrementAndGet();
+ if (concurrentAccessCount > 1) {
+ throw new ConcurrentAccessError(concurrentAccessCount);
+ }
+ try {
+ super.writeBytes(buffer, offset, length);
+ } finally {
+ concurrentAccessCounter.decrementAndGet();
+ }
+ }
+
+ }
+
+ private static final LogEvent[] LOG_EVENTS = createMessages();
+
+ private static LogEvent[] createMessages() {
+ final int messageCount = 1_000;
+ final LogEvent[] logEvents = new LogEvent[messageCount];
+ LogEventFixture
+ .createLiteLogEvents(messageCount)
+ .toArray(logEvents);
+ return logEvents;
+ }
+
+ @Test
+ public void test_concurrent_encode() {
+ final AtomicReference<Exception> encodeFailureRef = new AtomicReference<>(null);
+ produce(encodeFailureRef);
+ Assertions.assertThat(encodeFailureRef.get()).isNull();
+ }
+
+ private void produce(final AtomicReference<Exception> encodeFailureRef) {
+ final int threadCount = 10;
+ final JsonTemplateLayout layout = createLayout();
+ final ByteBufferDestination destination =
+ new ConcurrentAccessDetectingByteBufferDestination();
+ final AtomicLong encodeCounter = new AtomicLong(0);
+ final List<Thread> workers = IntStream
+ .range(0, threadCount)
+ .mapToObj((final int threadIndex) ->
+ createWorker(
+ layout,
+ destination,
+ encodeFailureRef,
+ encodeCounter,
+ threadIndex))
+ .collect(Collectors.toList());
+ workers.forEach(Thread::start);
+ workers.forEach((final Thread worker) -> {
+ try {
+ worker.join();
+ } catch (final InterruptedException ignored) {
+ System.err.format("join to %s interrupted%n", worker.getName());
+ }
+ });
+ }
+
+ private static JsonTemplateLayout createLayout() {
+ final Configuration config = new DefaultConfiguration();
+ return JsonTemplateLayout
+ .newBuilder()
+ .setConfiguration(config)
+ .setEventTemplate("{\"message\": \"${json:message}\"}")
+ .setStackTraceEnabled(false)
+ .setLocationInfoEnabled(false)
+ .build();
+ }
+
+ private Thread createWorker(
+ final JsonTemplateLayout layout,
+ final ByteBufferDestination destination,
+ final AtomicReference<Exception> encodeFailureRef,
+ final AtomicLong encodeCounter,
+ final int threadIndex) {
+ final int maxEncodeCount = 1_000;
+ final String threadName = String.format("Worker-%d", threadIndex);
+ return new Thread(
+ () -> {
+ try {
+ for (int logEventIndex = threadIndex % LOG_EVENTS.length;
+ encodeFailureRef.get() == null && encodeCounter.incrementAndGet() < maxEncodeCount;
+ logEventIndex = (logEventIndex + 1) % LOG_EVENTS.length) {
+ final LogEvent logEvent = LOG_EVENTS[logEventIndex];
+ layout.encode(logEvent, destination);
+ }
+ } catch (final Exception error) {
+ final boolean succeeded = encodeFailureRef.compareAndSet(null, error);
+ if (succeeded) {
+ System.err.format("%s failed%n", threadName);
+ error.printStackTrace(System.err);
+ }
+ }
+ },
+ threadName);
+ }
+
+}
diff --git a/log4j-layout-json-template/src/test/java/org/apache/logging/log4j/layout/json/template/JsonTemplateLayoutGcFreeTest.java b/log4j-layout-json-template/src/test/java/org/apache/logging/log4j/layout/json/template/JsonTemplateLayoutGcFreeTest.java
new file mode 100644
index 0000000..dfe097a
--- /dev/null
+++ b/log4j-layout-json-template/src/test/java/org/apache/logging/log4j/layout/json/template/JsonTemplateLayoutGcFreeTest.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.layout.json.template;
+
+import org.apache.logging.log4j.core.GcFreeLoggingTestUtil;
+import org.junit.Test;
+
+public class JsonTemplateLayoutGcFreeTest {
+
+ @Test
+ public void test_no_allocation_during_steady_state_logging() throws Exception {
+ 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("log4j.layout.jsonTemplate.recyclerFactory", "threadLocal");
+ System.setProperty("log4j2.garbagefree.threadContextMap", "true");
+ GcFreeLoggingTestUtil.executeLogging(
+ "gcFreeJsonTemplateLayoutLogging.xml",
+ JsonTemplateLayoutGcFreeTest.class);
+ }
+
+}
diff --git a/log4j-layout-json-template/src/test/java/org/apache/logging/log4j/layout/json/template/JsonTemplateLayoutNullEventDelimiterTest.java b/log4j-layout-json-template/src/test/java/org/apache/logging/log4j/layout/json/template/JsonTemplateLayoutNullEventDelimiterTest.java
new file mode 100644
index 0000000..2487b65
--- /dev/null
+++ b/log4j-layout-json-template/src/test/java/org/apache/logging/log4j/layout/json/template/JsonTemplateLayoutNullEventDelimiterTest.java
@@ -0,0 +1,127 @@
+package org.apache.logging.log4j.layout.json.template;
+
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.assertj.core.api.Assertions;
+import org.awaitility.Awaitility;
+import org.junit.Test;
+
+import java.io.ByteArrayOutputStream;
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.time.Duration;
+
+public class JsonTemplateLayoutNullEventDelimiterTest {
+
+ // Set the configuration.
+ static {
+ System.setProperty(
+ "log4j.configurationFile",
+ "nullEventDelimitedJsonTemplateLayoutLogging.xml");
+ }
+
+ // Note that this port is hardcoded in the configuration file too!
+ private static final int PORT = 50514;
+
+ @Test
+ public void test() throws Exception {
+
+ // Set the expected bytes.
+ final byte[] expectedBytes = {
+ '"', 'f', 'o', 'o', '"', '\0',
+ '"', 'b', 'a', 'r', '"', '\0'
+ };
+
+ // Start the TCP server.
+ try (final TcpServer server = new TcpServer(PORT)) {
+
+ // Produce log events.
+ final Logger logger = LogManager.getLogger(JsonTemplateLayoutNullEventDelimiterTest.class);
+ logger.log(Level.INFO, "foo");
+ logger.log(Level.INFO, "bar");
+
+ // Wait for the log events.
+ Awaitility
+ .await()
+ .atMost(Duration.ofSeconds(10))
+ .pollDelay(Duration.ofSeconds(2))
+ .until(() -> server.getTotalReadByteCount() >= expectedBytes.length);
+
+ // Verify the received log events.
+ final byte[] actualBytes = server.getReceivedBytes();
+ Assertions.assertThat(actualBytes).startsWith(expectedBytes);
+
+ }
+
+ }
+
+ private static final class TcpServer extends Thread implements AutoCloseable {
+
+ private final ServerSocket serverSocket;
+
+ private final ByteArrayOutputStream outputStream;
+
+ private volatile int totalReadByteCount = 0;
+
+ private volatile boolean closed = false;
+
+ private TcpServer(final int port) throws IOException {
+ this.serverSocket = new ServerSocket(port);
+ this.outputStream = new ByteArrayOutputStream();
+ serverSocket.setReuseAddress(true);
+ serverSocket.setSoTimeout(5_000);
+ setDaemon(true);
+ start();
+ }
+
+ @Override
+ public void run() {
+ try {
+ try (final Socket socket = serverSocket.accept()) {
+ final InputStream inputStream = socket.getInputStream();
+ final byte[] buffer = new byte[1024];
+ // noinspection InfiniteLoopStatement
+ while (true) {
+ final int readByteCount = inputStream.read(buffer);
+ if (readByteCount > 0) {
+ synchronized (this) {
+ totalReadByteCount += readByteCount;
+ outputStream.write(buffer, 0, readByteCount);
+ }
+ }
+ }
+ }
+ } catch (final EOFException ignored) {
+ // Socket is closed.
+ } catch (final Exception error) {
+ if (!closed) {
+ throw new RuntimeException(error);
+ }
+ }
+ }
+
+ public synchronized byte[] getReceivedBytes() {
+ return outputStream.toByteArray();
+ }
+
+ public synchronized int getTotalReadByteCount() {
+ return totalReadByteCount;
+ }
+
+ @Override
+ public synchronized void close() throws InterruptedException {
+ if (closed) {
+ throw new IllegalStateException("shutdown has already been invoked");
+ }
+ closed = true;
+ interrupt();
+ join(3_000L);
+ }
+
+ }
+
+}
diff --git a/log4j-layout-json-template/src/test/java/org/apache/logging/log4j/layout/json/template/JsonTemplateLayoutTest.java b/log4j-layout-json-template/src/test/java/org/apache/logging/log4j/layout/json/template/JsonTemplateLayoutTest.java
new file mode 100644
index 0000000..80cd2b9
--- /dev/null
+++ b/log4j-layout-json-template/src/test/java/org/apache/logging/log4j/layout/json/template/JsonTemplateLayoutTest.java
@@ -0,0 +1,1889 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.logging.log4j.layout.json.template;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.MappingIterator;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.Marker;
+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.appender.SocketAppender;
+import org.apache.logging.log4j.core.config.Configuration;
+import org.apache.logging.log4j.core.config.DefaultConfiguration;
+import org.apache.logging.log4j.core.config.builder.api.ConfigurationBuilderFactory;
+import org.apache.logging.log4j.core.impl.Log4jLogEvent;
+import org.apache.logging.log4j.core.layout.ByteBufferDestination;
+import org.apache.logging.log4j.core.lookup.MainMapLookup;
+import org.apache.logging.log4j.core.net.Severity;
+import org.apache.logging.log4j.core.time.MutableInstant;
+import org.apache.logging.log4j.layout.json.template.JsonTemplateLayout.EventTemplateAdditionalField;
+import org.apache.logging.log4j.layout.json.template.JsonTemplateLayout.EventTemplateAdditionalFields;
+import org.apache.logging.log4j.layout.json.template.util.JsonReader;
+import org.apache.logging.log4j.layout.json.template.util.JsonWriter;
+import org.apache.logging.log4j.layout.json.template.util.MapAccessor;
+import org.apache.logging.log4j.message.Message;
+import org.apache.logging.log4j.message.ObjectMessage;
+import org.apache.logging.log4j.message.SimpleMessage;
+import org.apache.logging.log4j.message.StringMapMessage;
+import org.apache.logging.log4j.test.AvailablePortFinder;
+import org.apache.logging.log4j.util.SortedArrayStringMap;
+import org.apache.logging.log4j.util.StringMap;
+import org.apache.logging.log4j.util.Strings;
+import org.assertj.core.api.Assertions;
+import org.junit.Test;
+
+import java.io.ByteArrayOutputStream;
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintStream;
+import java.io.UnsupportedEncodingException;
+import java.math.BigDecimal;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
+import java.text.SimpleDateFormat;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ArrayBlockingQueue;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+@SuppressWarnings("DoubleBraceInitialization")
+public class JsonTemplateLayoutTest {
+
+ private static final Configuration CONFIGURATION = new DefaultConfiguration();
+
+ private static final List<LogEvent> LOG_EVENTS = LogEventFixture.createFullLogEvents(5);
+
+ private static final JsonWriter JSON_WRITER = JsonWriter
+ .newBuilder()
+ .setMaxStringLength(10_000)
+ .setTruncatedStringSuffix("…")
+ .build();
+
+ private static final ObjectMapper OBJECT_MAPPER = JacksonFixture.getObjectMapper();
+
+ private static final String LOGGER_NAME = JsonTemplateLayoutTest.class.getSimpleName();
+
+ @Test
+ public void test_serialized_event() throws IOException {
+ final String lookupTestKey = "lookup_test_key";
+ final String lookupTestVal =
+ String.format("lookup_test_value_%d", (int) (1000 * Math.random()));
+ System.setProperty(lookupTestKey, lookupTestVal);
+ for (final LogEvent logEvent : LOG_EVENTS) {
+ checkLogEvent(logEvent, lookupTestKey, lookupTestVal);
+ }
+ }
+
+ private void checkLogEvent(
+ final LogEvent logEvent,
+ @SuppressWarnings("SameParameterValue")
+ final String lookupTestKey,
+ final String lookupTestVal) throws IOException {
+ final JsonTemplateLayout layout = JsonTemplateLayout
+ .newBuilder()
+ .setConfiguration(CONFIGURATION)
+ .setEventTemplateUri("classpath:testJsonTemplateLayout.json")
+ .setStackTraceEnabled(true)
+ .setLocationInfoEnabled(true)
+ .build();
+ final String serializedLogEvent = layout.toSerializable(logEvent);
+ final JsonNode rootNode = OBJECT_MAPPER.readValue(serializedLogEvent, JsonNode.class);
+ checkConstants(rootNode);
+ checkBasicFields(logEvent, rootNode);
+ checkSource(logEvent, rootNode);
+ checkException(layout.getCharset(), logEvent, rootNode);
+ checkLookupTest(lookupTestKey, lookupTestVal, rootNode);
+ }
+
+ private static void checkConstants(final JsonNode rootNode) {
+ assertThat(point(rootNode, "@version").asInt()).isEqualTo(1);
+ }
+
+ private static void checkBasicFields(final LogEvent logEvent, final JsonNode rootNode) {
+ assertThat(point(rootNode, "message").asText())
+ .isEqualTo(logEvent.getMessage().getFormattedMessage());
+ assertThat(point(rootNode, "level").asText())
+ .isEqualTo(logEvent.getLevel().name());
+ assertThat(point(rootNode, "logger_fqcn").asText())
+ .isEqualTo(logEvent.getLoggerFqcn());
+ assertThat(point(rootNode, "logger_name").asText())
+ .isEqualTo(logEvent.getLoggerName());
+ assertThat(point(rootNode, "thread_id").asLong())
+ .isEqualTo(logEvent.getThreadId());
+ assertThat(point(rootNode, "thread_name").asText())
+ .isEqualTo(logEvent.getThreadName());
+ assertThat(point(rootNode, "thread_priority").asInt())
+ .isEqualTo(logEvent.getThreadPriority());
+ assertThat(point(rootNode, "end_of_batch").asBoolean())
+ .isEqualTo(logEvent.isEndOfBatch());
+ }
+
+ private static void checkSource(final LogEvent logEvent, final JsonNode rootNode) {
+ assertThat(point(rootNode, "class").asText()).isEqualTo(logEvent.getSource().getClassName());
+ assertThat(point(rootNode, "file").asText()).isEqualTo(logEvent.getSource().getFileName());
+ assertThat(point(rootNode, "line_number").asInt()).isEqualTo(logEvent.getSource().getLineNumber());
+ }
+
+ private static void checkException(
+ final Charset charset,
+ final LogEvent logEvent,
+ final JsonNode rootNode) {
+ final Throwable thrown = logEvent.getThrown();
+ if (thrown != null) {
+ assertThat(point(rootNode, "exception_class").asText()).isEqualTo(thrown.getClass().getCanonicalName());
+ assertThat(point(rootNode, "exception_message").asText()).isEqualTo(thrown.getMessage());
+ final String stackTrace = serializeStackTrace(charset, thrown);
+ assertThat(point(rootNode, "stacktrace").asText()).isEqualTo(stackTrace);
+ }
+ }
+
+ private static String serializeStackTrace(
+ final Charset charset,
+ final Throwable exception) {
+ final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ final String charsetName = charset.name();
+ try (final PrintStream printStream =
+ new PrintStream(outputStream, false, charsetName)) {
+ exception.printStackTrace(printStream);
+ return outputStream.toString(charsetName);
+ } catch (final UnsupportedEncodingException error) {
+ throw new RuntimeException("failed converting the stack trace to string", error);
+ }
+ }
+
+ private static void checkLookupTest(
+ final String lookupTestKey,
+ final String lookupTestVal,
+ final JsonNode rootNode) {
+ assertThat(point(rootNode, lookupTestKey).asText()).isEqualTo(lookupTestVal);
+ }
+
+ private static JsonNode point(final JsonNode node, final Object... fields) {
+ final String pointer = createJsonPointer(fields);
+ return node.at(pointer);
+ }
+
+ private static String createJsonPointer(final Object... fields) {
+ final StringBuilder jsonPathBuilder = new StringBuilder();
+ for (final Object field : fields) {
+ jsonPathBuilder.append("/").append(field);
+ }
+ return jsonPathBuilder.toString();
+ }
+
+ @Test
+ public void test_inline_template() throws Exception {
+
+ // Create the log event.
+ final SimpleMessage message = new SimpleMessage("Hello, World");
+ final String timestamp = "2017-09-28T17:13:29.098+02:00";
+ final long timeMillis = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSXXX")
+ .parse(timestamp)
+ .getTime();
+ final LogEvent logEvent = Log4jLogEvent
+ .newBuilder()
+ .setLoggerName(LOGGER_NAME)
+ .setLevel(Level.INFO)
+ .setMessage(message)
+ .setTimeMillis(timeMillis)
+ .build();
+
+ // Create the event template.
+ final String timestampFieldName = "@timestamp";
+ final String staticFieldName = "staticFieldName";
+ final String staticFieldValue = "staticFieldValue";
+ final String eventTemplate = writeJson(Map(
+ timestampFieldName, Map(
+ "$resolver", "timestamp",
+ "pattern", Map("timeZone", "Europe/Amsterdam")),
+ staticFieldName, staticFieldValue));
+
+ // Create the layout.
+ final JsonTemplateLayout layout = JsonTemplateLayout
+ .newBuilder()
+ .setConfiguration(CONFIGURATION)
+ .setEventTemplate(eventTemplate)
+ .build();
+
+ // Check the serialized event.
+ usingSerializedLogEventAccessor(layout, logEvent, accessor -> {
+ assertThat(accessor.getString(timestampFieldName)).isEqualTo(timestamp);
+ assertThat(accessor.getString(staticFieldName)).isEqualTo(staticFieldValue);
+ });
+
+ }
+
+ @Test
+ public void test_log4j_deferred_runtime_resolver_for_MapMessage() {
+
+ // Create the event template.
+ final String eventTemplate = writeJson(Map(
+ "mapValue3", Map("$resolver", "message"),
+ "mapValue1", "${map:key1}",
+ "mapValue2", "${map:key2}",
+ "nestedLookupEmptyValue", "${map:noExist:-${map:noExist2:-${map:noExist3:-}}}",
+ "nestedLookupStaticValue", "${map:noExist:-${map:noExist2:-${map:noExist3:-Static Value}}}"));
+
+ // Create the layout.
+ final JsonTemplateLayout layout = JsonTemplateLayout
+ .newBuilder()
+ .setConfiguration(CONFIGURATION)
+ .setEventTemplate(eventTemplate)
+ .build();
+
+ // Create the log event with a MapMessage.
+ final StringMapMessage mapMessage = new StringMapMessage()
+ .with("key1", "val1")
+ .with("key2", "val2")
+ .with("key3", Collections.singletonMap("foo", "bar"));
+ final LogEvent logEvent = Log4jLogEvent
+ .newBuilder()
+ .setLoggerName(LOGGER_NAME)
+ .setLevel(Level.INFO)
+ .setMessage(mapMessage)
+ .setTimeMillis(System.currentTimeMillis())
+ .build();
+
+ // Check the serialized event.
+ usingSerializedLogEventAccessor(layout, logEvent, accessor -> {
+ assertThat(accessor.getString("mapValue1")).isEqualTo("val1");
+ assertThat(accessor.getString("mapValue2")).isEqualTo("val2");
+ assertThat(accessor.getString("nestedLookupEmptyValue")).isEmpty();
+ assertThat(accessor.getString("nestedLookupStaticValue")).isEqualTo("Static Value");
+ });
+
+ }
+
+ @Test
+ public void test_MapMessage_serialization() {
+
+ // Create the event template.
+ final String eventTemplate = writeJson(Map(
+ "message", Map("$resolver", "message")));
+
+ // Create the layout.
+ final JsonTemplateLayout layout = JsonTemplateLayout
+ .newBuilder()
+ .setConfiguration(CONFIGURATION)
+ .setEventTemplate(eventTemplate)
+ .build();
+
+ // Create the log event with a MapMessage.
+ final StringMapMessage mapMessage = new StringMapMessage()
+ .with("key1", "val1")
+ .with("key2", 0xDEADBEEF)
+ .with("key3", Collections.singletonMap("key3.1", "val3.1"));
+ final LogEvent logEvent = Log4jLogEvent
+ .newBuilder()
+ .setLoggerName(LOGGER_NAME)
+ .setLevel(Level.INFO)
+ .setMessage(mapMessage)
+ .setTimeMillis(System.currentTimeMillis())
+ .build();
+
+ // Check the serialized event.
+ usingSerializedLogEventAccessor(layout, logEvent, accessor -> {
+ assertThat(accessor.getString(new String[]{"message", "key1"})).isEqualTo("val1");
+ assertThat(accessor.getInteger(new String[]{"message", "key2"})).isEqualTo(0xDEADBEEF);
+ assertThat(accessor.getString(new String[]{"message", "key3", "key3.1"})).isEqualTo("val3.1");
+ });
+
+ }
+
+ @Test
+ public void test_MapMessage_keyed_access() {
+
+ // Create the event template.
+ final String key = "list";
+ final String eventTemplate = writeJson(Map(
+ "typedValue", Map(
+ "$resolver", "map",
+ "key", key),
+ "stringifiedValue", Map(
+ "$resolver", "map",
+ "key", key,
+ "stringified", true)));
+
+ // Create the layout.
+ final JsonTemplateLayout layout = JsonTemplateLayout
+ .newBuilder()
+ .setConfiguration(CONFIGURATION)
+ .setEventTemplate(eventTemplate)
+ .build();
+
+ // Create the log event with a MapMessage.
+ final List<Integer> value = Arrays.asList(1, 2);
+ final StringMapMessage mapMessage = new StringMapMessage()
+ .with(key, value);
+ final LogEvent logEvent = Log4jLogEvent
+ .newBuilder()
+ .setLoggerName(LOGGER_NAME)
+ .setLevel(Level.INFO)
+ .setMessage(mapMessage)
+ .setTimeMillis(System.currentTimeMillis())
+ .build();
+
+ // Check the serialized event.
+ usingSerializedLogEventAccessor(layout, logEvent, accessor -> {
+ assertThat(accessor.getObject("typedValue")).isEqualTo(value);
+ assertThat(accessor.getString("stringifiedValue")).isEqualTo(String.valueOf(value));
+ });
+
+ }
+
+ @Test
+ public void test_message_fallbackKey() {
+
+ // Create the event template.
+ final String eventTemplate = writeJson(Map(
+ "message", Map(
+ "$resolver", "message",
+ "fallbackKey", "formattedMessage")));
+
+ // Create the layout.
+ final JsonTemplateLayout layout = JsonTemplateLayout
+ .newBuilder()
+ .setConfiguration(CONFIGURATION)
+ .setEventTemplate(eventTemplate)
+ .build();
+
+ // Create a log event with a MapMessage.
+ final Message mapMessage = new StringMapMessage()
+ .with("key1", "val1");
+ final LogEvent mapMessageLogEvent = Log4jLogEvent
+ .newBuilder()
+ .setLoggerName(LOGGER_NAME)
+ .setLevel(Level.INFO)
+ .setMessage(mapMessage)
+ .setTimeMillis(System.currentTimeMillis())
+ .build();
+
+ // Check the serialized MapMessage.
+ usingSerializedLogEventAccessor(layout, mapMessageLogEvent, accessor ->
+ assertThat(accessor.getString(new String[]{"message", "key1"}))
+ .isEqualTo("val1"));
+
+ // Create a log event with a SimpleMessage.
+ final Message simpleMessage = new SimpleMessage("simple");
+ final LogEvent simpleMessageLogEvent = Log4jLogEvent
+ .newBuilder()
+ .setLoggerName(LOGGER_NAME)
+ .setLevel(Level.INFO)
+ .setMessage(simpleMessage)
+ .setTimeMillis(System.currentTimeMillis())
+ .build();
+
+ // Check the serialized MapMessage.
+ usingSerializedLogEventAccessor(layout, simpleMessageLogEvent, accessor ->
+ assertThat(accessor.getString(new String[]{"message", "formattedMessage"}))
+ .isEqualTo("simple"));
+
+ }
+
+ @Test
+ public void test_property_injection() {
+
+ // Create the log event.
+ final SimpleMessage message = new SimpleMessage("Hello, World");
+ final LogEvent logEvent = Log4jLogEvent
+ .newBuilder()
+ .setLoggerName(LOGGER_NAME)
+ .setLevel(Level.INFO)
+ .setMessage(message)
+ .build();
+
+ // Create the event template with property.
+ final String propertyName = "propertyName";
+ final String eventTemplate = writeJson(Map(
+ propertyName, "${" + propertyName + "}"));
+
+ // Create the layout with property.
+ final String propertyValue = "propertyValue";
+ final Configuration config = ConfigurationBuilderFactory
+ .newConfigurationBuilder()
+ .addProperty(propertyName, propertyValue)
+ .build();
+ final JsonTemplateLayout layout = JsonTemplateLayout
+ .newBuilder()
+ .setConfiguration(config)
+ .setEventTemplate(eventTemplate)
+ .build();
+
+ // Check the serialized event.
+ usingSerializedLogEventAccessor(layout, logEvent, accessor ->
+ assertThat(accessor.getString(propertyName)).isEqualTo(propertyValue));
+
+ }
+
+ @Test
+ public void test_empty_root_cause() {
+
+ // Create the log event.
+ final SimpleMessage message = new SimpleMessage("Hello, World!");
+ final RuntimeException exception = new RuntimeException("failure for test purposes");
+ final LogEvent logEvent = Log4jLogEvent
+ .newBuilder()
+ .setLoggerName(LOGGER_NAME)
+ .setLevel(Level.ERROR)
+ .setMessage(message)
+ .setThrown(exception)
+ .build();
+
+ // Create the event template.
+ final String eventTemplate = writeJson(Map(
+ "ex_class", Map(
+ "$resolver", "exception",
+ "field", "className"),
+ "ex_message", Map(
+ "$resolver", "exception",
+ "field", "message"),
+ "ex_stacktrace", Map(
+ "$resolver", "exception",
+ "field", "stackTrace",
+ "stringified", true),
+ "root_ex_class", Map(
+ "$resolver", "exceptionRootCause",
+ "field", "className"),
+ "root_ex_message", Map(
+ "$resolver", "exceptionRootCause",
+ "field", "message"),
+ "root_ex_stacktrace", Map(
+ "$resolver", "exceptionRootCause",
+ "field", "stackTrace",
+ "stringified", true)));
+
+ // Create the layout.
+ final JsonTemplateLayout layout = JsonTemplateLayout
+ .newBuilder()
+ .setConfiguration(CONFIGURATION)
+ .setStackTraceEnabled(true)
+ .setEventTemplate(eventTemplate)
+ .build();
+
+ // Check the serialized event.
+ usingSerializedLogEventAccessor(layout, logEvent, accessor -> {
+ assertThat(accessor.getString("ex_class"))
+ .isEqualTo(exception.getClass().getCanonicalName());
+ assertThat(accessor.getString("ex_message"))
+ .isEqualTo(exception.getMessage());
+ assertThat(accessor.getString("ex_stacktrace"))
+ .startsWith(exception.getClass().getCanonicalName() + ": " + exception.getMessage());
+ assertThat(accessor.getString("root_ex_class"))
+ .isEqualTo(accessor.getString("ex_class"));
+ assertThat(accessor.getString("root_ex_message"))
+ .isEqualTo(accessor.getString("ex_message"));
+ assertThat(accessor.getString("root_ex_stacktrace"))
+ .isEqualTo(accessor.getString("ex_stacktrace"));
+ });
+
+ }
+
+ @Test
+ public void test_root_cause() {
+
+ // Create the log event.
+ final SimpleMessage message = new SimpleMessage("Hello, World!");
+ final RuntimeException exceptionCause = new RuntimeException("failure cause for test purposes");
+ final RuntimeException exception = new RuntimeException("failure for test purposes", exceptionCause);
+ final LogEvent logEvent = Log4jLogEvent
+ .newBuilder()
+ .setLoggerName(LOGGER_NAME)
+ .setLevel(Level.ERROR)
+ .setMessage(message)
+ .setThrown(exception)
+ .build();
+
+ // Create the event template.
+ final String eventTemplate = writeJson(Map(
+ "ex_class", Map(
+ "$resolver", "exception",
+ "field", "className"),
+ "ex_message", Map(
+ "$resolver", "exception",
+ "field", "message"),
+ "ex_stacktrace", Map(
+ "$resolver", "exception",
+ "field", "stackTrace",
+ "stringified", true),
+ "root_ex_class", Map(
+ "$resolver", "exceptionRootCause",
+ "field", "className"),
+ "root_ex_message", Map(
+ "$resolver", "exceptionRootCause",
+ "field", "message"),
+ "root_ex_stacktrace", Map(
+ "$resolver", "exceptionRootCause",
+ "field", "stackTrace",
+ "stringified", true)));
+
+ // Create the layout.
+ final JsonTemplateLayout layout = JsonTemplateLayout
+ .newBuilder()
+ .setConfiguration(CONFIGURATION)
+ .setStackTraceEnabled(true)
+ .setEventTemplate(eventTemplate)
+ .build();
+
+ // Check the serialized event.
+ usingSerializedLogEventAccessor(layout, logEvent, accessor -> {
+ assertThat(accessor.getString("ex_class"))
+ .isEqualTo(exception.getClass().getCanonicalName());
+ assertThat(accessor.getString("ex_message"))
+ .isEqualTo(exception.getMessage());
+ assertThat(accessor.getString("ex_stacktrace"))
+ .startsWith(exception.getClass().getCanonicalName() + ": " + exception.getMessage());
+ assertThat(accessor.getString("root_ex_class"))
+ .isEqualTo(exceptionCause.getClass().getCanonicalName());
+ assertThat(accessor.getString("root_ex_message"))
+ .isEqualTo(exceptionCause.getMessage());
+ assertThat(accessor.getString("root_ex_stacktrace"))
+ .startsWith(exceptionCause.getClass().getCanonicalName() + ": " + exceptionCause.getMessage());
+ });
+
+ }
+
+ @Test
+ public void test_marker_name() {
+
+ // Create the log event.
+ final SimpleMessage message = new SimpleMessage("Hello, World!");
+ final String markerName = "test";
+ final Marker marker = MarkerManager.getMarker(markerName);
+ final LogEvent logEvent = Log4jLogEvent
+ .newBuilder()
+ .setLoggerName(LOGGER_NAME)
+ .setLevel(Level.ERROR)
+ .setMessage(message)
+ .setMarker(marker)
+ .build();
+
+ // Create the event template.
+ final String messageKey = "message";
+ final String markerNameKey = "marker";
+ final String eventTemplate = writeJson(Map(
+ "message", Map("$resolver", "message"),
+ "marker", Map(
+ "$resolver", "marker",
+ "field", "name")));
+
+ // Create the layout.
+ final JsonTemplateLayout layout = JsonTemplateLayout
+ .newBuilder()
+ .setConfiguration(CONFIGURATION)
+ .setEventTemplate(eventTemplate)
+ .build();
+
+ // Check the serialized event.
+ usingSerializedLogEventAccessor(layout, logEvent, accessor -> {
+ assertThat(accessor.getString(messageKey)).isEqualTo(message.getFormattedMessage());
+ assertThat(accessor.getString(markerNameKey)).isEqualTo(markerName);
+ });
+
+ }
+
+ @Test
+ public void test_lineSeparator_suffix() {
+
+ // Create the log event.
+ final SimpleMessage message = new SimpleMessage("Hello, World!");
+ final LogEvent logEvent = Log4jLogEvent
+ .newBuilder()
+ .setLoggerName(LOGGER_NAME)
+ .setLevel(Level.INFO)
+ .setMessage(message)
+ .build();
+
+ // Check line separators.
+ test_lineSeparator_suffix(logEvent, true);
+ test_lineSeparator_suffix(logEvent, false);
+
+ }
+
+ private void test_lineSeparator_suffix(
+ final LogEvent logEvent,
+ final boolean prettyPrintEnabled) {
+
+ // Create the layout.
+ final JsonTemplateLayout layout = JsonTemplateLayout
+ .newBuilder()
+ .setConfiguration(CONFIGURATION)
+ .setEventTemplateUri("classpath:LogstashJsonEventLayoutV1.json")
+ .build();
+
+ // Check the serialized event.
+ final String serializedLogEvent = layout.toSerializable(logEvent);
+ final String assertionCaption = String.format("testing lineSeperator (prettyPrintEnabled=%s)", prettyPrintEnabled);
+ assertThat(serializedLogEvent).as(assertionCaption).endsWith("}" + System.lineSeparator());
+
+ }
+
+ @Test
+ public void test_main_key_access() {
+
+ // Set main() arguments.
+ final String kwKey = "--name";
+ final String kwVal = "aNameValue";
+ final String positionArg = "position2Value";
+ final String missingKwKey = "--missing";
+ final String[] mainArgs = {kwKey, kwVal, positionArg};
+ MainMapLookup.setMainArguments(mainArgs);
+
+ // Create the log event.
+ final SimpleMessage message = new SimpleMessage("Hello, World!");
+ final LogEvent logEvent = Log4jLogEvent
+ .newBuilder()
+ .setLoggerName(LOGGER_NAME)
+ .setLevel(Level.INFO)
+ .setMessage(message)
+ .build();
+
+ // Create the template.
+ final String template = writeJson(Map(
+ "name", Map(
+ "$resolver", "main",
+ "key", kwKey),
+ "positionArg", Map(
+ "$resolver", "main",
+ "index", 2),
+ "notFoundArg", Map(
+ "$resolver", "main",
+ "key", missingKwKey)));
+
+ // Create the layout.
+ final JsonTemplateLayout layout = JsonTemplateLayout
+ .newBuilder()
+ .setConfiguration(CONFIGURATION)
+ .setEventTemplate(template)
+ .build();
+
+ // Check the serialized event.
+ usingSerializedLogEventAccessor(layout, logEvent, accessor -> {
+ assertThat(accessor.getString("name")).isEqualTo(kwVal);
+ assertThat(accessor.getString("positionArg")).isEqualTo(positionArg);
+ assertThat(accessor.exists("notFoundArg")).isFalse();
+ });
+
+ }
+
+ @Test
+ public void test_mdc_key_access() {
+
+ // Create the log event.
+ final SimpleMessage message = new SimpleMessage("Hello, World!");
+ final StringMap contextData = new SortedArrayStringMap();
+ final String mdcDirectlyAccessedKey = "mdcKey1";
+ final String mdcDirectlyAccessedValue = "mdcValue1";
+ contextData.putValue(mdcDirectlyAccessedKey, mdcDirectlyAccessedValue);
+ final String mdcDirectlyAccessedNullPropertyKey = "mdcKey2";
+ final String mdcDirectlyAccessedNullPropertyValue = null;
+ // noinspection ConstantConditions
+ contextData.putValue(mdcDirectlyAccessedNullPropertyKey, mdcDirectlyAccessedNullPropertyValue);
+ final LogEvent logEvent = Log4jLogEvent
+ .newBuilder()
+ .setLoggerName(LOGGER_NAME)
+ .setLevel(Level.INFO)
+ .setMessage(message)
+ .setContextData(contextData)
+ .build();
+
+ // Create the event template.
+ String eventTemplate = writeJson(Map(
+ mdcDirectlyAccessedKey, Map(
+ "$resolver", "mdc",
+ "key", mdcDirectlyAccessedKey),
+ mdcDirectlyAccessedNullPropertyKey, Map(
+ "$resolver", "mdc",
+ "key", mdcDirectlyAccessedNullPropertyKey)));
+
+ // Create the layout.
+ final JsonTemplateLayout layout = JsonTemplateLayout
+ .newBuilder()
+ .setConfiguration(CONFIGURATION)
+ .setStackTraceEnabled(true)
+ .setEventTemplate(eventTemplate)
+ .build();
+
+ // Check the serialized event.
+ usingSerializedLogEventAccessor(layout, logEvent, accessor -> {
+ assertThat(accessor.getString(mdcDirectlyAccessedKey)).isEqualTo(mdcDirectlyAccessedValue);
+ assertThat(accessor.getString(mdcDirectlyAccessedNullPropertyKey)).isNull();
+ });
+
+ }
+
+ @Test
+ public void test_mdc_pattern() {
+
+ // Create the log event.
+ final SimpleMessage message = new SimpleMessage("Hello, World!");
+ final StringMap contextData = new SortedArrayStringMap();
+ final String mdcPatternMatchedKey = "mdcKey1";
+ final String mdcPatternMatchedValue = "mdcValue1";
+ contextData.putValue(mdcPatternMatchedKey, mdcPatternMatchedValue);
+ final String mdcPatternMismatchedKey = "mdcKey2";
+ final String mdcPatternMismatchedValue = "mdcValue2";
+ contextData.putValue(mdcPatternMismatchedKey, mdcPatternMismatchedValue);
+ final LogEvent logEvent = Log4jLogEvent
+ .newBuilder()
+ .setLoggerName(LOGGER_NAME)
+ .setLevel(Level.INFO)
+ .setMessage(message)
+ .setContextData(contextData)
+ .build();
+
+ // Create the event template.
+ final String mdcFieldName = "mdc";
+ final String eventTemplate = writeJson(Map(
+ mdcFieldName, Map(
+ "$resolver", "mdc",
+ "pattern", mdcPatternMatchedKey)));
+
+ // Create the layout.
+ final JsonTemplateLayout layout = JsonTemplateLayout
+ .newBuilder()
+ .setConfiguration(CONFIGURATION)
+ .setStackTraceEnabled(true)
+ .setEventTemplate(eventTemplate)
+ .build();
+
+ // Check the serialized event.
+ usingSerializedLogEventAccessor(layout, logEvent, accessor -> {
+ assertThat(accessor.getString(new String[]{mdcFieldName, mdcPatternMatchedKey})).isEqualTo(mdcPatternMatchedValue);
+ assertThat(accessor.exists(new String[]{mdcFieldName, mdcPatternMismatchedKey})).isFalse();
+ });
+
+ }
+
+ @Test
+ public void test_mdc_flatten() {
+
+ // Create the log event.
+ final SimpleMessage message = new SimpleMessage("Hello, World!");
+ final StringMap contextData = new SortedArrayStringMap();
+ final String mdcPatternMatchedKey = "mdcKey1";
+ final String mdcPatternMatchedValue = "mdcValue1";
+ contextData.putValue(mdcPatternMatchedKey, mdcPatternMatchedValue);
+ final String mdcPatternMismatchedKey = "mdcKey2";
+ final String mdcPatternMismatchedValue = "mdcValue2";
+ contextData.putValue(mdcPatternMismatchedKey, mdcPatternMismatchedValue);
+ final LogEvent logEvent = Log4jLogEvent
+ .newBuilder()
+ .setLoggerName(LOGGER_NAME)
+ .setLevel(Level.INFO)
+ .setMessage(message)
+ .setContextData(contextData)
+ .build();
+
+ // Create the event template.
+ final String mdcPrefix = "_mdc.";
+ final String eventTemplate = writeJson(Map(
+ "ignoredFieldName", Map(
+ "$resolver", "mdc",
+ "pattern", mdcPatternMatchedKey,
+ "flatten", Map("prefix", mdcPrefix))));
+
+ // Create the layout.
+ final JsonTemplateLayout layout = JsonTemplateLayout
+ .newBuilder()
+ .setConfiguration(CONFIGURATION)
+ .setStackTraceEnabled(true)
+ .setEventTemplate(eventTemplate)
+ .build();
+
+ // Check the serialized event.
+ usingSerializedLogEventAccessor(layout, logEvent, accessor -> {
+ assertThat(accessor.getString(mdcPrefix + mdcPatternMatchedKey)).isEqualTo(mdcPatternMatchedValue);
+ assertThat(accessor.exists(mdcPrefix + mdcPatternMismatchedKey)).isFalse();
+ });
+
+ }
+
+ @Test
+ public void test_MapResolver() {
+
+ // Create the log event.
+ final StringMapMessage message = new StringMapMessage().with("key1", "val1");
+ final LogEvent logEvent = Log4jLogEvent
+ .newBuilder()
+ .setLoggerName(LOGGER_NAME)
+ .setLevel(Level.INFO)
+ .setMessage(message)
+ .build();
+
+ // Create the event template node with map values.
+ final String eventTemplate = writeJson(Map(
+ "mapValue1", Map(
+ "$resolver", "map",
+ "key", "key1"),
+ "mapValue2", Map(
+ "$resolver", "map",
+ "key", "key?")));
+
+ // Create the layout.
+ final JsonTemplateLayout layout = JsonTemplateLayout
+ .newBuilder()
+ .setConfiguration(CONFIGURATION)
+ .setEventTemplate(eventTemplate)
+ .build();
+
+ // Check serialized event.
+ usingSerializedLogEventAccessor(layout, logEvent, accessor -> {
+ assertThat(accessor.getString("mapValue1")).isEqualTo("val1");
+ assertThat(accessor.getString("mapValue2")).isNull();
+ });
+
+ }
+
+ @Test
+ public void test_StringMapMessage() {
+
+ // Create the log event.
+ final StringMapMessage message = new StringMapMessage();
+ message.put("message", "Hello, World!");
+ message.put("bottle", "Kickapoo Joy Juice");
+ final LogEvent logEvent = Log4jLogEvent
+ .newBuilder()
+ .setLoggerName(LOGGER_NAME)
+ .setLevel(Level.INFO)
+ .setMessage(message)
+ .build();
+
+ // Create the event template.
+ final String eventTemplate = writeJson(Map(
+ "message", Map("$resolver", "message")));
+
+ // Create the layout.
+ final JsonTemplateLayout layout = JsonTemplateLayout
+ .newBuilder()
+ .setConfiguration(CONFIGURATION)
+ .setStackTraceEnabled(true)
+ .setEventTemplate(eventTemplate)
+ .build();
+
+ // Check the serialized event.
+ usingSerializedLogEventAccessor(layout, logEvent, accessor -> {
+ assertThat(accessor.getString(new String[]{"message", "message"})).isEqualTo("Hello, World!");
+ assertThat(accessor.getString(new String[]{"message", "bottle"})).isEqualTo("Kickapoo Joy Juice");
+ });
+
+ }
+
+ @Test
+ public void test_ObjectMessage() {
+
+ // Create the log event.
+ final int id = 0xDEADBEEF;
+ final String name = "name-" + id;
+ final Object attachment = new LinkedHashMap<String, Object>() {{
+ put("id", id);
+ put("name", name);
+ }};
+ final ObjectMessage message = new ObjectMessage(attachment);
+ final LogEvent logEvent = Log4jLogEvent
+ .newBuilder()
+ .setLoggerName(LOGGER_NAME)
+ .setLevel(Level.INFO)
+ .setMessage(message)
+ .build();
+
+ // Create the event template.
+ final String eventTemplate = writeJson(Map(
+ "message", Map("$resolver", "message")));
+
+ // Create the layout.
+ JsonTemplateLayout layout = JsonTemplateLayout
+ .newBuilder()
+ .setConfiguration(CONFIGURATION)
+ .setStackTraceEnabled(true)
+ .setEventTemplate(eventTemplate)
+ .build();
+
+ // Check the serialized event.
+ usingSerializedLogEventAccessor(layout, logEvent, accessor -> {
+ assertThat(accessor.getInteger(new String[]{"message", "id"})).isEqualTo(id);
+ assertThat(accessor.getString(new String[]{"message", "name"})).isEqualTo(name);
+ });
+
+ }
+
+ @Test
+ public void test_StackTraceElement_template() {
+
+ // Create the stack trace element template.
+ final String classNameFieldName = "className";
+ final String methodNameFieldName = "methodName";
+ final String fileNameFieldName = "fileName";
+ final String lineNumberFieldName = "lineNumber";
+ final String stackTraceElementTemplate = writeJson(Map(
+ classNameFieldName, Map(
+ "$resolver", "stackTraceElement",
+ "field", "className"),
+ methodNameFieldName, Map(
+ "$resolver", "stackTraceElement",
+ "field", "methodName"),
+ fileNameFieldName, Map(
+ "$resolver", "stackTraceElement",
+ "field", "fileName"),
+ lineNumberFieldName, Map(
+ "$resolver", "stackTraceElement",
+ "field", "lineNumber")));
+
+ // Create the event template.
+ final String stackTraceFieldName = "stackTrace";
+ final String eventTemplate = writeJson(Map(
+ stackTraceFieldName, Map(
+ "$resolver", "exception",
+ "field", "stackTrace")));
+
+ // Create the layout.
+ final JsonTemplateLayout layout = JsonTemplateLayout
+ .newBuilder()
+ .setConfiguration(CONFIGURATION)
+ .setStackTraceEnabled(true)
+ .setStackTraceElementTemplate(stackTraceElementTemplate)
+ .setEventTemplate(eventTemplate)
+ .build();
+
+ // Create the log event.
+ final SimpleMessage message = new SimpleMessage("Hello, World!");
+ final RuntimeException exceptionCause = new RuntimeException("failure cause for test purposes");
+ final RuntimeException exception = new RuntimeException("failure for test purposes", exceptionCause);
+ final LogEvent logEvent = Log4jLogEvent
+ .newBuilder()
+ .setLoggerName(LOGGER_NAME)
+ .setLevel(Level.ERROR)
+ .setMessage(message)
+ .setThrown(exception)
+ .build();
+
+ // Check the serialized event.
+ usingSerializedLogEventAccessor(layout, logEvent, accessor -> {
+ assertThat(accessor.exists(stackTraceFieldName)).isTrue();
+ @SuppressWarnings("unchecked")
+ final List<Map<String, Object>> deserializedStackTraceElements =
+ accessor.getObject(stackTraceFieldName, List.class);
+ final StackTraceElement[] stackTraceElements = exception.getStackTrace();
+ assertThat(deserializedStackTraceElements.size()).isEqualTo(stackTraceElements.length);
+ for (int stackTraceElementIndex = 0;
+ stackTraceElementIndex < stackTraceElements.length;
+ stackTraceElementIndex++) {
+ final StackTraceElement stackTraceElement = stackTraceElements[stackTraceElementIndex];
+ final Map<String, Object> deserializedStackTraceElement = deserializedStackTraceElements.get(stackTraceElementIndex);
+ assertThat(deserializedStackTraceElement.size()).isEqualTo(4);
+ assertThat(deserializedStackTraceElement.get(classNameFieldName))
+ .isEqualTo(stackTraceElement.getClassName());
+ assertThat(deserializedStackTraceElement.get(methodNameFieldName))
+ .isEqualTo(stackTraceElement.getMethodName());
+ assertThat(deserializedStackTraceElement.get(fileNameFieldName))
+ .isEqualTo(stackTraceElement.getFileName());
+ assertThat(deserializedStackTraceElement.get(lineNumberFieldName))
+ .isEqualTo(stackTraceElement.getLineNumber());
+ }
+ });
+
+ }
+
+ @Test
+ public void test_toSerializable_toByteArray_encode_outputs() {
+
+ // Create the layout.
+ final JsonTemplateLayout layout = JsonTemplateLayout
+ .newBuilder()
+ .setConfiguration(CONFIGURATION)
+ .setEventTemplateUri("classpath:LogstashJsonEventLayoutV1.json")
+ .setStackTraceEnabled(true)
+ .build();
+
+ // Create the log event.
+ final LogEvent logEvent = LogEventFixture.createFullLogEvents(1).get(0);
+
+ // Get toSerializable() output.
+ final String toSerializableOutput = layout.toSerializable(logEvent);
+
+ // Get toByteArrayOutput().
+ final byte[] toByteArrayOutputBytes = layout.toByteArray(logEvent);
+ final String toByteArrayOutput = new String(
+ toByteArrayOutputBytes,
+ 0,
+ toByteArrayOutputBytes.length,
+ layout.getCharset());
+
+ // Get encode() output.
+ final ByteBuffer byteBuffer = ByteBuffer.allocate(512 * 1024);
+ final ByteBufferDestination byteBufferDestination = new ByteBufferDestination() {
+
+ @Override
+ public ByteBuffer getByteBuffer() {
+ return byteBuffer;
+ }
+
+ @Override
+ public ByteBuffer drain(final ByteBuffer ignored) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void writeBytes(final ByteBuffer data) {
+ byteBuffer.put(data);
+ }
+
+ @Override
+ public void writeBytes(final byte[] buffer, final int offset, final int length) {
+ byteBuffer.put(buffer, offset, length);
+ }
+
+ };
+ layout.encode(logEvent, byteBufferDestination);
+ String encodeOutput = new String(
+ byteBuffer.array(),
+ 0,
+ byteBuffer.position(),
+ layout.getCharset());
+
+ // Compare outputs.
+ assertThat(toSerializableOutput).isEqualTo(toByteArrayOutput);
+ assertThat(toByteArrayOutput).isEqualTo(encodeOutput);
+
+ }
+
+ @Test
+ public void test_maxStringLength() {
+
+ // Create the log event.
+ final int maxStringLength = 30;
+ final String excessiveMessageString = Strings.repeat("m", maxStringLength) + 'M';
+ final SimpleMessage message = new SimpleMessage(excessiveMessageString);
+ final Throwable thrown = new RuntimeException();
+ final LogEvent logEvent = Log4jLogEvent
+ .newBuilder()
+ .setLoggerName(LOGGER_NAME)
+ .setLevel(Level.INFO)
+ .setMessage(message)
+ .setThrown(thrown)
+ .build();
+
+ // Create the event template node with map values.
+ final String messageKey = "message";
+ final String excessiveKey = Strings.repeat("k", maxStringLength) + 'K';
+ final String excessiveValue = Strings.repeat("v", maxStringLength) + 'V';
+ final String nullValueKey = "nullValueKey";
+ final String eventTemplate = writeJson(Map(
+ messageKey, Map("$resolver", "message"),
+ excessiveKey, excessiveValue,
+ nullValueKey, Map(
+ "$resolver", "exception",
+ "field", "message")));
+
+ // Create the layout.
+ final JsonTemplateLayout layout = JsonTemplateLayout
+ .newBuilder()
+ .setConfiguration(CONFIGURATION)
+ .setEventTemplate(eventTemplate)
+ .setMaxStringLength(maxStringLength)
+ .build();
+
+ // Check serialized event.
+ usingSerializedLogEventAccessor(layout, logEvent, accessor -> {
+ final String truncatedStringSuffix =
+ JsonTemplateLayoutDefaults.getTruncatedStringSuffix();
+ final String truncatedMessageString =
+ excessiveMessageString.substring(0, maxStringLength) +
+ truncatedStringSuffix;
+ assertThat(accessor.getString(messageKey)).isEqualTo(truncatedMessageString);
+ final String truncatedKey =
+ excessiveKey.substring(0, maxStringLength) +
+ truncatedStringSuffix;
+ final String truncatedValue =
+ excessiveValue.substring(0, maxStringLength) +
+ truncatedStringSuffix;
+ assertThat(accessor.getString(truncatedKey)).isEqualTo(truncatedValue);
+ assertThat(accessor.getString(nullValueKey)).isNull();
+ });
+
+ }
+
+ private static final class NonAsciiUtf8MethodNameContainingException extends RuntimeException {;
+
+ public static final long serialVersionUID = 0;
+
+ private static final String NON_ASCII_UTF8_TEXT = "அஆஇฬ๘";
+
+ private static final NonAsciiUtf8MethodNameContainingException INSTANCE =
+ createInstance();
+
+ private static NonAsciiUtf8MethodNameContainingException createInstance() {
+ try {
+ throwException_அஆஇฬ๘();
+ throw new IllegalStateException("should not have reached here");
+ } catch (final NonAsciiUtf8MethodNameContainingException exception) {
+ return exception;
+ }
+ }
+
+ @SuppressWarnings("NonAsciiCharacters")
+ private static void throwException_அஆஇฬ๘() {
+ throw new NonAsciiUtf8MethodNameContainingException(
+ "exception with non-ASCII UTF-8 method name");
+ }
+
+ private NonAsciiUtf8MethodNameContainingException(final String message) {
+ super(message);
+ }
+
+ }
+
+ @Test
+ public void test_exception_with_nonAscii_utf8_method_name() {
+
+ // Create the log event.
+ final SimpleMessage message = new SimpleMessage("Hello, World!");
+ final RuntimeException exception = NonAsciiUtf8MethodNameContainingException.INSTANCE;
+ final LogEvent logEvent = Log4jLogEvent
+ .newBuilder()
+ .setLoggerName(LOGGER_NAME)
+ .setLevel(Level.ERROR)
+ .setMessage(message)
+ .setThrown(exception)
+ .build();
+
+ // Create the event template.
+ final String eventTemplate = writeJson(Map(
+ "ex_stacktrace", Map(
+ "$resolver", "exception",
+ "field", "stackTrace",
+ "stringified", true)));
+
+ // Create the layout.
+ final JsonTemplateLayout layout = JsonTemplateLayout
+ .newBuilder()
+ .setConfiguration(CONFIGURATION)
+ .setStackTraceEnabled(true)
+ .setEventTemplate(eventTemplate)
+ .build();
+
+ // Check the serialized event.
+ usingSerializedLogEventAccessor(layout, logEvent, accessor ->
+ assertThat(accessor.getString("ex_stacktrace"))
+ .contains(NonAsciiUtf8MethodNameContainingException.NON_ASCII_UTF8_TEXT));
+
+ }
+
+ @Test
+ public void test_event_template_additional_fields() {
+
+ // Create the log event.
+ final SimpleMessage message = new SimpleMessage("Hello, World!");
+ final RuntimeException exception = NonAsciiUtf8MethodNameContainingException.INSTANCE;
+ final Level level = Level.ERROR;
+ final LogEvent logEvent = Log4jLogEvent
+ .newBuilder()
+ .setLoggerName(LOGGER_NAME)
+ .setLevel(level)
+ .setMessage(message)
+ .setThrown(exception)
+ .build();
+
+ // Create the event template.
+ final String eventTemplate = "{}";
+
+ // Create the layout.
+ final EventTemplateAdditionalField[] additionalFieldPairs = {
+ EventTemplateAdditionalField
+ .newBuilder()
+ .setKey("number")
+ .setValue("1")
+ .setType(EventTemplateAdditionalField.Type.JSON)
+ .build(),
+ EventTemplateAdditionalField
+ .newBuilder()
+ .setKey("string")
+ .setValue("foo")
+ .build(),
+ EventTemplateAdditionalField
+ .newBuilder()
+ .setKey("level")
+ .setValue("{\"$resolver\": \"level\", \"field\": \"name\"}")
+ .setType(EventTemplateAdditionalField.Type.JSON)
+ .build()
+ };
+ final EventTemplateAdditionalFields additionalFields = EventTemplateAdditionalFields
+ .newBuilder()
+ .setAdditionalFields(additionalFieldPairs)
+ .build();
+ final JsonTemplateLayout layout = JsonTemplateLayout
+ .newBuilder()
+ .setConfiguration(CONFIGURATION)
+ .setStackTraceEnabled(true)
+ .setEventTemplate(eventTemplate)
+ .setEventTemplateAdditionalFields(additionalFields)
+ .build();
+
+ // Check the serialized event.
+ usingSerializedLogEventAccessor(layout, logEvent, accessor -> {
+ assertThat(accessor.getInteger("number")).isEqualTo(1);
+ assertThat(accessor.getString("string")).isEqualTo("foo");
+ assertThat(accessor.getString("level")).isEqualTo(level.name());
+ });
+
+ }
+
+ @Test
+ @SuppressWarnings("FloatingPointLiteralPrecision")
+ public void test_timestamp_epoch_resolvers() {
+
+ final List<Map<String, Object>> testCases = Arrays.asList(
+ Map(
+ "epochSecs", new BigDecimal("1581082727.982123456"),
+ "epochSecsRounded", 1581082727,
+ "epochSecsNanos", 982123456,
+ "epochMillis", new BigDecimal("1581082727982.123456"),
+ "epochMillisRounded", 1581082727982L,
+ "epochMillisNanos", 123456,
+ "epochNanos", 1581082727982123456L),
+ Map(
+ "epochSecs", new BigDecimal("1591177590.005000001"),
+ "epochSecsRounded", 1591177590,
+ "epochSecsNanos", 5000001,
+ "epochMillis", new BigDecimal("1591177590005.000001"),
+ "epochMillisRounded", 1591177590005L,
+ "epochMillisNanos", 1,
+ "epochNanos", 1591177590005000001L));
+
+ // Create the event template.
+ final String eventTemplate = writeJson(Map(
+ "epochSecs", Map(
+ "$resolver", "timestamp",
+ "epoch", Map("unit", "secs")),
+ "epochSecsRounded", Map(
+ "$resolver", "timestamp",
+ "epoch", Map(
+ "unit", "secs",
+ "rounded", true)),
+ "epochSecsNanos", Map(
+ "$resolver", "timestamp",
+ "epoch", Map("unit", "secs.nanos")),
+ "epochMillis", Map(
+ "$resolver", "timestamp",
+ "epoch", Map("unit", "millis")),
+ "epochMillisRounded", Map(
+ "$resolver", "timestamp",
+ "epoch", Map(
+ "unit", "millis",
+ "rounded", true)),
+ "epochMillisNanos", Map(
+ "$resolver", "timestamp",
+ "epoch", Map("unit", "millis.nanos")),
+ "epochNanos", Map(
+ "$resolver", "timestamp",
+ "epoch", Map("unit", "nanos"))));
+
+ // Create the layout.
+ final JsonTemplateLayout layout = JsonTemplateLayout
+ .newBuilder()
+ .setConfiguration(CONFIGURATION)
+ .setEventTemplate(eventTemplate)
+ .build();
+
+ testCases.forEach(testCase -> {
+
+ // Create the log event.
+ final SimpleMessage message = new SimpleMessage("Hello, World!");
+ final Level level = Level.ERROR;
+ final MutableInstant instant = new MutableInstant();
+ final Object instantSecsObject = testCase.get("epochSecsRounded");
+ final long instantSecs = instantSecsObject instanceof Long
+ ? (long) instantSecsObject
+ : (int) instantSecsObject;
+ final int instantSecsNanos = (int) testCase.get("epochSecsNanos");
+ instant.initFromEpochSecond(instantSecs, instantSecsNanos);
+ final LogEvent logEvent = Log4jLogEvent
+ .newBuilder()
+ .setLoggerName(LOGGER_NAME)
+ .setLevel(level)
+ .setMessage(message)
+ .setInstant(instant)
+ .build();
+
+ // Verify the test case.
+ usingSerializedLogEventAccessor(layout, logEvent, accessor ->
+ testCase.forEach((key, expectedValue) ->
+ Assertions
+ .assertThat(accessor.getObject(key))
+ .describedAs("key=%s", key)
+ .isEqualTo(expectedValue)));
+
+ });
+
+ }
+
+ @Test
+ public void test_timestamp_pattern_resolver() {
+
+ // Create log events.
+ final String logEvent1FormattedInstant = "2019-01-02T09:34:11Z";
+ final LogEvent logEvent1 = createLogEventAtInstant(logEvent1FormattedInstant);
+ final String logEvent2FormattedInstant = "2019-01-02T09:34:12Z";
+ final LogEvent logEvent2 = createLogEventAtInstant(logEvent2FormattedInstant);
+ @SuppressWarnings("UnnecessaryLocalVariable")
+ final String logEvent3FormattedInstant = logEvent2FormattedInstant;
+ final LogEvent logEvent3 = createLogEventAtInstant(logEvent3FormattedInstant);
+ final String logEvent4FormattedInstant = "2019-01-02T09:34:13Z";
+ final LogEvent logEvent4 = createLogEventAtInstant(logEvent4FormattedInstant);
+
+ // Create the event template.
+ final String eventTemplate = writeJson(Map(
+ "timestamp", Map(
+ "$resolver", "timestamp",
+ "pattern", Map(
+ "format", "yyyy-MM-dd'T'HH:mm:ss'Z'",
+ "timeZone", "UTC"))));
+
+ // Create the layout.
+ final JsonTemplateLayout layout = JsonTemplateLayout
+ .newBuilder()
+ .setConfiguration(CONFIGURATION)
+ .setEventTemplate(eventTemplate)
+ .build();
+
+ // Check the serialized 1st event.
+ usingSerializedLogEventAccessor(layout, logEvent1, accessor ->
+ assertThat(accessor.getString("timestamp"))
+ .isEqualTo(logEvent1FormattedInstant));
+
+ // Check the serialized 2nd event.
+ usingSerializedLogEventAccessor(layout, logEvent2, accessor ->
+ assertThat(accessor.getString("timestamp"))
+ .isEqualTo(logEvent2FormattedInstant));
+
+ // Check the serialized 3rd event.
+ usingSerializedLogEventAccessor(layout, logEvent3, accessor ->
+ assertThat(accessor.getString("timestamp"))
+ .isEqualTo(logEvent3FormattedInstant));
+
+ // Check the serialized 4th event.
+ usingSerializedLogEventAccessor(layout, logEvent4, accessor ->
+ assertThat(accessor.getString("timestamp"))
+ .isEqualTo(logEvent4FormattedInstant));
+
+ }
+
+ private static LogEvent createLogEventAtInstant(final String formattedInstant) {
+ final SimpleMessage message = new SimpleMessage("LogEvent at instant " + formattedInstant);
+ final long instantEpochMillis = Instant.parse(formattedInstant).toEpochMilli();
+ final MutableInstant instant = new MutableInstant();
+ instant.initFromEpochMilli(instantEpochMillis, 0);
+ return Log4jLogEvent
+ .newBuilder()
+ .setLoggerName(LOGGER_NAME)
+ .setMessage(message)
+ .setInstant(instant)
+ .build();
+ }
+
+ @Test
+ public void test_level_severity() {
+
+ // Create the event template.
+ final String eventTemplate = writeJson(Map(
+ "severityKeyword", Map(
+ "$resolver", "level",
+ "field", "severity",
+ "severity", Map("field", "keyword")),
+ "severityCode", Map(
+ "$resolver", "level",
+ "field", "severity",
+ "severity", Map("field", "code"))));
+
+ // Create the layout.
+ final JsonTemplateLayout layout = JsonTemplateLayout
+ .newBuilder()
+ .setConfiguration(CONFIGURATION)
+ .setEventTemplate(eventTemplate)
+ .build();
+
+ for (final Level level : Level.values()) {
+
+ // Create the log event.
+ final SimpleMessage message = new SimpleMessage("Hello, World!");
+ final LogEvent logEvent = Log4jLogEvent
+ .newBuilder()
+ .setLoggerName(LOGGER_NAME)
+ .setLevel(level)
+ .setMessage(message)
+ .build();
+
+ // Check the serialized event.
+ usingSerializedLogEventAccessor(layout, logEvent, accessor -> {
+ final Severity expectedSeverity = Severity.getSeverity(level);
+ final String expectedSeverityKeyword = expectedSeverity.name();
+ final int expectedSeverityCode = expectedSeverity.getCode();
+ assertThat(accessor.getString("severityKeyword")).isEqualTo(expectedSeverityKeyword);
+ assertThat(accessor.getInteger("severityCode")).isEqualTo(expectedSeverityCode);
+ });
+
+ }
+
+ }
+
+ @Test
+ public void test_exception_resolvers_against_no_exceptions() {
+
+ // Create the log event.
+ final SimpleMessage message = new SimpleMessage("Hello, World!");
+ final LogEvent logEvent = Log4jLogEvent
+ .newBuilder()
+ .setLoggerName(LOGGER_NAME)
+ .setMessage(message)
+ .build();
+
+ // Create the event template.
+ final String eventTemplate = writeJson(Map(
+ "exStackTrace", Map(
+ "$resolver", "exception",
+ "field", "stackTrace"),
+ "exStackTraceString", Map(
+ "$resolver", "exception",
+ "field", "stackTrace",
+ "stringified", true),
+ "exRootCauseStackTrace", Map(
+ "$resolver", "exceptionRootCause",
+ "field", "stackTrace"),
+ "exRootCauseStackTraceString", Map(
+ "$resolver", "exceptionRootCause",
+ "field", "stackTrace",
+ "stringified", true),
+ "requiredFieldTriggeringError", true));
+
+ // Create the layout.
+ final JsonTemplateLayout layout = JsonTemplateLayout
+ .newBuilder()
+ .setConfiguration(CONFIGURATION)
+ .setEventTemplate(eventTemplate)
+ .setStackTraceEnabled(true)
+ .build();
+
+ // Check the serialized event.
+ usingSerializedLogEventAccessor(layout, logEvent, accessor -> {
+ assertThat(accessor.getObject("exStackTrace")).isNull();
+ assertThat(accessor.getObject("exStackTraceString")).isNull();
+ assertThat(accessor.getObject("exRootCauseStackTrace")).isNull();
+ assertThat(accessor.getObject("exRootCauseStackTraceString")).isNull();
+ assertThat(accessor.getBoolean("requiredFieldTriggeringError")).isTrue();
+ });
+
+ }
+
+ @Test
+ public void test_StackTraceTextResolver_with_maxStringLength() {
+
+ // Create the event template.
+ final String eventTemplate = writeJson(Map(
+ "stackTrace", Map(
+ "$resolver", "exception",
+ "field", "stackTrace",
+ "stringified", true)));
+
+ // Create the layout.
+ final int maxStringLength = eventTemplate.length();
+ final JsonTemplateLayout layout = JsonTemplateLayout
+ .newBuilder()
+ .setConfiguration(CONFIGURATION)
+ .setEventTemplate(eventTemplate)
+ .setMaxStringLength(maxStringLength)
+ .setStackTraceEnabled(true)
+ .build();
+
+ // Create the log event.
+ final SimpleMessage message = new SimpleMessage("foo");
+ final LogEvent logEvent = Log4jLogEvent
+ .newBuilder()
+ .setLoggerName(LOGGER_NAME)
+ .setMessage(message)
+ .setThrown(NonAsciiUtf8MethodNameContainingException.INSTANCE)
+ .build();
+
+ // Check the serialized event.
+ usingSerializedLogEventAccessor(layout, logEvent, accessor -> {
+ final int expectedLength = maxStringLength +
+ JsonTemplateLayoutDefaults.getTruncatedStringSuffix().length();
+ assertThat(accessor.getString("stackTrace").length()).isEqualTo(expectedLength);
+ });
+
+ }
+
+ @Test
+ public void test_null_eventDelimiter() {
+
+ // Create the event template.
+ final String eventTemplate = writeJson(Map("key", "val"));
+
+ // Create the layout.
+ final JsonTemplateLayout layout = JsonTemplateLayout
+ .newBuilder()
+ .setConfiguration(CONFIGURATION)
+ .setEventTemplate(eventTemplate)
+ .setEventDelimiter("\0")
+ .build();
+
+ // Create the log event.
+ final SimpleMessage message = new SimpleMessage("foo");
+ final LogEvent logEvent = Log4jLogEvent
+ .newBuilder()
+ .setLoggerName(LOGGER_NAME)
+ .setMessage(message)
+ .setThrown(NonAsciiUtf8MethodNameContainingException.INSTANCE)
+ .build();
+
+ // Check the serialized event.
+ final String serializedLogEvent = layout.toSerializable(logEvent);
+ assertThat(serializedLogEvent).isEqualTo(eventTemplate + '\0');
+
+ }
+
+ @Test
+ public void test_against_SocketAppender() throws Exception {
+
+ // Craft nasty events.
+ final List<LogEvent> logEvents = createNastyLogEvents();
+
+ // Create the event template.
+ final String eventTemplate = writeJson(Map(
+ "message", Map("$resolver", "message")));
+
+ // Create the layout.
+ final JsonTemplateLayout layout = JsonTemplateLayout
+ .newBuilder()
+ .setConfiguration(CONFIGURATION)
+ .setEventTemplate(eventTemplate)
+ .build();
+
+ // Create the server.
+ final int port = AvailablePortFinder.getNextAvailable();
+ try (final JsonAcceptingTcpServer server = new JsonAcceptingTcpServer(port, 1)) {
+
+ // Create the appender.
+ final SocketAppender appender = SocketAppender
+ .newBuilder()
+ .setHost("localhost")
+ .setBufferedIo(false)
+ .setPort(port)
+ .setReconnectDelayMillis(100)
+ .setName("test")
+ .setImmediateFail(false)
+ .setIgnoreExceptions(false)
+ .setLayout(layout)
+ .build();
+
+ // Start the appender.
+ appender.start();
+
+ // Transfer and verify the log events.
+ for (int logEventIndex = 0; logEventIndex < logEvents.size(); logEventIndex++) {
+
+ // Send the log event.
+ final LogEvent logEvent = logEvents.get(logEventIndex);
+ appender.append(logEvent);
+ appender.getManager().flush();
+
+ // Pull the parsed log event.
+ final JsonNode node = server.receivedNodes.poll(3, TimeUnit.SECONDS);
+ assertThat(node)
+ .as("logEventIndex=%d", logEventIndex)
+ .isNotNull();
+
+ // Verify the received content.
+ final String expectedMessage = logEvent.getMessage().getFormattedMessage();
+ final String expectedMessageChars = explainChars(expectedMessage);
+ final String actualMessage = point(node, "message").asText();
+ final String actualMessageChars = explainChars(actualMessage);
+ assertThat(actualMessageChars)
+ .as("logEventIndex=%d", logEventIndex)
+ .isEqualTo(expectedMessageChars);
+
+ }
+
+ // Verify that there were no overflows.
+ assertThat(server.droppedNodeCount).isZero();
+
+ }
+
+ }
+
+ private static List<LogEvent> createNastyLogEvents() {
+ return createNastyMessages()
+ .stream()
+ .map(message -> Log4jLogEvent
+ .newBuilder()
+ .setLoggerName(LOGGER_NAME)
+ .setMessage(message)
+ .build())
+ .collect(Collectors.toList());
+ }
+
+ private static List<SimpleMessage> createNastyMessages() {
+
+ // Determine the message count and character offset.
+ final int messageCount = 1024;
+ final int minChar = Character.MIN_VALUE;
+ final int maxChar = Character.MIN_HIGH_SURROGATE - 1;
+ final int totalCharCount = maxChar - minChar + 1;
+ final int charOffset = totalCharCount / messageCount;
+
+ // Populate messages.
+ List<SimpleMessage> messages = new ArrayList<>(messageCount);
+ for (int messageIndex = 0; messageIndex < messageCount; messageIndex++) {
+ final StringBuilder stringBuilder = new StringBuilder(messageIndex + "@");
+ for (int charIndex = 0; charIndex < charOffset; charIndex++) {
+ final char c = (char) (minChar + messageIndex * charOffset + charIndex);
+ stringBuilder.append(c);
+ }
+ final String messageString = stringBuilder.toString();
+ final SimpleMessage message = new SimpleMessage(messageString);
+ messages.add(message);
+ }
+ return messages;
+
+ }
+
+ private static final class JsonAcceptingTcpServer extends Thread implements AutoCloseable {
+
+ private final ServerSocket serverSocket;
+
+ private final BlockingQueue<JsonNode> receivedNodes;
+
+ private volatile int droppedNodeCount = 0;
+
+ private volatile boolean closed = false;
+
+ private JsonAcceptingTcpServer(
+ final int port,
+ final int capacity) throws IOException {
+ this.serverSocket = new ServerSocket(port);
+ this.receivedNodes = new ArrayBlockingQueue<>(capacity);
+ serverSocket.setReuseAddress(true);
+ serverSocket.setSoTimeout(5_000);
+ setDaemon(true);
+ start();
+ }
+
+ @Override
+ public void run() {
+ try {
+ try (final Socket socket = serverSocket.accept()) {
+ final InputStream inputStream = socket.getInputStream();
+ while (!closed) {
+ final MappingIterator<JsonNode> iterator = JacksonFixture
+ .getObjectMapper()
+ .readerFor(JsonNode.class)
+ .readValues(inputStream);
+ while (iterator.hasNextValue()) {
+ final JsonNode value = iterator.nextValue();
+ synchronized (this) {
+ final boolean added = receivedNodes.offer(value);
+ if (!added) {
+ droppedNodeCount++;
+ }
+ }
+ }
+ }
+ }
+ } catch (final EOFException ignored) {
+ // Socket is closed.
+ } catch (final Exception error) {
+ if (!closed) {
+ throw new RuntimeException(error);
+ }
+ }
+ }
+
+ @Override
+ public synchronized void close() throws InterruptedException {
+ if (closed) {
+ throw new IllegalStateException("shutdown has already been invoked");
+ }
+ closed = true;
+ interrupt();
+ join(3_000L);
+ }
+
+ }
+
+ private static String explainChars(final String input) {
+ return IntStream
+ .range(0, input.length())
+ .mapToObj(i -> {
+ final char c = input.charAt(i);
+ return String.format("'%c' (%04X)", c, (int) c);
+ })
+ .collect(Collectors.joining(", "));
+ }
+
+ @Test
+ public void test_PatternResolver() {
+
+ // Create the event template.
+ final String eventTemplate = writeJson(Map(
+ "message", Map(
+ "$resolver", "pattern",
+ "pattern", "%p:%m")));
+
+ // Create the layout.
+ final JsonTemplateLayout layout = JsonTemplateLayout
+ .newBuilder()
+ .setConfiguration(CONFIGURATION)
+ .setEventTemplate(eventTemplate)
+ .build();
+
+ // Create the log event.
+ final SimpleMessage message = new SimpleMessage("foo");
+ final Level level = Level.FATAL;
+ final LogEvent logEvent = Log4jLogEvent
+ .newBuilder()
+ .setLoggerName(LOGGER_NAME)
+ .setMessage(message)
+ .setLevel(level)
+ .build();
+
+ // Check the serialized event.
+ usingSerializedLogEventAccessor(layout, logEvent, accessor -> {
+ final String expectedMessage = String.format(
+ "%s:%s",
+ level, message.getFormattedMessage());
+ assertThat(accessor.getString("message")).isEqualTo(expectedMessage);
+ });
+
+ }
+
+ @Test
+ public void test_unresolvable_nested_fields_are_skipped() {
+
+ // Create the event template.
+ final String eventTemplate = writeJson(Map(
+ "exception", Map(
+ "message", Map(
+ "$resolver", "exception",
+ "field", "message"),
+ "className", Map(
+ "$resolver", "exception",
+ "field", "className")),
+ "exceptionRootCause", Map(
+ "message", Map(
+ "$resolver", "exceptionRootCause",
+ "field", "message"),
+ "className", Map(
+ "$resolver", "exceptionRootCause",
+ "field", "className")),
+ "source", Map(
+ "lineNumber", Map(
+ "$resolver", "source",
+ "field", "lineNumber"),
+ "fileName", Map(
+ "$resolver", "source",
+ "field", "fileName")),
+ "emptyMap", Collections.emptyMap(),
+ "emptyList", Collections.emptyList(),
+ "null", null));
+
+ // Create the layout.
+ final JsonTemplateLayout layout = JsonTemplateLayout
+ .newBuilder()
+ .setConfiguration(CONFIGURATION)
+ .setEventTemplate(eventTemplate)
+ .setStackTraceEnabled(false) // Disable "exception" and "exceptionRootCause" resolvers.
+ .setLocationInfoEnabled(false) // Disable the "source" resolver.
+ .build();
+
+ // Create the log event.
+ final SimpleMessage message = new SimpleMessage("foo");
+ final Level level = Level.FATAL;
+ final Exception thrown = new RuntimeException("bar");
+ final LogEvent logEvent = Log4jLogEvent
+ .newBuilder()
+ .setLoggerName(LOGGER_NAME)
+ .setMessage(message)
+ .setLevel(level)
+ .setThrown(thrown)
+ .build();
+
+ // Check the serialized event.
+ final String expectedSerializedLogEventJson =
+ "{}" + JsonTemplateLayoutDefaults.getEventDelimiter();
+ final String actualSerializedLogEventJson = layout.toSerializable(logEvent);
+ Assertions
+ .assertThat(actualSerializedLogEventJson)
+ .isEqualTo(expectedSerializedLogEventJson);
+
+ }
+
+ private static String writeJson(final Object value) {
+ final StringBuilder stringBuilder = JSON_WRITER.getStringBuilder();
+ stringBuilder.setLength(0);
+ try {
+ JSON_WRITER.writeValue(value);
+ return stringBuilder.toString();
+ } finally {
+ stringBuilder.setLength(0);
+ }
+ }
+
+ private static void usingSerializedLogEventAccessor(
+ final Layout<String> layout,
+ final LogEvent logEvent,
+ final Consumer<MapAccessor> accessorConsumer) {
+ final String serializedLogEventJson = layout.toSerializable(logEvent);
+ @SuppressWarnings("unchecked")
+ final Map<String, Object> serializedLogEvent =
+ (Map<String, Object>) readJson(serializedLogEventJson);
+ final MapAccessor serializedLogEventAccessor = new MapAccessor(serializedLogEvent);
+ accessorConsumer.accept(serializedLogEventAccessor);
+ }
+
+ private static Object readJson(final String json) {
+ return JsonReader.read(json);
+ }
+
+ private static Map<String, Object> Map(final Object... pairs) {
+ final Map<String, Object> map = new LinkedHashMap<>();
+ if (pairs.length % 2 != 0) {
+ throw new IllegalArgumentException("odd number of arguments");
+ }
+ for (int i = 0; i < pairs.length; i += 2) {
+ final String key = (String) pairs[i];
+ final Object value = pairs[i + 1];
+ map.put(key, value);
+ }
+ return map;
+ }
+
+}
diff --git a/log4j-layout-json-template/src/test/java/org/apache/logging/log4j/layout/json/template/LayoutComparisonHelpers.java b/log4j-layout-json-template/src/test/java/org/apache/logging/log4j/layout/json/template/LayoutComparisonHelpers.java
new file mode 100644
index 0000000..6f0a6c0
--- /dev/null
+++ b/log4j-layout-json-template/src/test/java/org/apache/logging/log4j/layout/json/template/LayoutComparisonHelpers.java
@@ -0,0 +1,19 @@
+package org.apache.logging.log4j.layout.json.template;
+
+import org.apache.logging.log4j.core.Layout;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.layout.json.template.util.JsonReader;
+
+import java.util.Map;
+
+enum LayoutComparisonHelpers {;
+
+ @SuppressWarnings("unchecked")
+ static Map<String, Object> renderUsing(
+ final LogEvent logEvent,
+ final Layout<String> layout) {
+ final String json = layout.toSerializable(logEvent);
+ return (Map<String, Object>) JsonReader.read(json);
+ }
+
+}
diff --git a/log4j-layout-json-template/src/test/java/org/apache/logging/log4j/layout/json/template/LogEventFixture.java b/log4j-layout-json-template/src/test/java/org/apache/logging/log4j/layout/json/template/LogEventFixture.java
new file mode 100644
index 0000000..1b0975a
--- /dev/null
+++ b/log4j-layout-json-template/src/test/java/org/apache/logging/log4j/layout/json/template/LogEventFixture.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.layout.json.template;
+
+import org.apache.logging.log4j.Level;
+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.message.SimpleMessage;
+import org.apache.logging.log4j.spi.MutableThreadContextStack;
+import org.apache.logging.log4j.spi.ThreadContextStack;
+import org.apache.logging.log4j.util.StringMap;
+
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.util.ArrayList;
+import java.util.List;
+
+enum LogEventFixture {;
+
+ private static final int TIME_OVERLAPPING_CONSECUTIVE_EVENT_COUNT = 10;
+
+ static List<LogEvent> createLiteLogEvents(final int logEventCount) {
+ final List<LogEvent> logEvents = new ArrayList<>(logEventCount);
+ final long startTimeMillis = System.currentTimeMillis();
+ for (int logEventIndex = 0; logEventIndex < logEventCount; logEventIndex++) {
+ final String logEventId = String.valueOf(logEventIndex);
+ final long logEventTimeMillis = createLogEventTimeMillis(startTimeMillis, logEventIndex);
+ final LogEvent logEvent = LogEventFixture.createLiteLogEvent(logEventId, logEventTimeMillis);
+ logEvents.add(logEvent);
+ }
+ return logEvents;
+ }
+
+ private static LogEvent createLiteLogEvent(final String id, final long timeMillis) {
+ final SimpleMessage message = new SimpleMessage("lite LogEvent message " + id);
+ final Level level = Level.DEBUG;
+ final String loggerFqcn = "f.q.c.n" + id;
+ final String loggerName = "a.B" + id;
+ final long nanoTime = timeMillis * 2;
+ return Log4jLogEvent
+ .newBuilder()
+ .setLoggerName(loggerName)
+ .setLoggerFqcn(loggerFqcn)
+ .setLevel(level)
+ .setMessage(message)
+ .setTimeMillis(timeMillis)
+ .setNanoTime(nanoTime)
+ .build();
+ }
+
+ static List<LogEvent> createFullLogEvents(final int logEventCount) {
+ final List<LogEvent> logEvents = new ArrayList<>(logEventCount);
+ final long startTimeMillis = System.currentTimeMillis();
+ for (int logEventIndex = 0; logEventIndex < logEventCount; logEventIndex++) {
+ final String logEventId = String.valueOf(logEventIndex);
+ final long logEventTimeMillis = createLogEventTimeMillis(startTimeMillis, logEventIndex);
+ final LogEvent logEvent = LogEventFixture.createFullLogEvent(logEventId, logEventTimeMillis);
+ logEvents.add(logEvent);
+ }
+ return logEvents;
+ }
+
+ private static long createLogEventTimeMillis(
+ final long startTimeMillis,
+ final int logEventIndex) {
+ // Create event time repeating every certain number of consecutive
+ // events. This is better aligned with the real-world use case and
+ // gives surface to timestamp formatter caches to perform their
+ // magic, which is implemented for almost all layouts.
+ return startTimeMillis + logEventIndex / TIME_OVERLAPPING_CONSECUTIVE_EVENT_COUNT;
+ }
+
+ private static LogEvent createFullLogEvent(
+ final String id,
+ final long timeMillis) {
+
+ // Create exception.
+ final Exception sourceHelper = new Exception();
+ sourceHelper.fillInStackTrace();
+ final Exception cause = new NullPointerException("testNPEx-" + id);
+ sourceHelper.fillInStackTrace();
+ final StackTraceElement source = sourceHelper.getStackTrace()[0];
+ final IOException ioException = new IOException("testIOEx-" + id, cause);
+ ioException.addSuppressed(new IndexOutOfBoundsException("I am suppressed exception 1" + id));
+ ioException.addSuppressed(new IndexOutOfBoundsException("I am suppressed exception 2" + id));
+
+ // Create rest of the event attributes.
+ final SimpleMessage message = new SimpleMessage("full LogEvent message " + id);
+ final StringMap contextData = createContextData(id);
+ final ThreadContextStack contextStack = createContextStack(id);
+ final int threadId = id.hashCode();
+ final String threadName = "MyThreadName" + id;
+ final int threadPriority = threadId % 10;
+ final Level level = Level.DEBUG;
+ final String loggerFqcn = "f.q.c.n" + id;
+ final String loggerName = "a.B" + id;
+ final long nanoTime = timeMillis * 2;
+
+ // Create the event.
+ return Log4jLogEvent
+ .newBuilder()
+ .setLoggerName(loggerName)
+ .setLoggerFqcn(loggerFqcn)
+ .setLevel(level)
+ .setMessage(message)
+ .setThrown(ioException)
+ .setContextData(contextData)
+ .setContextStack(contextStack)
+ .setThreadId(threadId)
+ .setThreadName(threadName)
+ .setThreadPriority(threadPriority)
+ .setSource(source)
+ .setTimeMillis(timeMillis)
+ .setNanoTime(nanoTime)
+ .build();
+
+ }
+
+ private static StringMap createContextData(final String id) {
+ final StringMap contextData = ContextDataFactory.createContextData();
+ contextData.putValue("MDC.String." + id, "String");
+ contextData.putValue("MDC.BigDecimal." + id, BigDecimal.valueOf(Math.PI));
+ contextData.putValue("MDC.Integer." + id, 10);
+ contextData.putValue("MDC.Long." + id, Long.MAX_VALUE);
+ return contextData;
+ }
+
+ private static ThreadContextStack createContextStack(final String id) {
+ final ThreadContextStack contextStack = new MutableThreadContextStack();
+ contextStack.clear();
+ contextStack.push("stack_msg1" + id);
+ contextStack.add("stack_msg2" + id);
+ return contextStack;
+ }
+
+}
diff --git a/log4j-layout-json-template/src/test/java/org/apache/logging/log4j/layout/json/template/LogstashIT.java b/log4j-layout-json-template/src/test/java/org/apache/logging/log4j/layout/json/template/LogstashIT.java
new file mode 100644
index 0000000..311b0df
--- /dev/null
+++ b/log4j-layout-json-template/src/test/java/org/apache/logging/log4j/layout/json/template/LogstashIT.java
@@ -0,0 +1,503 @@
+package org.apache.logging.log4j.layout.json.template;
+
+import co.elastic.logging.log4j2.EcsLayout;
+import org.apache.http.HttpHost;
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.core.Appender;
+import org.apache.logging.log4j.core.Layout;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.appender.SocketAppender;
+import org.apache.logging.log4j.core.config.DefaultConfiguration;
+import org.apache.logging.log4j.core.impl.Log4jLogEvent;
+import org.apache.logging.log4j.core.layout.GelfLayout;
+import org.apache.logging.log4j.core.util.NetUtils;
+import org.apache.logging.log4j.layout.json.template.JsonTemplateLayout.EventTemplateAdditionalField;
+import org.apache.logging.log4j.layout.json.template.util.ThreadLocalRecyclerFactory;
+import org.apache.logging.log4j.message.SimpleMessage;
+import org.apache.logging.log4j.status.StatusLogger;
+import org.assertj.core.api.Assertions;
+import org.awaitility.Awaitility;
+import org.elasticsearch.ElasticsearchStatusException;
+import org.elasticsearch.action.admin.cluster.health.ClusterHealthRequest;
+import org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse;
+import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
+import org.elasticsearch.action.search.SearchRequest;
+import org.elasticsearch.action.search.SearchResponse;
+import org.elasticsearch.action.support.master.AcknowledgedResponse;
+import org.elasticsearch.client.RequestOptions;
+import org.elasticsearch.client.RestClient;
+import org.elasticsearch.client.RestClientBuilder;
+import org.elasticsearch.client.RestHighLevelClient;
+import org.elasticsearch.cluster.health.ClusterHealthStatus;
+import org.elasticsearch.rest.RestStatus;
+import org.elasticsearch.search.SearchHit;
+import org.elasticsearch.search.builder.SearchSourceBuilder;
+import org.junit.Test;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+public class LogstashIT {
+
+ private static final StatusLogger LOGGER = StatusLogger.getLogger();
+
+ private static final DefaultConfiguration CONFIGURATION = new DefaultConfiguration();
+
+ private static final Charset CHARSET = StandardCharsets.UTF_8;
+
+ private static final String HOST_NAME = NetUtils.getLocalHostname();
+
+ private static final String SERVICE_NAME = "LogstashIT";
+
+ private static final String EVENT_DATASET = SERVICE_NAME + ".log";
+
+ private static final GelfLayout GELF_LAYOUT = GelfLayout
+ .newBuilder()
+ .setConfiguration(CONFIGURATION)
+ .setCharset(CHARSET)
+ .setCompressionType(GelfLayout.CompressionType.OFF)
+ .setIncludeNullDelimiter(true)
+ .setHost(HOST_NAME)
+ .build();
+
+ private static final JsonTemplateLayout JSON_TEMPLATE_GELF_LAYOUT = JsonTemplateLayout
+ .newBuilder()
+ .setConfiguration(CONFIGURATION)
+ .setCharset(CHARSET)
+ .setEventTemplateUri("classpath:GelfLayout.json")
+ .setEventDelimiter("\0")
+ .setEventTemplateAdditionalFields(JsonTemplateLayout
+ .EventTemplateAdditionalFields
+ .newBuilder()
+ .setAdditionalFields(
+ new EventTemplateAdditionalField[]{
+ EventTemplateAdditionalField
+ .newBuilder()
+ .setKey("host")
+ .setValue(HOST_NAME)
+ .build()
+ })
+ .build())
+ .build();
+
+ private static final EcsLayout ECS_LAYOUT = EcsLayout
+ .newBuilder()
+ .setConfiguration(CONFIGURATION)
+ .setCharset(CHARSET)
+ .setServiceName(SERVICE_NAME)
+ .setEventDataset(EVENT_DATASET)
+ .build();
+
+ private static final JsonTemplateLayout JSON_TEMPLATE_ECS_LAYOUT = JsonTemplateLayout
+ .newBuilder()
+ .setConfiguration(CONFIGURATION)
+ .setCharset(CHARSET)
+ .setEventTemplateUri("classpath:EcsLayout.json")
+ .setRecyclerFactory(ThreadLocalRecyclerFactory.getInstance())
+ .setEventTemplateAdditionalFields(JsonTemplateLayout
+ .EventTemplateAdditionalFields
+ .newBuilder()
+ .setAdditionalFields(
+ new EventTemplateAdditionalField[]{
+ EventTemplateAdditionalField
+ .newBuilder()
+ .setKey("service.name")
+ .setValue(SERVICE_NAME)
+ .build(),
+ EventTemplateAdditionalField
+ .newBuilder()
+ .setKey("event.dataset")
+ .setValue(EVENT_DATASET)
+ .build()
+ })
+ .build())
+ .build();
+
+ private static final int LOG_EVENT_COUNT = 100;
+
+ private static final String ES_INDEX_MESSAGE_FIELD_NAME = "message";
+
+ /**
+ * Constants hardcoded in docker-maven-plugin configuration, do not change!
+ */
+ private enum MavenHardcodedConstants {;
+
+ private static final int LS_GELF_INPUT_PORT = 12222;
+
+ private static final int LS_TCP_INPUT_PORT = 12345;
+
+ private static final int ES_PORT = 9200;
+
+ private static final String ES_INDEX_NAME = "log4j";
+
+ }
+
+ @Test
+ public void test_lite_events() throws IOException {
+ final List<LogEvent> logEvents =
+ LogEventFixture.createLiteLogEvents(LOG_EVENT_COUNT);
+ testEvents(logEvents);
+ }
+
+ @Test
+ public void test_full_events() throws IOException {
+ final List<LogEvent> logEvents =
+ LogEventFixture.createFullLogEvents(LOG_EVENT_COUNT);
+ testEvents(logEvents);
+ }
+
+ private static void testEvents(final List<LogEvent> logEvents) throws IOException {
+ try (final RestHighLevelClient client = createClient()) {
+ final Appender appender = createStartedAppender(
+ JSON_TEMPLATE_GELF_LAYOUT,
+ MavenHardcodedConstants.LS_GELF_INPUT_PORT);
+ try {
+
+ // Append events.
+ LOGGER.info("appending events");
+ logEvents.forEach(appender::append);
+ LOGGER.info("completed appending events");
+
+ // Wait all messages to arrive.
+ Awaitility
+ .await()
+ .atMost(Duration.ofSeconds(60))
+ .pollDelay(Duration.ofSeconds(2))
+ .until(() -> queryDocumentCount(client) == LOG_EVENT_COUNT);
+
+ // Verify indexed messages.
+ final Set<String> expectedMessages = logEvents
+ .stream()
+ .map(LogstashIT::expectedLogstashMessageField)
+ .collect(Collectors.toSet());
+ final Set<String> actualMessages = queryDocuments(client)
+ .stream()
+ .map(source -> (String) source.get(ES_INDEX_MESSAGE_FIELD_NAME))
+ .filter(Objects::nonNull)
+ .collect(Collectors.toSet());
+ Assertions
+ .assertThat(actualMessages)
+ .isEqualTo(expectedMessages);
+
+ } finally {
+ appender.stop();
+ }
+ }
+ }
+
+ private static String expectedLogstashMessageField(final LogEvent logEvent) {
+ final Throwable throwable = logEvent.getThrown();
+ if (throwable != null) {
+ try (final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
+ final PrintStream printStream = new PrintStream(outputStream, false, CHARSET.name())) {
+ throwable.printStackTrace(printStream);
+ return outputStream.toString(CHARSET.name());
+ } catch (final Exception error) {
+ throw new RuntimeException(
+ "failed printing stack trace",
+ error);
+ }
+ } else {
+ return logEvent.getMessage().getFormattedMessage();
+ }
+ }
+
+ @Test
+ public void test_newlines() throws IOException {
+
+ // Create two log events containing new lines.
+ final Level level = Level.DEBUG;
+ final String loggerFqcn = "f.q.c.n";
+ final String loggerName = "A";
+ final SimpleMessage message1 = new SimpleMessage("line1\nline2\r\nline3");
+ final long instantMillis1 = Instant.EPOCH.toEpochMilli();
+ final LogEvent logEvent1 = Log4jLogEvent
+ .newBuilder()
+ .setLoggerName(loggerName)
+ .setLoggerFqcn(loggerFqcn)
+ .setLevel(level)
+ .setMessage(message1)
+ .setTimeMillis(instantMillis1)
+ .build();
+ final SimpleMessage message2 = new SimpleMessage("line4\nline5\r\nline6");
+ final long instantMillis2 = instantMillis1 + Duration.ofDays(1).toMillis();
+ final LogEvent logEvent2 = Log4jLogEvent
+ .newBuilder()
+ .setLoggerName(loggerName)
+ .setLoggerFqcn(loggerFqcn)
+ .setLevel(level)
+ .setMessage(message2)
+ .setTimeMillis(instantMillis2)
+ .build();
+
+ try (final RestHighLevelClient client = createClient()) {
+ final Appender appender = createStartedAppender(
+ JSON_TEMPLATE_GELF_LAYOUT,
+ MavenHardcodedConstants.LS_GELF_INPUT_PORT);
+ try {
+
+ // Append the event.
+ LOGGER.info("appending events");
+ appender.append(logEvent1);
+ appender.append(logEvent2);
+ LOGGER.info("completed appending events");
+
+ // Wait the message to arrive.
+ Awaitility
+ .await()
+ .atMost(Duration.ofSeconds(60))
+ .pollDelay(Duration.ofSeconds(2))
+ .until(() -> queryDocumentCount(client) == 2);
+
+ // Verify indexed messages.
+ final Set<String> expectedMessages = Stream
+ .of(logEvent1, logEvent2)
+ .map(LogstashIT::expectedLogstashMessageField)
+ .collect(Collectors.toSet());
+ final Set<String> actualMessages = queryDocuments(client)
+ .stream()
+ .map(source -> (String) source.get(ES_INDEX_MESSAGE_FIELD_NAME))
+ .filter(Objects::nonNull)
+ .collect(Collectors.toSet());
+ Assertions
+ .assertThat(actualMessages)
+ .isEqualTo(expectedMessages);
+
+ } finally {
+ appender.stop();
+ }
+ }
+
+ }
+
+ @Test
+ public void test_GelfLayout() throws IOException {
+
+ // Create log events.
+ final List<LogEvent> logEvents =
+ LogEventFixture.createFullLogEvents(LOG_EVENT_COUNT);
+
+ // Append log events and collect persisted sources.
+ final Function<Map<String, Object>, Integer> keyMapper =
+ (final Map<String, Object> source) -> {
+ final String timestamp = (String) source.get("timestamp");
+ final String shortMessage = (String) source.get("short_message");
+ final String fullMessage = (String) source.get("full_message");
+ return Objects.hash(timestamp, shortMessage, fullMessage);
+ };
+ final Map<Integer, Object> expectedSourceByKey =
+ appendAndCollect(
+ logEvents,
+ GELF_LAYOUT,
+ MavenHardcodedConstants.LS_GELF_INPUT_PORT,
+ keyMapper,
+ Collections.emptySet());
+ final Map<Integer, Object> actualSourceByKey =
+ appendAndCollect(
+ logEvents,
+ JSON_TEMPLATE_GELF_LAYOUT,
+ MavenHardcodedConstants.LS_GELF_INPUT_PORT,
+ keyMapper,
+ Collections.emptySet());
+
+ // Compare persisted sources.
+ Assertions.assertThat(actualSourceByKey).isEqualTo(expectedSourceByKey);
+
+ }
+
+ @Test
+ public void test_EcsLayout() throws IOException {
+
+ // Create log events.
+ final List<LogEvent> logEvents =
+ LogEventFixture.createFullLogEvents(LOG_EVENT_COUNT);
+
+ // Append log events and collect persisted sources.
+ final Function<Map<String, Object>, Integer> keyMapper =
+ (final Map<String, Object> source) -> {
+ final String timestamp = (String) source.get("@timestamp");
+ final String message = (String) source.get("message");
+ final String errorMessage = (String) source.get("error.message");
+ return Objects.hash(timestamp, message, errorMessage);
+ };
+ final Set<String> excludedKeys = Collections.singleton("port");
+ final Map<Integer, Object> expectedSourceByKey =
+ appendAndCollect(
+ logEvents,
+ ECS_LAYOUT,
+ MavenHardcodedConstants.LS_TCP_INPUT_PORT,
+ keyMapper,
+ excludedKeys);
+ final Map<Integer, Object> actualSourceByKey =
+ appendAndCollect(
+ logEvents,
+ JSON_TEMPLATE_ECS_LAYOUT,
+ MavenHardcodedConstants.LS_TCP_INPUT_PORT,
+ keyMapper,
+ excludedKeys);
+
+ // Compare persisted sources.
+ Assertions.assertThat(actualSourceByKey).isEqualTo(expectedSourceByKey);
+
+ }
+
+ private static <K> Map<K, Object> appendAndCollect(
+ final List<LogEvent> logEvents,
+ final Layout<?> layout,
+ final int port,
+ final Function<Map<String, Object>, K> keyMapper,
+ final Set<String> excludedKeys) throws IOException {
+ try (final RestHighLevelClient client = createClient()) {
+ final Appender appender = createStartedAppender(layout, port);
+ try {
+
+ // Append the event.
+ LOGGER.info("appending events");
+ logEvents.forEach(appender::append);
+ LOGGER.info("completed appending events");
+
+ // Wait the message to arrive.
+ Awaitility
+ .await()
+ .atMost(Duration.ofSeconds(60))
+ .pollDelay(Duration.ofSeconds(2))
+ .until(() -> queryDocumentCount(client) == LOG_EVENT_COUNT);
+
+ // Retrieve the persisted messages.
+ return queryDocuments(client)
+ .stream()
+ .collect(Collectors.toMap(
+ keyMapper,
+ (final Map<String, Object> source) -> {
+ excludedKeys.forEach(source::remove);
+ return source;
+ }));
+
+ } finally {
+ appender.stop();
+ }
+ }
+ }
+
+ private static RestHighLevelClient createClient() throws IOException {
+
+ // Instantiate the client.
+ LOGGER.info("instantiating the ES client");
+ final HttpHost httpHost = new HttpHost(HOST_NAME, MavenHardcodedConstants.ES_PORT);
+ final RestClientBuilder clientBuilder =
+ RestClient.builder(httpHost);
+ final RestHighLevelClient client = new RestHighLevelClient(clientBuilder);
+
+ // Verify the connection.
+ LOGGER.info("verifying the ES connection");
+ final ClusterHealthResponse healthResponse = client
+ .cluster()
+ .health(new ClusterHealthRequest(), RequestOptions.DEFAULT);
+ Assertions
+ .assertThat(healthResponse.getStatus())
+ .isNotEqualTo(ClusterHealthStatus.RED);
+
+ // Delete the index.
+ LOGGER.info("deleting the ES index");
+ final DeleteIndexRequest deleteRequest =
+ new DeleteIndexRequest(MavenHardcodedConstants.ES_INDEX_NAME);
+ try {
+ final AcknowledgedResponse deleteResponse = client
+ .indices()
+ .delete(deleteRequest, RequestOptions.DEFAULT);
+ Assertions
+ .assertThat(deleteResponse.isAcknowledged())
+ .isTrue();
+ } catch (ElasticsearchStatusException error) {
+ Assertions.assertThat(error)
+ .satisfies(ignored -> Assertions
+ .assertThat(error.status())
+ .isEqualTo(RestStatus.NOT_FOUND));
+ }
+
+ return client;
+
+ }
+
+ private static SocketAppender createStartedAppender(
+ final Layout<?> layout,
+ final int port) {
+ LOGGER.info("creating the appender");
+ final SocketAppender appender = SocketAppender
+ .newBuilder()
+ .setConfiguration(CONFIGURATION)
+ .setHost(HOST_NAME)
+ .setPort(port)
+ .setReconnectDelayMillis(100)
+ .setName("LogstashItAppender")
+ .setBufferedIo(false)
+ .setImmediateFail(true)
+ .setIgnoreExceptions(false)
+ .setLayout(layout)
+ .build();
+ appender.start();
+ return appender;
+ }
+
+ private static long queryDocumentCount(
+ final RestHighLevelClient client)
+ throws IOException {
+ final SearchSourceBuilder searchSourceBuilder =
+ new SearchSourceBuilder()
+ .size(0)
+ .fetchSource(false);
+ final SearchRequest searchRequest =
+ new SearchRequest(MavenHardcodedConstants.ES_INDEX_NAME)
+ .source(searchSourceBuilder);
+ try {
+ final SearchResponse searchResponse =
+ client.search(searchRequest, RequestOptions.DEFAULT);
+ return searchResponse.getHits().getTotalHits().value;
+ } catch (ElasticsearchStatusException error) {
+ if (RestStatus.NOT_FOUND.equals(error.status())) {
+ return 0L;
+ }
+ throw new IOException(error);
+ }
+ }
+
+ private static List<Map<String, Object>> queryDocuments(
+ final RestHighLevelClient client
+ ) throws IOException {
+ final SearchSourceBuilder searchSourceBuilder =
+ new SearchSourceBuilder()
+ .size(LOG_EVENT_COUNT)
+ .fetchSource(true);
+ final SearchRequest searchRequest =
+ new SearchRequest(MavenHardcodedConstants.ES_INDEX_NAME)
+ .source(searchSourceBuilder);
+ try {
+ final SearchResponse searchResponse =
+ client.search(searchRequest, RequestOptions.DEFAULT);
+ return Arrays
+ .stream(searchResponse.getHits().getHits())
+ .map(SearchHit::getSourceAsMap)
+ .collect(Collectors.toList());
+ } catch (ElasticsearchStatusException error) {
+ if (RestStatus.NOT_FOUND.equals(error.status())) {
+ return Collections.emptyList();
+ }
+ throw new IOException(error);
+ }
+ }
+
+}
diff --git a/log4j-layout-json-template/src/test/java/org/apache/logging/log4j/layout/json/template/util/JsonReaderTest.java b/log4j-layout-json-template/src/test/java/org/apache/logging/log4j/layout/json/template/util/JsonReaderTest.java
new file mode 100644
index 0000000..aa4c40e
--- /dev/null
+++ b/log4j-layout-json-template/src/test/java/org/apache/logging/log4j/layout/json/template/util/JsonReaderTest.java
@@ -0,0 +1,380 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.logging.log4j.layout.json.template.util;
+
+import org.assertj.core.api.Assertions;
+import org.junit.Test;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+
+public class JsonReaderTest {
+
+ @Test
+ public void test_valid_null() {
+ test("null", null);
+ test("[null, null]", Arrays.asList(null, null));
+ }
+
+ @Test
+ public void test_invalid_null() {
+ for (final String json : new String[]{"nuL", "nulL", "nul1"}) {
+ Assertions
+ .assertThatThrownBy(() -> JsonReader.read(json))
+ .as("json=%s", json)
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageStartingWith("was expecting keyword 'null' at index");
+
+ }
+ }
+
+ @Test
+ public void test_valid_boolean() {
+ test("true", true);
+ test("false", false);
+ test("[true, false]", Arrays.asList(true, false));
+ }
+
+ @Test
+ public void test_invalid_boolean() {
+ for (final String json : new String[]{"tru", "truE", "fals", "falsE"}) {
+ Assertions
+ .assertThatThrownBy(() -> JsonReader.read(json))
+ .as("json=%s", json)
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageMatching("^was expecting keyword '(true|false)' at index [0-9]+: .*$");
+
+ }
+ }
+
+ @Test
+ public void test_valid_string() {
+ test("\"\"", "");
+ test("\" \"", " ");
+ test("\" a\"", " a");
+ test("\"a \"", "a ");
+ test("\"abc\"", "abc");
+ test("\"abc\\\"\"", "abc\"");
+ test("\"\\b\\f\\n\\r\\t\"", "\b\f\n\r\t");
+ }
+
+ @Test
+ public void test_invalid_string_start() {
+ final String json = "abc\"";
+ Assertions
+ .assertThatThrownBy(() -> JsonReader.read(json))
+ .as("json=%s", json)
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage("invalid character at index 0: a");
+ }
+
+ @Test
+ public void test_invalid_string_end() {
+ for (final String json : new String[]{"", " ", "\r", "\t", "\"abc"}) {
+ Assertions
+ .assertThatThrownBy(() -> JsonReader.read(json))
+ .as("json=%s", json)
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage("premature end of input");
+ }
+ }
+
+ @Test
+ public void test_invalid_string_escape() {
+ for (final String json : new String[]{"\"\\k\"", "\"\\d\""}) {
+ Assertions
+ .assertThatThrownBy(() -> JsonReader.read(json))
+ .as("json=%s", json)
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageStartingWith(
+ "was expecting an escape character at index 2: ");
+ }
+ }
+
+ @Test
+ public void test_invalid_string_concat() {
+ final String json = "\"foo\"\"bar\"";
+ Assertions
+ .assertThatThrownBy(() -> JsonReader.read(json))
+ .as("json=%s", json)
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage("was not expecting input at index 5: \"");
+ }
+
+ @Test
+ public void test_valid_unicode_string() {
+ final String json = "\"a\\u00eF4bc\"";
+ Assertions
+ .assertThat(JsonReader.read(json))
+ .as("json=%s", json)
+ .isEqualTo("a\u00ef4bc");
+ }
+
+ @Test
+ public void test_invalid_unicode() {
+ Assertions
+ .assertThatThrownBy(() -> JsonReader.read("\"\\u000x\""))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage("was expecting a unicode character at index 6: x");
+ }
+
+ @Test
+ public void test_valid_integers() {
+ for (final String integer : new String[]{
+ "0",
+ "1",
+ "" + Long.MAX_VALUE + "" + Long.MAX_VALUE}) {
+ for (final String signedInteger : new String[]{integer, '-' + integer}) {
+ final Object expectedToken =
+ signedInteger.length() < 3
+ ? Integer.parseInt(signedInteger)
+ : new BigInteger(signedInteger);
+ test(signedInteger, expectedToken);
+ }
+ }
+ }
+
+ @Test
+ public void test_invalid_integers() {
+ for (final String integer : new String[]{
+ "0-",
+ "1a"}) {
+ for (final String signedInteger : new String[]{integer, '-' + integer}) {
+ Assertions
+ .assertThatThrownBy(() -> JsonReader.read(signedInteger))
+ .as("signedInteger=%s", signedInteger)
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageStartingWith("was not expecting input at index");
+ }
+ }
+ }
+
+ @Test
+ public void test_valid_decimals() {
+ for (final String decimal : new String[]{
+ "0.0",
+ "1.0",
+ "1.2",
+ "1e2",
+ "1e-2",
+ "1.2e3",
+ "1.2e-3"}) {
+ for (final String signedDecimal : new String[]{decimal, '-' + decimal}) {
+ test(signedDecimal, new BigDecimal(signedDecimal));
+ }
+ }
+ }
+
+ @Test
+ public void test_invalid_decimals() {
+ for (final String decimal : new String[]{
+ "0.",
+ ".1",
+ "1e",
+ "1e-",
+ "1.2e",
+ "1.2e-"}) {
+ for (final String signedDecimal : new String[]{decimal, '-' + decimal}) {
+ Assertions
+ .assertThatThrownBy(() -> JsonReader.read(signedDecimal))
+ .as("signedDecimal=%s", signedDecimal)
+ .isInstanceOf(IllegalArgumentException.class);
+ }
+ }
+ }
+
+ @Test
+ public void test_valid_arrays() {
+ for (final String json : new String[]{
+ "[]",
+ "[ ]"}) {
+ test(json, Collections.emptyList());
+ }
+ for (final String json : new String[]{
+ "[1]",
+ "[ 1]",
+ "[1 ]",
+ "[ 1 ]"}) {
+ test(json, Collections.singletonList(1));
+ }
+ for (final String json : new String[]{
+ "[1,2]",
+ "[1, 2]",
+ "[ 1, 2]",
+ "[1 , 2]",
+ "[ 1 , 2 ]"}) {
+ test(json, Arrays.asList(1, 2));
+ }
+ }
+
+ @Test
+ public void test_invalid_array_start() {
+ final String json = "[";
+ Assertions
+ .assertThatThrownBy(() -> JsonReader.read(json))
+ .as("json=%s", json)
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage("premature end of input");
+ }
+
+ @Test
+ public void test_invalid_array_end_1() {
+ final String json = "]";
+ Assertions
+ .assertThatThrownBy(() -> JsonReader.read(json))
+ .as("json=%s", json)
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage("was not expecting ARRAY_END at index 0");
+ }
+
+ @Test
+ public void test_invalid_array_comma() {
+ final String json = "[,";
+ Assertions
+ .assertThatThrownBy(() -> JsonReader.read(json))
+ .as("json=%s", json)
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage("was expecting an array element at index 1: COMMA");
+ }
+
+ @Test
+ public void test_invalid_array_end_2() {
+ final String json = "[1,";
+ Assertions
+ .assertThatThrownBy(() -> JsonReader.read(json))
+ .as("json=%s", json)
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage("premature end of input");
+ }
+
+ @Test
+ public void test_invalid_array_end_3() {
+ final String json = "[1,]";
+ Assertions
+ .assertThatThrownBy(() -> JsonReader.read(json))
+ .as("json=%s", json)
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage("was expecting an array element at index 3: ARRAY_END");
+ }
+
+ @Test
+ public void test_valid_objects() {
+ test("{}", Collections.emptyMap());
+ test("{\"foo\":\"bar\"}", Collections.singletonMap("foo", "bar"));
+ }
+
+ @Test
+ public void test_invalid_object_start() {
+ final String json = "{";
+ Assertions
+ .assertThatThrownBy(() -> JsonReader.read(json))
+ .as("json=%s", json)
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage("premature end of input");
+ }
+
+ @Test
+ public void test_invalid_object_end() {
+ final String json = "}";
+ Assertions
+ .assertThatThrownBy(() -> JsonReader.read(json))
+ .as("json=%s", json)
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage("was not expecting OBJECT_END at index 0");
+ }
+
+ @Test
+ public void test_invalid_object_colon_1() {
+ final String json = "{\"foo\"\"bar\"}";
+ Assertions
+ .assertThatThrownBy(() -> JsonReader.read(json))
+ .as("json=%s", json)
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage("was expecting COLON at index 6: bar");
+ }
+
+ @Test
+ public void test_invalid_object_colon_2() {
+ final String json = "{\"foo\":}";
+ Assertions
+ .assertThatThrownBy(() -> JsonReader.read(json))
+ .as("json=%s", json)
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage("premature end of input");
+ }
+
+ @Test
+ public void test_invalid_object_token() {
+ final String json = "{\"foo\":\"bar}";
+ Assertions
+ .assertThatThrownBy(() -> JsonReader.read(json))
+ .as("json=%s", json)
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage("premature end of input");
+ }
+
+ @Test
+ public void test_invalid_object_comma() {
+ final String json = "{\"foo\":\"bar\",}";
+ Assertions
+ .assertThatThrownBy(() -> JsonReader.read(json))
+ .as("json=%s", json)
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage("was expecting an object key at index 13: OBJECT_END");
+ }
+
+ @Test
+ public void test_invalid_object_key() {
+ final String json = "{\"foo\":\"bar\",]}";
+ Assertions
+ .assertThatThrownBy(() -> JsonReader.read(json))
+ .as("json=%s", json)
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage("was expecting an object key at index 13: ARRAY_END");
+ }
+
+ @Test
+ @SuppressWarnings("DoubleBraceInitialization")
+ public void test_nesting() {
+ test(
+ "{\"k1\": [true, null, 1e5, {\"k2\": \"v2\", \"k3\": {\"k4\": \"v4\"}}]}",
+ Collections.singletonMap(
+ "k1",
+ Arrays.asList(
+ true,
+ null,
+ new BigDecimal("1e5"),
+ new LinkedHashMap<String, Object>() {{
+ put("k2", "v2");
+ put("k3", Collections.singletonMap("k4", "v4"));
+ }})));
+ }
+
+ private void test(final String json, final Object expected) {
+ // Wrapping the assertion one more time to decorate it with the input.
+ Assertions
+ .assertThatCode(() -> Assertions
+ .assertThat(JsonReader.read(json))
+ .isEqualTo(expected))
+ .as("json=%s", json)
+ .doesNotThrowAnyException();
+ }
+
+}
diff --git a/log4j-layout-json-template/src/test/java/org/apache/logging/log4j/layout/json/template/util/JsonWriterTest.java b/log4j-layout-json-template/src/test/java/org/apache/logging/log4j/layout/json/template/util/JsonWriterTest.java
new file mode 100644
index 0000000..99451d3
--- /dev/null
+++ b/log4j-layout-json-template/src/test/java/org/apache/logging/log4j/layout/json/template/util/JsonWriterTest.java
@@ -0,0 +1,729 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.logging.log4j.layout.json.template.util;
+
+import org.apache.logging.log4j.core.impl.JdkMapAdapterStringMap;
+import org.apache.logging.log4j.layout.json.template.JacksonFixture;
+import org.apache.logging.log4j.util.BiConsumer;
+import org.apache.logging.log4j.util.IndexedReadOnlyStringMap;
+import org.apache.logging.log4j.util.SortedArrayStringMap;
+import org.apache.logging.log4j.util.StringBuilderFormattable;
+import org.apache.logging.log4j.util.StringMap;
+import org.apache.logging.log4j.util.Strings;
+import org.assertj.core.api.Assertions;
+import org.assertj.core.api.SoftAssertions;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Function;
+
+@SuppressWarnings("DoubleBraceInitialization")
+public class JsonWriterTest {
+
+ private static final JsonWriter WRITER = JsonWriter
+ .newBuilder()
+ .setMaxStringLength(128)
+ .setTruncatedStringSuffix("~")
+ .build();
+
+ @Test
+ public void test_writeValue_null_Object() {
+ expectNull(() -> WRITER.writeValue(null));
+ }
+
+ @Test
+ public void test_writeValue() {
+ final Object value = Collections.singletonMap("a", "b");
+ final String expectedJson = "{'a':'b'}".replace('\'', '"');
+ final String actualJson = WRITER.use(() -> WRITER.writeValue(value));
+ Assertions.assertThat(actualJson).isEqualTo(expectedJson);
+ }
+
+ @Test
+ public void test_writeObject_null_StringMap() {
+ expectNull(() -> WRITER.writeObject((StringMap) null));
+ }
+
+ @Test
+ public void test_writeObject_StringMap() {
+ final StringMap map = new JdkMapAdapterStringMap(Collections.singletonMap("a", "b"));
+ final String expectedJson = "{'a':'b'}".replace('\'', '"');
+ final String actualJson = WRITER.use(() -> WRITER.writeObject(map));
+ Assertions.assertThat(actualJson).isEqualTo(expectedJson);
+ }
+
+ @Test
+ public void test_writeObject_null_IndexedReadOnlyStringMap() {
+ expectNull(() -> WRITER.writeObject((IndexedReadOnlyStringMap) null));
+ }
+
+ @Test
+ public void test_writeObject_IndexedReadOnlyStringMap() {
+ final IndexedReadOnlyStringMap map =
+ new SortedArrayStringMap(new LinkedHashMap<String, Object>() {{
+ put("buzz", 1.2D);
+ put("foo", "bar");
+ }});
+ final String expectedJson = "{'buzz':1.2,'foo':'bar'}".replace('\'', '"');
+ final String actualJson = WRITER.use(() -> WRITER.writeObject(map));
+ Assertions.assertThat(actualJson).isEqualTo(expectedJson);
+ }
+
+ @Test
+ public void test_writeObject_null_Map() {
+ expectNull(() -> WRITER.writeObject((Map<String, Object>) null));
+ }
+
+ @Test
+ public void test_writeObject_Map() {
+ final Map<String, Object> map = new LinkedHashMap<String, Object>() {{
+ put("key1", "val1");
+ put("key2", Collections.singletonMap("key2.1", "val2.1"));
+ put("key3", Arrays.asList(
+ 3,
+ (byte) 127,
+ 4.5D,
+ 4.6F,
+ Arrays.asList(true, false),
+ new BigDecimal("30.12345678901234567890123456789"),
+ new BigInteger("12345678901234567890123456789"),
+ Collections.singleton('a'),
+ Collections.singletonMap("key3.3", "val3.3")));
+ put("key4", new LinkedHashMap<String, Object>() {{
+ put("chars", new char[]{'a', 'b', 'c'});
+ put("booleans", new boolean[]{true, false});
+ put("bytes", new byte[]{1, 2});
+ put("shorts", new short[]{3, 4});
+ put("ints", new int[]{256, 257});
+ put("longs", new long[]{2147483648L, 2147483649L});
+ put("floats", new float[]{1.0F, 1.1F});
+ put("doubles", new double[]{2.0D, 2.1D});
+ put("objects", new Object[]{"foo", "bar"});
+ }});
+ put("key5\t", new Object() {
+ @Override
+ public String toString() {
+ return "custom-object\r";
+ }
+ });
+ put("key6", Arrays.asList(
+ new SortedArrayStringMap(new LinkedHashMap<String, Object>() {{
+ put("buzz", 1.2D);
+ put("foo", "bar");
+ }}),
+ new JdkMapAdapterStringMap(Collections.singletonMap("a", "b"))));
+ put("key7", (StringBuilderFormattable) buffer ->
+ buffer.append(7.7777777777777D));
+ }};
+ final String expectedJson = ("{" +
+ "'key1':'val1'," +
+ "'key2':{'key2.1':'val2.1'}," +
+ "'key3':[" +
+ "3," +
+ "127," +
+ "4.5," +
+ "4.6," +
+ "[true,false]," +
+ "30.12345678901234567890123456789," +
+ "12345678901234567890123456789," +
+ "['a']," +
+ "{'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']" +
+ "}," +
+ "'key5\\t':'custom-object\\r'," +
+ "'key6':[{'buzz':1.2,'foo':'bar'},{'a':'b'}]," +
+ "'key7':'7.7777777777777'" +
+ "}").replace('\'', '"');
+ final String actualJson = WRITER.use(() -> WRITER.writeObject(map));
+ Assertions.assertThat(actualJson).isEqualTo(expectedJson);
+ }
+
+ @Test
+ public void test_writeArray_null_List() {
+ expectNull(() -> WRITER.writeArray((List<Object>) null));
+ }
+
+ @Test
+ public void test_writeArray_List() {
+ final List<Object> items = Arrays.asList(
+ 1, 2, 3,
+ "yo",
+ Collections.singletonMap("foo", "bar"));
+ final String expectedJson = "[1,2,3,\"yo\",{\"foo\":\"bar\"}]";
+ final String actualJson = WRITER.use(() -> WRITER.writeArray(items));
+ Assertions.assertThat(actualJson).isEqualTo(expectedJson);
+ }
+
+ @Test
+ public void test_writeArray_null_Collection() {
+ expectNull(() -> WRITER.writeArray((Collection<Object>) null));
+ }
+
+ @Test
+ public void test_writeArray_Collection() {
+ final Collection<Object> items = Arrays.asList(
+ 1, 2, 3,
+ Collections.singletonMap("foo", "bar"));
+ final String expectedJson = "[1,2,3,{\"foo\":\"bar\"}]";
+ final String actualJson = WRITER.use(() -> WRITER.writeArray(items));
+ Assertions.assertThat(actualJson).isEqualTo(expectedJson);
+ }
+
+ @Test
+ public void test_writeArray_null_char() {
+ expectNull(() -> WRITER.writeArray((char[]) null));
+ }
+
+ @Test
+ public void test_writeArray_char() {
+ final char[] items = {'\u0000', 'a', 'b', 'c', '\u007f'};
+ final String expectedJson = "[\"\\u0000\",\"a\",\"b\",\"c\",\"\u007F\"]";
+ final String actualJson = WRITER.use(() -> WRITER.writeArray(items));
+ Assertions.assertThat(actualJson).isEqualTo(expectedJson);
+ }
+
+ @Test
+ public void test_writeArray_null_boolean() {
+ expectNull(() -> WRITER.writeArray((boolean[]) null));
+ }
+
+ @Test
+ public void test_writeArray_boolean() {
+ final boolean[] items = {true, false};
+ final String expectedJson = "[true,false]";
+ final String actualJson = WRITER.use(() -> WRITER.writeArray(items));
+ Assertions.assertThat(actualJson).isEqualTo(expectedJson);
+ }
+
+ @Test
+ public void test_writeArray_null_byte() {
+ expectNull(() -> WRITER.writeArray((byte[]) null));
+ }
+
+ @Test
+ public void test_writeArray_byte() {
+ final byte[] items = {Byte.MIN_VALUE, -1, 0, 1, Byte.MAX_VALUE};
+ final String expectedJson = Arrays
+ .toString(items)
+ .replaceAll(" ", "");
+ final String actualJson = WRITER.use(() -> WRITER.writeArray(items));
+ Assertions.assertThat(actualJson).isEqualTo(expectedJson);
+ }
+
+ @Test
+ public void test_writeArray_null_short() {
+ expectNull(() -> WRITER.writeArray((short[]) null));
+ }
+
+ @Test
+ public void test_writeArray_short() {
+ final short[] items = {Short.MIN_VALUE, -1, 0, 1, Short.MAX_VALUE};
+ final String expectedJson = Arrays
+ .toString(items)
+ .replaceAll(" ", "");
+ final String actualJson = WRITER.use(() -> WRITER.writeArray(items));
+ Assertions.assertThat(actualJson).isEqualTo(expectedJson);
+ }
+
+ @Test
+ public void test_writeArray_null_int() {
+ expectNull(() -> WRITER.writeArray((int[]) null));
+ }
+
+ @Test
+ public void test_writeArray_int() {
+ final int[] items = {Integer.MIN_VALUE, -1, 0, 1, Integer.MAX_VALUE};
+ final String expectedJson = Arrays
+ .toString(items)
+ .replaceAll(" ", "");
+ final String actualJson = WRITER.use(() -> WRITER.writeArray(items));
+ Assertions.assertThat(actualJson).isEqualTo(expectedJson);
+ }
+
+ @Test
+ public void test_writeArray_null_long() {
+ expectNull(() -> WRITER.writeArray((long[]) null));
+ }
+
+ @Test
+ public void test_writeArray_long() {
+ final long[] items = {Long.MIN_VALUE, -1L, 0L, 1L, Long.MAX_VALUE};
+ final String expectedJson = Arrays
+ .toString(items)
+ .replaceAll(" ", "");
+ final String actualJson = WRITER.use(() -> WRITER.writeArray(items));
+ Assertions.assertThat(actualJson).isEqualTo(expectedJson);
+ }
+
+ @Test
+ public void test_writeArray_null_float() {
+ expectNull(() -> WRITER.writeArray((float[]) null));
+ }
+
+ @Test
+ public void test_writeArray_float() {
+ final float[] items = {Float.MIN_VALUE, -1F, 0F, 1F, Float.MAX_VALUE};
+ final String expectedJson = Arrays
+ .toString(items)
+ .replaceAll(" ", "");
+ final String actualJson = WRITER.use(() -> WRITER.writeArray(items));
+ Assertions.assertThat(actualJson).isEqualTo(expectedJson);
+ }
+
+ @Test
+ public void test_writeArray_null_double() {
+ expectNull(() -> WRITER.writeArray((double[]) null));
+ }
+
+ @Test
+ public void test_writeArray_double() {
+ final double[] items = {Double.MIN_VALUE, -1D, 0D, 1D, Double.MAX_VALUE};
+ final String expectedJson = Arrays
+ .toString(items)
+ .replaceAll(" ", "");
+ final String actualJson = WRITER.use(() -> WRITER.writeArray(items));
+ Assertions.assertThat(actualJson).isEqualTo(expectedJson);
+ }
+
+ @Test
+ public void test_writeArray_null_Object() {
+ expectNull(() -> WRITER.writeArray((Object[]) null));
+ }
+
+ @Test
+ public void test_writeArray_Object() {
+ final String expectedJson = "[\"foo\",{\"bar\":\"buzz\"},null]";
+ final String actualJson = WRITER.use(() ->
+ WRITER.writeArray(new Object[]{
+ "foo",
+ Collections.singletonMap("bar", "buzz"),
+ null
+ }));
+ Assertions.assertThat(actualJson).isEqualTo(expectedJson);
+ }
+
+ @Test
+ public void test_writeString_null_emitter() {
+ Assertions
+ .assertThatThrownBy(() ->
+ WRITER.use(() -> WRITER.writeString(null, 0L)))
+ .isInstanceOf(NullPointerException.class)
+ .hasMessageContaining("emitter");
+ }
+
+ @Test
+ public void test_writeString_emitter() {
+ final String state = "there-is-no-spoon";
+ final BiConsumer<StringBuilder, String> emitter = StringBuilder::append;
+ final String expectedJson = '"' + state + '"';
+ final String actualJson =
+ WRITER.use(() -> WRITER.writeString(emitter, state));
+ Assertions.assertThat(actualJson).isEqualTo(expectedJson);
+ }
+
+ @Test
+ public void test_writeString_emitter_excessive_string() {
+ final int maxStringLength = WRITER.getMaxStringLength();
+ final String excessiveString = Strings.repeat("x", maxStringLength) + 'y';
+ final String expectedJson = '"' +
+ excessiveString.substring(0, maxStringLength) +
+ WRITER.getTruncatedStringSuffix() +
+ '"';
+ final BiConsumer<StringBuilder, String> emitter = StringBuilder::append;
+ final String actualJson =
+ WRITER.use(() -> WRITER.writeString(emitter, excessiveString));
+ Assertions.assertThat(actualJson).isEqualTo(expectedJson);
+ }
+
+ @Test
+ public void test_writeString_null_formattable() {
+ expectNull(() -> WRITER.writeString((StringBuilderFormattable) null));
+ }
+
+ @Test
+ public void test_writeString_formattable() {
+ final String expectedJson = "\"foo\\tbar\\tbuzz\"";
+ @SuppressWarnings("Convert2Lambda")
+ final String actualJson = WRITER.use(() ->
+ WRITER.writeString(new StringBuilderFormattable() {
+ @Override
+ public void formatTo(StringBuilder stringBuilder) {
+ stringBuilder.append("foo\tbar\tbuzz");
+ }
+ }));
+ Assertions.assertThat(actualJson).isEqualTo(expectedJson);
+ }
+
+ @Test
+ public void test_writeString_formattable_excessive_string() {
+ final int maxStringLength = WRITER.getMaxStringLength();
+ final String excessiveString = Strings.repeat("x", maxStringLength) + 'y';
+ final String expectedJson = '"' +
+ excessiveString.substring(0, maxStringLength) +
+ WRITER.getTruncatedStringSuffix() +
+ '"';
+ @SuppressWarnings("Convert2Lambda")
+ final String actualJson = WRITER.use(() ->
+ WRITER.writeString(new StringBuilderFormattable() {
+ @Override
+ public void formatTo(StringBuilder stringBuilder) {
+ stringBuilder.append(excessiveString);
+ }
+ }));
+ Assertions.assertThat(actualJson).isEqualTo(expectedJson);
+ }
+
+ @Test
+ public void test_writeString_null_seq_1() {
+ expectNull(() -> WRITER.writeString((CharSequence) null));
+ }
+
+ @Test
+ public void test_writeString_null_seq_2() {
+ expectNull(() -> WRITER.writeString((CharSequence) null, 0, 4));
+ }
+
+ @Test
+ public void test_writeString_seq_negative_offset() {
+ Assertions
+ .assertThatThrownBy(() ->
+ WRITER.use(() -> WRITER.writeString("a", -1, 0)))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageContaining("offset");
+ }
+
+ @Test
+ public void test_writeString_seq_negative_length() {
+ Assertions
+ .assertThatThrownBy(() ->
+ WRITER.use(() -> WRITER.writeString("a", 0, -1)))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageContaining("length");
+ }
+
+ @Test
+ public void test_writeString_excessive_seq() {
+ final CharSequence seq = Strings.repeat("x", WRITER.getMaxStringLength()) + 'y';
+ final String expectedJson = "\"" +
+ Strings.repeat("x", WRITER.getMaxStringLength()) +
+ WRITER.getTruncatedStringSuffix() +
+ '"';
+ final String actualJson = WRITER.use(() -> WRITER.writeString(seq));
+ Assertions.assertThat(actualJson).isEqualTo(expectedJson);
+ }
+
+ @Test
+ public void test_writeString_seq() throws IOException {
+ testQuoting((final Character c) -> {
+ final String s = "" + c;
+ return WRITER.use(() -> WRITER.writeString(s));
+ });
+ }
+
+ @Test
+ public void test_writeString_null_buffer_1() {
+ expectNull(() -> WRITER.writeString((char[]) null));
+ }
+
+ @Test
+ public void test_writeString_null_buffer_2() {
+ expectNull(() -> WRITER.writeString((char[]) null, 0, 4));
+ }
+
+ @Test
+ public void test_writeString_buffer_negative_offset() {
+ Assertions
+ .assertThatThrownBy(() ->
+ WRITER.use(() -> WRITER.writeString(new char[]{'a'}, -1, 0)))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageContaining("offset");
+ }
+
+ @Test
+ public void test_writeString_buffer_negative_length() {
+ Assertions
+ .assertThatThrownBy(() ->
+ WRITER.use(() -> WRITER.writeString(new char[]{'a'}, 0, -1)))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageContaining("length");
+ }
+
+ @Test
+ public void test_writeString_excessive_buffer() {
+ final char[] buffer =
+ (Strings.repeat("x", WRITER.getMaxStringLength()) + 'y')
+ .toCharArray();
+ final String expectedJson = "\"" +
+ Strings.repeat("x", WRITER.getMaxStringLength()) +
+ WRITER.getTruncatedStringSuffix() +
+ '"';
+ final String actualJson = WRITER.use(() -> WRITER.writeString(buffer));
+ Assertions.assertThat(actualJson).isEqualTo(expectedJson);
+ }
+
+ @Test
+ public void test_writeString_buffer() throws IOException {
+ final char[] buffer = new char[1];
+ testQuoting((final Character c) -> {
+ buffer[0] = c;
+ return WRITER.use(() -> WRITER.writeString(buffer));
+ });
+ }
+
+ private void testQuoting(
+ final Function<Character, String> quoter) throws IOException {
+ final SoftAssertions assertions = new SoftAssertions();
+ for (char c = Character.MIN_VALUE;; c++) {
+ final String s = "" + c;
+ final String expectedJson = JacksonFixture
+ .getObjectMapper()
+ .writeValueAsString(s);
+ final String actualJson = quoter.apply(c);
+ assertions
+ .assertThat(actualJson)
+ .as("c='%c' (%d)", c, (int) c)
+ .isEqualTo(expectedJson);
+ if (c == Character.MAX_VALUE) {
+ break;
+ }
+ }
+ assertions.assertAll();
+ }
+
+ @Test
+ public void test_writeNumber_null_BigDecimal() {
+ expectNull(() -> WRITER.writeNumber((BigDecimal) null));
+ }
+
+ @Test
+ public void test_writeNumber_BigDecimal() {
+ for (final BigDecimal number : new BigDecimal[]{
+ BigDecimal.ZERO,
+ BigDecimal.ONE,
+ BigDecimal.TEN,
+ new BigDecimal("" + Long.MAX_VALUE +
+ "" + Long.MAX_VALUE +
+ '.' + Long.MAX_VALUE +
+ "" + Long.MAX_VALUE)}) {
+ final String expectedJson = String.valueOf(number);
+ final String actualJson = WRITER.use(() -> WRITER.writeNumber(number));
+ Assertions.assertThat(actualJson).isEqualTo(expectedJson);
+ }
+ }
+
+ @Test
+ public void test_writeNumber_null_BigInteger() {
+ expectNull(() -> WRITER.writeNumber((BigInteger) null));
+ }
+
+ @Test
+ public void test_writeNumber_BigInteger() {
+ for (final BigInteger number : new BigInteger[]{
+ BigInteger.ZERO,
+ BigInteger.ONE,
+ BigInteger.TEN,
+ new BigInteger("" + Long.MAX_VALUE + "" + Long.MAX_VALUE)}) {
+ final String expectedJson = String.valueOf(number);
+ final String actualJson = WRITER.use(() -> WRITER.writeNumber(number));
+ Assertions.assertThat(actualJson).isEqualTo(expectedJson);
+ }
+ }
+
+ @Test
+ public void test_writeNumber_float() {
+ for (final float number : new float[]{Float.MIN_VALUE, -1.0F, 0F, 1.0F, Float.MAX_VALUE}) {
+ final String expectedJson = String.valueOf(number);
+ final String actualJson = WRITER.use(() -> WRITER.writeNumber(number));
+ Assertions.assertThat(actualJson).isEqualTo(expectedJson);
+ }
+ }
+
+ @Test
+ public void test_writeNumber_double() {
+ for (final double number : new double[]{Double.MIN_VALUE, -1.0D, 0D, 1.0D, Double.MAX_VALUE}) {
+ final String expectedJson = String.valueOf(number);
+ final String actualJson = WRITER.use(() -> WRITER.writeNumber(number));
+ Assertions.assertThat(actualJson).isEqualTo(expectedJson);
+ }
+ }
+
+ @Test
+ public void test_writeNumber_short() {
+ for (final short number : new short[]{Short.MIN_VALUE, -1, 0, 1, Short.MAX_VALUE}) {
+ final String expectedJson = String.valueOf(number);
+ final String actualJson = WRITER.use(() -> WRITER.writeNumber(number));
+ Assertions.assertThat(actualJson).isEqualTo(expectedJson);
+ }
+ }
+
+ @Test
+ public void test_writeNumber_int() {
+ for (final int number : new int[]{Integer.MIN_VALUE, -1, 0, 1, Integer.MAX_VALUE}) {
+ final String expectedJson = String.valueOf(number);
+ final String actualJson = WRITER.use(() -> WRITER.writeNumber(number));
+ Assertions.assertThat(actualJson).isEqualTo(expectedJson);
+ }
+ }
+
+ @Test
+ public void test_writeNumber_long() {
+ for (final long number : new long[]{Long.MIN_VALUE, -1L, 0L, 1L, Long.MAX_VALUE}) {
+ final String expectedJson = String.valueOf(number);
+ final String actualJson = WRITER.use(() -> WRITER.writeNumber(number));
+ Assertions.assertThat(actualJson).isEqualTo(expectedJson);
+ }
+ }
+
+ @Test
+ public void test_writeNumber_integral_and_negative_fractional() {
+ Assertions
+ .assertThatThrownBy(() -> WRITER.use(() -> WRITER.writeNumber(0, -1)))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage("was expecting a positive fraction: -1");
+ }
+
+ @Test
+ public void test_writeNumber_integral_and_zero_fractional() {
+ final String expectedJson = "123";
+ final String actualJson = WRITER.use(() -> WRITER.writeNumber(123L, 0L));
+ Assertions.assertThat(actualJson).isEqualTo(expectedJson);
+ }
+
+ @Test
+ public void test_writeNumber_integral_and_fractional() {
+ final String expectedJson = "123.456";
+ final String actualJson = WRITER.use(() -> WRITER.writeNumber(123L, 456L));
+ Assertions.assertThat(actualJson).isEqualTo(expectedJson);
+ }
+
+ @Test
+ public void test_writeBoolean_true() {
+ final String expectedJson = "true";
+ final String actualJson = WRITER.use(() -> WRITER.writeBoolean(true));
+ Assertions.assertThat(actualJson).isEqualTo(expectedJson);
+ }
+
+ @Test
+ public void test_writeBoolean_false() {
+ final String expectedJson = "false";
+ final String actualJson = WRITER.use(() -> WRITER.writeBoolean(false));
+ Assertions.assertThat(actualJson).isEqualTo(expectedJson);
+ }
+
+ @Test
+ public void test_writeNull() {
+ expectNull(WRITER::writeNull);
+ }
+
+ private void expectNull(Runnable body) {
+ final String expectedJson = "null";
+ final String actualJson = WRITER.use(body);
+ Assertions.assertThat(actualJson).isEqualTo(expectedJson);
+ }
+
+ @Test
+ public void test_writeRawString_null_seq() {
+ Assertions
+ .assertThatThrownBy(() ->
+ WRITER.use(() ->
+ WRITER.writeRawString((String) null)))
+ .isInstanceOf(NullPointerException.class)
+ .hasMessage("seq");
+ }
+
+ @Test
+ public void test_writeRawString_seq_negative_offset() {
+ Assertions
+ .assertThatThrownBy(() ->
+ WRITER.use(() ->
+ WRITER.writeRawString("a", -1, 0)))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageContaining("offset");
+ }
+
+ @Test
+ public void test_writeRawString_seq_negative_length() {
+ Assertions
+ .assertThatThrownBy(() ->
+ WRITER.use(() ->
+ WRITER.writeRawString("a", 0, -1)))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageContaining("length");
+ }
+
+ @Test
+ public void test_writeRawString_seq() {
+ final String expectedJson = "this is not a valid JSON string";
+ final String actualJson = WRITER.use(() -> WRITER.writeRawString(expectedJson));
+ Assertions.assertThat(actualJson).isEqualTo(expectedJson);
+ }
+
+ @Test
+ public void test_writeRawString_null_buffer() {
+ Assertions
+ .assertThatThrownBy(() -> WRITER.use(() ->
+ WRITER.writeRawString((char[]) null)))
+ .isInstanceOf(NullPointerException.class)
+ .hasMessage("buffer");
+ }
+
+ @Test
+ public void test_writeRawString_buffer_negative_offset() {
+ Assertions
+ .assertThatThrownBy(() ->
+ WRITER.use(() ->
+ WRITER.writeRawString(new char[]{'a'}, -1, 0)))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageContaining("offset");
+ }
+
+ @Test
+ public void test_writeRawString_buffer_negative_length() {
+ Assertions
+ .assertThatThrownBy(() ->
+ WRITER.use(() ->
+ WRITER.writeRawString(new char[]{'a'}, 0, -1)))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageContaining("length");
+ }
+
+ @Test
+ public void test_writeRawString_buffer() {
+ final String expectedJson = "this is not a valid JSON string";
+ final String actualJson = WRITER.use(() -> WRITER.writeRawString(expectedJson.toCharArray()));
+ Assertions.assertThat(actualJson).isEqualTo(expectedJson);
+ }
+
+}
diff --git a/log4j-layout-json-template/src/test/java/org/apache/logging/log4j/layout/json/template/util/RecyclerFactoriesTest.java b/log4j-layout-json-template/src/test/java/org/apache/logging/log4j/layout/json/template/util/RecyclerFactoriesTest.java
new file mode 100644
index 0000000..bc42d8e
--- /dev/null
+++ b/log4j-layout-json-template/src/test/java/org/apache/logging/log4j/layout/json/template/util/RecyclerFactoriesTest.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.layout.json.template.util;
+
+import org.apache.logging.log4j.plugins.convert.TypeConverter;
+import org.apache.logging.log4j.plugins.convert.TypeConverterRegistry;
+import org.assertj.core.api.Assertions;
+import org.junit.Test;
+
+import java.util.ArrayDeque;
+import java.util.concurrent.ArrayBlockingQueue;
+
+public class RecyclerFactoriesTest {
+
+ @Test
+ public void test_RecyclerFactoryConverter() throws Exception {
+
+ // Check if the type converter is registered.
+ final TypeConverter<?> converter = TypeConverterRegistry
+ .getInstance()
+ .findCompatibleConverter(RecyclerFactory.class);
+ Assertions.assertThat(converter).isNotNull();
+
+ // Check dummy recycler factory.
+ {
+ final Object actualDummyRecyclerFactory = converter.convert("dummy");
+ Assertions
+ .assertThat(actualDummyRecyclerFactory)
+ .isSameAs(DummyRecyclerFactory.getInstance());
+ }
+
+ // Check thread-local recycler factory.
+ {
+ final Object actualThreadLocalRecyclerFactory = converter.convert("threadLocal");
+ Assertions
+ .assertThat(actualThreadLocalRecyclerFactory)
+ .isSameAs(ThreadLocalRecyclerFactory.getInstance());
+ }
+
+ // Check queueing recycler factory.
+ {
+ final Object actualQueueingRecyclerFactory = converter.convert("queue");
+ Assertions
+ .assertThat(actualQueueingRecyclerFactory)
+ .isInstanceOf(QueueingRecyclerFactory.class);
+ }
+
+ // Check queueing recycler factory with supplier.
+ {
+ final Object recyclerFactory = converter.convert(
+ "queue:supplier=java.util.ArrayDeque.new");
+ Assertions
+ .assertThat(recyclerFactory)
+ .isInstanceOf(QueueingRecyclerFactory.class);
+ final QueueingRecyclerFactory queueingRecyclerFactory =
+ (QueueingRecyclerFactory) recyclerFactory;
+ final Recycler<Object> recycler =
+ queueingRecyclerFactory.create(Object::new);
+ Assertions
+ .assertThat(recycler)
+ .isInstanceOf(QueueingRecycler.class);
+ final QueueingRecycler<Object> queueingRecycler =
+ (QueueingRecycler<Object>) recycler;
+ Assertions
+ .assertThat(queueingRecycler.getQueue())
+ .isInstanceOf(ArrayDeque.class);
+ }
+
+ // Check queueing recycler factory with capacity.
+ {
+ final Object actualQueueingRecyclerFactory = converter.convert(
+ "queue:capacity=100");
+ Assertions
+ .assertThat(actualQueueingRecyclerFactory)
+ .isInstanceOf(QueueingRecyclerFactory.class);
+ }
+
+ // Check queueing recycler factory with supplier and capacity.
+ {
+ final Object recyclerFactory = converter.convert(
+ "queue:" +
+ "supplier=java.util.concurrent.ArrayBlockingQueue.new," +
+ "capacity=100");
+ Assertions
+ .assertThat(recyclerFactory)
+ .isInstanceOf(QueueingRecyclerFactory.class);
+ final QueueingRecyclerFactory queueingRecyclerFactory =
+ (QueueingRecyclerFactory) recyclerFactory;
+ final Recycler<Object> recycler =
+ queueingRecyclerFactory.create(Object::new);
+ Assertions
+ .assertThat(recycler)
+ .isInstanceOf(QueueingRecycler.class);
+ final QueueingRecycler<Object> queueingRecycler =
+ (QueueingRecycler<Object>) recycler;
+ Assertions
+ .assertThat(queueingRecycler.getQueue())
+ .isInstanceOf(ArrayBlockingQueue.class);
+ final ArrayBlockingQueue<Object> queue =
+ (ArrayBlockingQueue<Object>) queueingRecycler.getQueue();
+ Assertions.assertThat(queue.remainingCapacity()).isEqualTo(100);
+ }
+
+ }
+
+}
diff --git a/log4j-layout-json-template/src/test/java/org/apache/logging/log4j/layout/json/template/util/StringParameterParserTest.java b/log4j-layout-json-template/src/test/java/org/apache/logging/log4j/layout/json/template/util/StringParameterParserTest.java
new file mode 100644
index 0000000..5930b99
--- /dev/null
+++ b/log4j-layout-json-template/src/test/java/org/apache/logging/log4j/layout/json/template/util/StringParameterParserTest.java
@@ -0,0 +1,393 @@
+package org.apache.logging.log4j.layout.json.template.util;
+
+import org.apache.logging.log4j.layout.json.template.util.StringParameterParser.DoubleQuotedStringValue;
+import org.apache.logging.log4j.layout.json.template.util.StringParameterParser.NullValue;
+import org.apache.logging.log4j.layout.json.template.util.StringParameterParser.StringValue;
+import org.apache.logging.log4j.layout.json.template.util.StringParameterParser.Value;
+import org.apache.logging.log4j.layout.json.template.util.StringParameterParser.Values;
+import org.assertj.core.api.Assertions;
+import org.junit.Test;
+
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+
+public class StringParameterParserTest {
+
+ @Test
+ public void test_empty_string() {
+ testSuccess(
+ "",
+ Collections.emptyMap());
+ }
+
+ @Test
+ public void test_blank_string() {
+ testSuccess(
+ "\t",
+ Collections.emptyMap());
+ }
+
+ @Test
+ public void test_simple_pair() {
+ testSuccess(
+ "a=b",
+ Collections.singletonMap("a", Values.stringValue("b")));
+ }
+
+ @Test
+ public void test_simple_pair_with_whitespace_1() {
+ testSuccess(
+ " a=b",
+ Collections.singletonMap("a", Values.stringValue("b")));
+ }
+
+ @Test
+ public void test_simple_pair_with_whitespace_2() {
+ testSuccess(
+ " a =b",
+ Collections.singletonMap("a", Values.stringValue("b")));
+ }
+
+ @Test
+ public void test_simple_pair_with_whitespace_3() {
+ testSuccess(
+ " a = b",
+ Collections.singletonMap("a", Values.stringValue("b")));
+ }
+
+ @Test
+ public void test_simple_pair_with_whitespace_4() {
+ testSuccess(
+ " a = b ",
+ Collections.singletonMap("a", Values.stringValue("b")));
+ }
+
+ @Test
+ public void test_null_value_1() {
+ testSuccess(
+ "a",
+ Collections.singletonMap("a", Values.nullValue()));
+ }
+
+ @Test
+ public void test_null_value_2() {
+ testSuccess(
+ "a,b=c,d=",
+ new LinkedHashMap<String, Value>() {{
+ put("a", Values.nullValue());
+ put("b", Values.stringValue("c"));
+ put("d", Values.nullValue());
+ }});
+ }
+
+ @Test
+ public void test_null_value_3() {
+ testSuccess(
+ "a,b=c,d",
+ new LinkedHashMap<String, Value>() {{
+ put("a", Values.nullValue());
+ put("b", Values.stringValue("c"));
+ put("d", Values.nullValue());
+ }});
+ }
+
+ @Test
+ public void test_null_value_4() {
+ testSuccess(
+ "a,b=\"c,=\\\"\",d=,e=f",
+ new LinkedHashMap<String, Value>() {{
+ put("a", Values.nullValue());
+ put("b", Values.doubleQuotedStringValue("c,=\""));
+ put("d", Values.nullValue());
+ put("e", Values.stringValue("f"));
+ }});
+ }
+
+ @Test
+ public void test_two_pairs() {
+ testSuccess(
+ "a=b,c=d",
+ new LinkedHashMap<String, Value>() {{
+ put("a", Values.stringValue("b"));
+ put("c", Values.stringValue("d"));
+ }});
+ }
+
+ @Test
+ public void test_quoted_string_01() {
+ testSuccess(
+ "a=\"b\"",
+ Collections.singletonMap("a", Values.doubleQuotedStringValue("b")));
+ }
+
+ @Test
+ public void test_quoted_string_02() {
+ testSuccess(
+ "a=\"b\",c=d",
+ new LinkedHashMap<String, Value>() {{
+ put("a", Values.doubleQuotedStringValue("b"));
+ put("c", Values.stringValue("d"));
+ }});
+ }
+
+ @Test
+ public void test_quoted_string_03() {
+ testSuccess(
+ "a=b,c=\"d\"",
+ new LinkedHashMap<String, Value>() {{
+ put("a", Values.stringValue("b"));
+ put("c", Values.doubleQuotedStringValue("d"));
+ }});
+ }
+
+ @Test
+ public void test_quoted_string_04() {
+ testSuccess(
+ "a=\"b\",c=\"d\"",
+ new LinkedHashMap<String, Value>() {{
+ put("a", Values.doubleQuotedStringValue("b"));
+ put("c", Values.doubleQuotedStringValue("d"));
+ }});
+ }
+
+ @Test
+ public void test_quoted_string_05() {
+ testSuccess(
+ "a=\"\\\"b\"",
+ Collections.singletonMap("a", Values.doubleQuotedStringValue("\"b")));
+ }
+
+ @Test
+ public void test_quoted_string_06() {
+ testSuccess(
+ "a=\"\\\"b\\\"\"",
+ Collections.singletonMap("a", Values.doubleQuotedStringValue("\"b\"")));
+ }
+
+ @Test
+ public void test_quoted_string_07() {
+ testSuccess(
+ "a=\"\\\"b\",c=d",
+ new LinkedHashMap<String, Value>() {{
+ put("a", Values.doubleQuotedStringValue("\"b"));
+ put("c", Values.stringValue("d"));
+ }});
+ }
+
+ @Test
+ public void test_quoted_string_08() {
+ testSuccess(
+ "a=\"\\\"b\\\"\",c=d",
+ new LinkedHashMap<String, Value>() {{
+ put("a", Values.doubleQuotedStringValue("\"b\""));
+ put("c", Values.stringValue("d"));
+ }});
+ }
+
+ @Test
+ public void test_quoted_string_09() {
+ testSuccess(
+ "a=\"\\\"b,\",c=d",
+ new LinkedHashMap<String, Value>() {{
+ put("a", Values.doubleQuotedStringValue("\"b,"));
+ put("c", Values.stringValue("d"));
+ }});
+ }
+
+ @Test
+ public void test_quoted_string_10() {
+ testSuccess(
+ "a=\"\\\"b\\\",\",c=d",
+ new LinkedHashMap<String, Value>() {{
+ put("a", Values.doubleQuotedStringValue("\"b\","));
+ put("c", Values.stringValue("d"));
+ }});
+ }
+
+ @Test
+ public void test_quoted_string_11() {
+ testSuccess(
+ "a=\"\\\"b\",c=\"d\"",
+ new LinkedHashMap<String, Value>() {{
+ put("a", Values.doubleQuotedStringValue("\"b"));
+ put("c", Values.doubleQuotedStringValue("d"));
+ }});
+ }
+
+ @Test
+ public void test_quoted_string_12() {
+ testSuccess(
+ "a=\"\\\"b\\\"\",c=\"d\"",
+ new LinkedHashMap<String, Value>() {{
+ put("a", Values.doubleQuotedStringValue("\"b\""));
+ put("c", Values.doubleQuotedStringValue("d"));
+ }});
+ }
+
+ @Test
+ public void test_quoted_string_13() {
+ testSuccess(
+ "a=\"\\\"b,\",c=\"\\\"d\"",
+ new LinkedHashMap<String, Value>() {{
+ put("a", Values.doubleQuotedStringValue("\"b,"));
+ put("c", Values.doubleQuotedStringValue("\"d"));
+ }});
+ }
+
+ @Test
+ public void test_quoted_string_14() {
+ testSuccess(
+ "a=\"\\\"b\\\",\",c=\"\\\"d\\\"\"",
+ new LinkedHashMap<String, Value>() {{
+ put("a", Values.doubleQuotedStringValue("\"b\","));
+ put("c", Values.doubleQuotedStringValue("\"d\""));
+ }});
+ }
+
+ @Test
+ public void test_quoted_string_15() {
+ testSuccess(
+ "a=\"\\\"b\",c=\",d\"",
+ new LinkedHashMap<String, Value>() {{
+ put("a", Values.doubleQuotedStringValue("\"b"));
+ put("c", Values.doubleQuotedStringValue(",d"));
+ }});
+ }
+
+ @Test
+ public void test_quoted_string_16() {
+ testSuccess(
+ "a=\"\\\"b\\\"\",c=\",d\"",
+ new LinkedHashMap<String, Value>() {{
+ put("a", Values.doubleQuotedStringValue("\"b\""));
+ put("c", Values.doubleQuotedStringValue(",d"));
+ }});
+ }
+
+ @Test
+ public void test_quoted_string_17() {
+ testSuccess(
+ "a=\"\\\"b,\",c=\"\\\"d,\"",
+ new LinkedHashMap<String, Value>() {{
+ put("a", Values.doubleQuotedStringValue("\"b,"));
+ put("c", Values.doubleQuotedStringValue("\"d,"));
+ }});
+ }
+
+ @Test
+ public void test_quoted_string_18() {
+ testSuccess(
+ "a=\"\\\"b\\\",\",c=\"\\\"d\\\",\"",
+ new LinkedHashMap<String, Value>() {{
+ put("a", Values.doubleQuotedStringValue("\"b\","));
+ put("c", Values.doubleQuotedStringValue("\"d\","));
+ }});
+ }
+
+ private static void testSuccess(
+ final String input,
+ final Map<String, Value> expectedMap) {
+ final Map<String, Value> actualMap = StringParameterParser.parse(input);
+ Assertions
+ .assertThat(actualMap)
+ .as("input: %s", input)
+ .isEqualTo(expectedMap);
+ }
+
+ @Test
+ public void test_missing_key() {
+ Assertions
+ .assertThatThrownBy(() -> {
+ final String input = ",a=b";
+ StringParameterParser.parse(input);
+ })
+ .hasMessageStartingWith("failed to locate key at index 0");
+ }
+
+ @Test
+ public void test_conflicting_key() {
+ Assertions
+ .assertThatThrownBy(() -> {
+ final String input = "a,a";
+ StringParameterParser.parse(input);
+ })
+ .hasMessageStartingWith("conflicting key at index 2");
+ }
+
+ @Test
+ public void test_prematurely_ending_quoted_string_01() {
+ Assertions
+ .assertThatThrownBy(() -> {
+ final String input = "a,b=\"";
+ StringParameterParser.parse(input);
+ })
+ .hasMessageStartingWith("failed to locate the end of double-quoted content starting at index 4");
+ }
+
+ @Test
+ public void test_prematurely_ending_quoted_string_02() {
+ Assertions
+ .assertThatThrownBy(() -> {
+ final String input = "a,b=\"c";
+ StringParameterParser.parse(input);
+ })
+ .hasMessageStartingWith("failed to locate the end of double-quoted content starting at index 4");
+ }
+
+ @Test
+ public void test_prematurely_ending_quoted_string_03() {
+ Assertions
+ .assertThatThrownBy(() -> {
+ final String input = "a,b=\",c";
+ StringParameterParser.parse(input);
+ })
+ .hasMessageStartingWith("failed to locate the end of double-quoted content starting at index 4");
+ }
+
+ @Test
+ public void test_prematurely_ending_quoted_string_04() {
+ Assertions
+ .assertThatThrownBy(() -> {
+ final String input = "a,b=\",c\" x";
+ StringParameterParser.parse(input);
+ })
+ .hasMessageStartingWith("was expecting comma at index 9");
+ }
+
+ @Test
+ public void test_NullValue_toString() {
+ final Map<String, Value> map = StringParameterParser.parse("a");
+ final NullValue value = (NullValue) map.get("a");
+ Assertions.assertThat(value.toString()).isEqualTo(null);
+ }
+
+ @Test
+ public void test_StringValue_toString() {
+ final Map<String, Value> map = StringParameterParser.parse("a=b");
+ final StringValue value = (StringValue) map.get("a");
+ Assertions.assertThat(value.toString()).isEqualTo("b");
+ }
+
+ @Test
+ public void test_DoubleQuotedStringValue_toString() {
+ final Map<String, Value> map = StringParameterParser.parse("a=\"\\\"b\"");
+ final DoubleQuotedStringValue value = (DoubleQuotedStringValue) map.get("a");
+ Assertions.assertThat(value.toString()).isEqualTo("\"b");
+ }
+
+ @Test
+ public void test_allowedKeys() {
+ Assertions
+ .assertThatThrownBy(() -> {
+ final String input = "a,b";
+ final Set<String> allowedKeys =
+ new LinkedHashSet<>(Collections.singletonList("a"));
+ StringParameterParser.parse(input, allowedKeys);
+ })
+ .hasMessageStartingWith("unknown key \"b\" is found in input: a,b");
+ }
+
+}
diff --git a/log4j-layout-json-template/src/test/java/org/apache/logging/log4j/layout/json/template/util/TruncatingBufferedWriterTest.java b/log4j-layout-json-template/src/test/java/org/apache/logging/log4j/layout/json/template/util/TruncatingBufferedWriterTest.java
new file mode 100644
index 0000000..0373e75
--- /dev/null
+++ b/log4j-layout-json-template/src/test/java/org/apache/logging/log4j/layout/json/template/util/TruncatingBufferedWriterTest.java
@@ -0,0 +1,228 @@
+package org.apache.logging.log4j.layout.json.template.util;
+
+import org.assertj.core.api.Assertions;
+import org.junit.Test;
+
+public class TruncatingBufferedWriterTest {
+
+ @Test
+ public void test_ctor_invalid_args() {
+ Assertions
+ .assertThatThrownBy(() -> new TruncatingBufferedWriter(-1))
+ .isInstanceOf(NegativeArraySizeException.class);
+ }
+
+ @Test
+ public void test_okay_payloads() {
+
+ // Fill in the writer.
+ final int capacity = 1_000;
+ final TruncatingBufferedWriter writer = new TruncatingBufferedWriter(capacity);
+ writer.write(Character.MAX_VALUE);
+ writer.write(new char[]{Character.MIN_VALUE, Character.MAX_VALUE});
+ writer.write("foo");
+ writer.write("foobar", 3, 3);
+ writer.write(new char[]{'f', 'o', 'o', 'b', 'a', 'r', 'b', 'u', 'z', 'z'}, 6, 4);
+ writer.append('!');
+ writer.append("yo");
+ writer.append(null);
+ writer.append("yo dog", 3, 6);
+ writer.append(null, -1, -1);
+
+ // Verify accessors.
+ final char[] expectedBuffer = new char[capacity];
+ int expectedPosition = 0;
+ expectedBuffer[expectedPosition++] = Character.MAX_VALUE;
+ expectedBuffer[expectedPosition++] = Character.MIN_VALUE;
+ expectedBuffer[expectedPosition++] = Character.MAX_VALUE;
+ expectedBuffer[expectedPosition++] = 'f';
+ expectedBuffer[expectedPosition++] = 'o';
+ expectedBuffer[expectedPosition++] = 'o';
+ expectedBuffer[expectedPosition++] = 'b';
+ expectedBuffer[expectedPosition++] = 'a';
+ expectedBuffer[expectedPosition++] = 'r';
+ expectedBuffer[expectedPosition++] = 'b';
+ expectedBuffer[expectedPosition++] = 'u';
+ expectedBuffer[expectedPosition++] = 'z';
+ expectedBuffer[expectedPosition++] = 'z';
+ expectedBuffer[expectedPosition++] = '!';
+ expectedBuffer[expectedPosition++] = 'y';
+ expectedBuffer[expectedPosition++] = 'o';
+ expectedBuffer[expectedPosition++] = 'n';
+ expectedBuffer[expectedPosition++] = 'u';
+ expectedBuffer[expectedPosition++] = 'l';
+ expectedBuffer[expectedPosition++] = 'l';
+ expectedBuffer[expectedPosition++] = 'd';
+ expectedBuffer[expectedPosition++] = 'o';
+ expectedBuffer[expectedPosition++] = 'g';
+ expectedBuffer[expectedPosition++] = 'n';
+ expectedBuffer[expectedPosition++] = 'u';
+ expectedBuffer[expectedPosition++] = 'l';
+ expectedBuffer[expectedPosition++] = 'l';
+ Assertions.assertThat(writer.getBuffer()).isEqualTo(expectedBuffer);
+ Assertions.assertThat(writer.getPosition()).isEqualTo(expectedPosition);
+ Assertions.assertThat(writer.getCapacity()).isEqualTo(capacity);
+ Assertions.assertThat(writer.isTruncated()).isFalse();
+ verifyClose(writer);
+
+ }
+
+ @Test
+ public void test_write_int_truncation() {
+ final TruncatingBufferedWriter writer = new TruncatingBufferedWriter(1);
+ writer.write('a');
+ writer.write('b');
+ verifyTruncation(writer, 'a');
+ }
+
+ @Test
+ public void test_write_char_array_truncation() {
+ final TruncatingBufferedWriter writer = new TruncatingBufferedWriter(1);
+ writer.write(new char[]{'a', 'b'});
+ verifyTruncation(writer, 'a');
+ }
+
+ @Test
+ public void test_write_String_truncation() {
+ final TruncatingBufferedWriter writer = new TruncatingBufferedWriter(1);
+ writer.write("ab");
+ verifyTruncation(writer, 'a');
+ }
+
+ @Test
+ public void test_write_String_slice_invalid_args() {
+ final TruncatingBufferedWriter writer = new TruncatingBufferedWriter(1);
+ final String string = "a";
+ Assertions
+ .assertThatThrownBy(() -> writer.write(string, -1, 1))
+ .isInstanceOf(IndexOutOfBoundsException.class)
+ .hasMessageStartingWith("invalid offset");
+ Assertions
+ .assertThatThrownBy(() -> writer.write(string, 1, 1))
+ .isInstanceOf(IndexOutOfBoundsException.class)
+ .hasMessageStartingWith("invalid offset");
+ Assertions
+ .assertThatThrownBy(() -> writer.write(string, 0, -1))
+ .isInstanceOf(IndexOutOfBoundsException.class)
+ .hasMessageStartingWith("invalid length");
+ Assertions
+ .assertThatThrownBy(() -> writer.write(string, 0, 2))
+ .isInstanceOf(IndexOutOfBoundsException.class)
+ .hasMessageStartingWith("invalid length");
+ }
+
+ @Test
+ public void test_write_String_slice_truncation() {
+ final TruncatingBufferedWriter writer = new TruncatingBufferedWriter(1);
+ writer.write("ab", 0, 2);
+ verifyTruncation(writer, 'a');
+ }
+
+ @Test
+ public void test_write_char_array_slice_invalid_args() {
+ final TruncatingBufferedWriter writer = new TruncatingBufferedWriter(1);
+ final char[] buffer = new char[]{'a'};
+ Assertions
+ .assertThatThrownBy(() -> writer.write(buffer, -1, 1))
+ .isInstanceOf(IndexOutOfBoundsException.class)
+ .hasMessageStartingWith("invalid offset");
+ Assertions
+ .assertThatThrownBy(() -> writer.write(buffer, 1, 1))
+ .isInstanceOf(IndexOutOfBoundsException.class)
+ .hasMessageStartingWith("invalid offset");
+ Assertions
+ .assertThatThrownBy(() -> writer.write(buffer, 0, -1))
+ .isInstanceOf(IndexOutOfBoundsException.class)
+ .hasMessageStartingWith("invalid length");
+ Assertions
+ .assertThatThrownBy(() -> writer.write(buffer, 0, 2))
+ .isInstanceOf(IndexOutOfBoundsException.class)
+ .hasMessageStartingWith("invalid length");
+ }
+
+ @Test
+ public void test_write_char_array_slice_truncation() {
+ final TruncatingBufferedWriter writer = new TruncatingBufferedWriter(1);
+ writer.write(new char[]{'a', 'b'}, 0, 2);
+ verifyTruncation(writer, 'a');
+ }
+
+ @Test
+ public void test_append_char_truncation() {
+ final TruncatingBufferedWriter writer = new TruncatingBufferedWriter(1);
+ writer.append('a');
+ writer.append('b');
+ verifyTruncation(writer, 'a');
+ }
+
+ @Test
+ public void test_append_seq_truncation() {
+ final TruncatingBufferedWriter writer = new TruncatingBufferedWriter(1);
+ writer.append("ab");
+ verifyTruncation(writer, 'a');
+ }
+
+ @Test
+ public void test_append_seq_null_truncation() {
+ final TruncatingBufferedWriter writer = new TruncatingBufferedWriter(1);
+ writer.append(null);
+ verifyTruncation(writer, 'n');
+ }
+
+ @Test
+ public void test_append_seq_slice_invalid_args() {
+ final TruncatingBufferedWriter writer = new TruncatingBufferedWriter(1);
+ final CharSequence seq = "ab";
+ Assertions
+ .assertThatThrownBy(() -> writer.append(seq, -1, 2))
+ .isInstanceOf(IndexOutOfBoundsException.class)
+ .hasMessageStartingWith("invalid start");
+ Assertions
+ .assertThatThrownBy(() -> writer.append(seq, 2, 2))
+ .isInstanceOf(IndexOutOfBoundsException.class)
+ .hasMessageStartingWith("invalid start");
+ Assertions
+ .assertThatThrownBy(() -> writer.append(seq, 0, -1))
+ .isInstanceOf(IndexOutOfBoundsException.class)
+ .hasMessageStartingWith("invalid end");
+ Assertions
+ .assertThatThrownBy(() -> writer.append(seq, 1, 0))
+ .isInstanceOf(IndexOutOfBoundsException.class)
+ .hasMessageStartingWith("invalid end");
+ Assertions
+ .assertThatThrownBy(() -> writer.append(seq, 0, 3))
+ .isInstanceOf(IndexOutOfBoundsException.class)
+ .hasMessageStartingWith("invalid end");
+ }
+
+ @Test
+ public void test_append_seq_slice_truncation() {
+ final TruncatingBufferedWriter writer = new TruncatingBufferedWriter(1);
+ writer.append("ab", 0, 1);
+ verifyTruncation(writer, 'a');
+ }
+
+ @Test
+ public void test_append_seq_slice_null_truncation() {
+ final TruncatingBufferedWriter writer = new TruncatingBufferedWriter(1);
+ writer.append(null, -1, -1);
+ verifyTruncation(writer, 'n');
+ }
+
+ private void verifyTruncation(
+ final TruncatingBufferedWriter writer,
+ final char c) {
+ Assertions.assertThat(writer.getBuffer()).isEqualTo(new char[]{c});
+ Assertions.assertThat(writer.getPosition()).isEqualTo(1);
+ Assertions.assertThat(writer.getCapacity()).isEqualTo(1);
+ Assertions.assertThat(writer.isTruncated()).isTrue();
+ verifyClose(writer);
+ }
+
+ private void verifyClose(final TruncatingBufferedWriter writer) {
+ writer.close();
+ Assertions.assertThat(writer.getPosition()).isEqualTo(0);
+ Assertions.assertThat(writer.isTruncated()).isFalse();
+ }
+
+}
diff --git a/log4j-layout-json-template/src/test/java/org/apache/logging/log4j/layout/json/template/util/UrisTest.java b/log4j-layout-json-template/src/test/java/org/apache/logging/log4j/layout/json/template/util/UrisTest.java
new file mode 100644
index 0000000..05e679b
--- /dev/null
+++ b/log4j-layout-json-template/src/test/java/org/apache/logging/log4j/layout/json/template/util/UrisTest.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.layout.json.template.util;
+
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.status.StatusLogger;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.URI;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+
+public class UrisTest {
+
+ private static final Logger LOGGER = StatusLogger.getLogger();
+
+ @Test
+ public void testClassPathResource() {
+ final String content = Uris.readUri(
+ "classpath:JsonLayout.json",
+ StandardCharsets.US_ASCII);
+ Assert.assertTrue(
+ "was expecting content to start with '{': " + content,
+ content.startsWith("{"));
+ }
+
+ @Test
+ public void testFilePathResource() throws IOException {
+ final String nonAsciiUtfText = "அஆஇฬ๘";
+ final File file = Files.createTempFile("log4j-UriUtilTest-", ".txt").toFile();
+ try {
+ try (final OutputStream outputStream = new FileOutputStream(file)) {
+ outputStream.write(nonAsciiUtfText.getBytes(StandardCharsets.UTF_8));
+ }
+ final URI uri = file.toURI();
+ final String content = Uris.readUri(uri, StandardCharsets.UTF_8);
+ Assert.assertEquals(nonAsciiUtfText, content);
+ } finally {
+ final boolean deleted = file.delete();
+ if (!deleted) {
+ LOGGER.warn("could not delete temporary file: " + file);
+ }
+ }
+ }
+
+}
diff --git a/log4j-layout-json-template/src/test/resources/gcFreeJsonTemplateLayoutLogging.xml b/log4j-layout-json-template/src/test/resources/gcFreeJsonTemplateLayoutLogging.xml
new file mode 100644
index 0000000..245ff06
--- /dev/null
+++ b/log4j-layout-json-template/src/test/resources/gcFreeJsonTemplateLayoutLogging.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-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 "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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">
+ <Appenders>
+ <Console name="Console" target="SYSTEM_OUT">
+ <PatternLayout pattern="%p %c{1.} [%t] %X{aKey} %X %m%ex%n" />
+ </Console>
+ <File name="File"
+ fileName="target/gcFreeJsonTemplateLayoutLogging.log"
+ bufferedIO="false"
+ append="false">
+ <JsonTemplateLayout recyclerFactory="threadLocal"/>
+ </File>
+ </Appenders>
+ <Loggers>
+ <Root level="trace" includeLocation="false">
+ <Property name="prop1">value1</Property>
+ <Property name="prop2">value2</Property>
+ <appender-ref ref="Console" level="FATAL"/>
+ <appender-ref ref="File"/>
+ </Root>
+ </Loggers>
+</Configuration>
diff --git a/log4j-layout-json-template/src/test/resources/nullEventDelimitedJsonTemplateLayoutLogging.xml b/log4j-layout-json-template/src/test/resources/nullEventDelimitedJsonTemplateLayoutLogging.xml
new file mode 100644
index 0000000..39d87a9
--- /dev/null
+++ b/log4j-layout-json-template/src/test/resources/nullEventDelimitedJsonTemplateLayoutLogging.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-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 "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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">
+ <Appenders>
+ <Socket name="Socket"
+ host="localhost"
+ port="50514"
+ protocol="TCP"
+ ignoreExceptions="false"
+ reconnectionDelay="100"
+ immediateFlush="true">
+ <JsonTemplateLayout eventTemplate='{"$resolver": "message"}'
+ eventDelimiter=""
+ nullEventDelimiterEnabled="true"
+ charset="US-ASCII"/>
+ </Socket>
+ </Appenders>
+ <Loggers>
+ <Root level="TRACE">
+ <AppenderRef ref="Socket"/>
+ </Root>
+ </Loggers>
+</Configuration>
diff --git a/log4j-layout-json-template/src/test/resources/testJsonTemplateLayout.json b/log4j-layout-json-template/src/test/resources/testJsonTemplateLayout.json
new file mode 100644
index 0000000..daf455e
--- /dev/null
+++ b/log4j-layout-json-template/src/test/resources/testJsonTemplateLayout.json
@@ -0,0 +1,68 @@
+{
+ "exception_class": {
+ "$resolver": "exception",
+ "field": "className"
+ },
+ "exception_message": {
+ "$resolver": "exception",
+ "field": "message"
+ },
+ "stacktrace": {
+ "$resolver": "exception",
+ "field": "stackTrace",
+ "stringified": true
+ },
+ "line_number": {
+ "$resolver": "source",
+ "field": "lineNumber"
+ },
+ "class": {
+ "$resolver": "source",
+ "field": "className"
+ },
+ "@version": 1,
+ "source_host": "${hostName}",
+ "message": {
+ "$resolver": "message",
+ "stringified": true
+ },
+ "thread_id": {
+ "$resolver": "thread",
+ "field": "id"
+ },
+ "thread_name": {
+ "$resolver": "thread",
+ "field": "name"
+ },
+ "thread_priority": {
+ "$resolver": "thread",
+ "field": "priority"
+ },
+ "@timestamp": {
+ "$resolver": "timestamp"
+ },
+ "level": {
+ "$resolver": "level",
+ "field": "name"
+ },
+ "file": {
+ "$resolver": "source",
+ "field": "fileName"
+ },
+ "method": {
+ "$resolver": "source",
+ "field": "methodName"
+ },
+ "logger_fqcn": {
+ "$resolver": "logger",
+ "field": "fqcn"
+ },
+ "logger_name": {
+ "$resolver": "logger",
+ "field": "name"
+ },
+ "end_of_batch": {
+ "$resolver": "endOfBatch"
+ },
+ "lookup_test_key": "${sys:lookup_test_key}"
+}
diff --git a/log4j-liquibase/pom.xml b/log4j-liquibase/pom.xml
index c748766..8fcfee8 100644
--- a/log4j-liquibase/pom.xml
+++ b/log4j-liquibase/pom.xml
@@ -206,6 +206,7 @@
project -->
<detectOfflineLinks>false</detectOfflineLinks>
<linksource>true</linksource>
+ <source>8</source>
</configuration>
<reportSets>
<reportSet>
diff --git a/log4j-mongodb2/pom.xml b/log4j-mongodb2/pom.xml
deleted file mode 100644
index 0b33b74..0000000
--- a/log4j-mongodb2/pom.xml
+++ /dev/null
@@ -1,195 +0,0 @@
-<?xml version="1.0" encoding="UTF-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 "License"); you may not use this file except in compliance with
- ~ the License. You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT 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 xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
- <parent>
- <groupId>org.apache.logging.log4j</groupId>
- <artifactId>log4j</artifactId>
- <version>3.0.0-SNAPSHOT</version>
- </parent>
- <modelVersion>4.0.0</modelVersion>
-
- <artifactId>log4j-mongodb2</artifactId>
- <name>Apache Log4j MongoDB 2</name>
- <description>
- MongoDB appender for Log4j using the MongoDB 2 driver API.
- </description>
- <properties>
- <log4jParentDir>${basedir}/..</log4jParentDir>
- <docLabel>MongoDB 2 Documentation</docLabel>
- <projectDir>/log4j-mongodb2</projectDir>
- <module.name>org.apache.logging.log4j.mongodb2</module.name>
- </properties>
-
- <dependencies>
- <dependency>
- <groupId>org.apache.logging.log4j</groupId>
- <artifactId>log4j-core</artifactId>
- </dependency>
- <dependency>
- <groupId>org.mongodb</groupId>
- <artifactId>mongo-java-driver</artifactId>
- </dependency>
- <!-- Test Dependencies -->
- <dependency>
- <groupId>junit</groupId>
- <artifactId>junit</artifactId>
- </dependency>
- <dependency>
- <groupId>org.mockito</groupId>
- <artifactId>mockito-core</artifactId>
- <scope>test</scope>
- </dependency>
- <dependency>
- <groupId>org.apache.logging.log4j</groupId>
- <artifactId>log4j-api</artifactId>
- <type>test-jar</type>
- </dependency>
- <dependency>
- <groupId>org.apache.logging.log4j</groupId>
- <artifactId>log4j-core</artifactId>
- <type>test-jar</type>
- </dependency>
- <dependency>
- <groupId>org.apache.logging.log4j</groupId>
- <artifactId>log4j-slf4j-impl</artifactId>
- <scope>test</scope>
- </dependency>
- <dependency>
- <groupId>de.flapdoodle.embed</groupId>
- <artifactId>de.flapdoodle.embed.mongo</artifactId>
- <scope>test</scope>
- </dependency>
- </dependencies>
-
- <build>
- <plugins>
- <plugin>
- <groupId>org.apache.felix</groupId>
- <artifactId>maven-bundle-plugin</artifactId>
- <configuration>
- <instructions>
- <Fragment-Host>org.apache.logging.log4j.core</Fragment-Host>
- <Export-Package>*</Export-Package>
- </instructions>
- </configuration>
- </plugin>
- <!-- workaround flaky "Operation not permitted" failures when running tests in parallel -->
- <plugin>
- <artifactId>maven-surefire-plugin</artifactId>
- <configuration>
- <forkCount>1</forkCount>
- <reuseForks>false</reuseForks>
- </configuration>
- </plugin>
- </plugins>
- </build>
- <reporting>
- <plugins>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-changes-plugin</artifactId>
- <version>${changes.plugin.version}</version>
- <reportSets>
- <reportSet>
- <reports>
- <report>changes-report</report>
- </reports>
- </reportSet>
- </reportSets>
- <configuration>
- <issueLinkTemplate>%URL%/show_bug.cgi?id=%ISSUE%</issueLinkTemplate>
- <useJql>true</useJql>
- </configuration>
- </plugin>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-checkstyle-plugin</artifactId>
- <version>${checkstyle.plugin.version}</version>
- <configuration>
- <!--<propertiesLocation>${vfs.parent.dir}/checkstyle.properties</propertiesLocation> -->
- <configLocation>${log4jParentDir}/checkstyle.xml</configLocation>
- <suppressionsLocation>${log4jParentDir}/checkstyle-suppressions.xml</suppressionsLocation>
- <enableRulesSummary>false</enableRulesSummary>
- <propertyExpansion>basedir=${basedir}</propertyExpansion>
- <propertyExpansion>licensedir=${log4jParentDir}/checkstyle-header.txt</propertyExpansion>
- </configuration>
- </plugin>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-javadoc-plugin</artifactId>
- <version>${javadoc.plugin.version}</version>
- <configuration>
- <bottom><![CDATA[<p align="center">Copyright © {inceptionYear}-{currentYear} {organizationName}. All Rights Reserved.<br />
- Apache Logging, Apache Log4j, Log4j, Apache, the Apache feather logo, the Apache Logging project logo,
- and the Apache Log4j logo are trademarks of The Apache Software Foundation.</p>]]></bottom>
- <!-- module link generation is completely broken in the javadoc plugin for a multi-module non-aggregating
- project -->
- <detectOfflineLinks>false</detectOfflineLinks>
- <linksource>true</linksource>
- </configuration>
- <reportSets>
- <reportSet>
- <id>non-aggregate</id>
- <reports>
- <report>javadoc</report>
- </reports>
- </reportSet>
- </reportSets>
- </plugin>
- <plugin>
- <groupId>com.github.spotbugs</groupId>
- <artifactId>spotbugs-maven-plugin</artifactId>
- <configuration>
- <fork>true</fork>
- <jvmArgs>-Duser.language=en</jvmArgs>
- <threshold>Normal</threshold>
- <effort>Default</effort>
- <excludeFilterFile>${log4jParentDir}/spotbugs-exclude-filter.xml</excludeFilterFile>
- </configuration>
- </plugin>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-jxr-plugin</artifactId>
- <version>${jxr.plugin.version}</version>
- <reportSets>
- <reportSet>
- <id>non-aggregate</id>
- <reports>
- <report>jxr</report>
- </reports>
- </reportSet>
- <reportSet>
- <id>aggregate</id>
- <reports>
- <report>aggregate</report>
- </reports>
- </reportSet>
- </reportSets>
- </plugin>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-pmd-plugin</artifactId>
- <version>${pmd.plugin.version}</version>
- <configuration>
- <targetJdk>${maven.compiler.target}</targetJdk>
- </configuration>
- </plugin>
- </plugins>
- </reporting>
-</project>
diff --git a/log4j-mongodb2/src/main/java/org/apache/logging/log4j/mongodb2/MongoDbConnection.java b/log4j-mongodb2/src/main/java/org/apache/logging/log4j/mongodb2/MongoDbConnection.java
deleted file mode 100644
index ec3b7dd..0000000
--- a/log4j-mongodb2/src/main/java/org/apache/logging/log4j/mongodb2/MongoDbConnection.java
+++ /dev/null
@@ -1,103 +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.mongodb2;
-
-import org.apache.logging.log4j.Level;
-import org.apache.logging.log4j.Logger;
-import org.apache.logging.log4j.core.appender.AppenderLoggingException;
-import org.apache.logging.log4j.core.appender.nosql.AbstractNoSqlConnection;
-import org.apache.logging.log4j.core.appender.nosql.NoSqlConnection;
-import org.apache.logging.log4j.core.appender.nosql.NoSqlObject;
-import org.apache.logging.log4j.status.StatusLogger;
-import org.bson.BSON;
-import org.bson.Transformer;
-
-import com.mongodb.BasicDBObject;
-import com.mongodb.DB;
-import com.mongodb.DBCollection;
-import com.mongodb.Mongo;
-import com.mongodb.MongoException;
-import com.mongodb.WriteConcern;
-
-/**
- * The MongoDB implementation of {@link NoSqlConnection}.
- */
-public final class MongoDbConnection extends AbstractNoSqlConnection<BasicDBObject, MongoDbObject> {
-
- private static final Logger LOGGER = StatusLogger.getLogger();
-
- static {
- BSON.addEncodingHook(Level.class, new Transformer() {
- @Override
- public Object transform(final Object o) {
- if (o instanceof Level) {
- return ((Level) o).name();
- }
- return o;
- }
- });
- }
-
- private final DBCollection collection;
- private final WriteConcern writeConcern;
-
- public MongoDbConnection(final DB database, final WriteConcern writeConcern, final String collectionName,
- final Boolean isCapped, final Integer collectionSize) {
- if (database.collectionExists(collectionName)) {
- LOGGER.debug("Gettting collection {}", collectionName);
- collection = database.getCollection(collectionName);
- } else {
- final BasicDBObject options = new BasicDBObject();
- options.put("capped", isCapped);
- options.put("size", collectionSize);
- LOGGER.debug("Creating collection {} (capped = {}, size = {})", collectionName, isCapped, collectionSize);
- this.collection = database.createCollection(collectionName, options);
- }
- this.writeConcern = writeConcern;
- }
-
- @Override
- public void closeImpl() {
- // LOG4J2-1196
- final Mongo mongo = this.collection.getDB().getMongo();
- LOGGER.debug("Closing {} client {}", mongo.getClass().getSimpleName(), mongo);
- mongo.close();
- }
-
- @Override
- public MongoDbObject[] createList(final int length) {
- return new MongoDbObject[length];
- }
-
- @Override
- public MongoDbObject createObject() {
- return new MongoDbObject();
- }
-
- @Override
- public void insertObject(final NoSqlObject<BasicDBObject> object) {
- try {
- final BasicDBObject unwrapped = object.unwrap();
- LOGGER.debug("Inserting object {}", unwrapped);
- this.collection.insert(unwrapped, this.writeConcern);
- } catch (final MongoException e) {
- throw new AppenderLoggingException("Failed to write log event to MongoDB due to error: " + e.getMessage(),
- e);
- }
- }
-
-}
diff --git a/log4j-mongodb2/src/main/java/org/apache/logging/log4j/mongodb2/MongoDbObject.java b/log4j-mongodb2/src/main/java/org/apache/logging/log4j/mongodb2/MongoDbObject.java
deleted file mode 100644
index 28a35cd..0000000
--- a/log4j-mongodb2/src/main/java/org/apache/logging/log4j/mongodb2/MongoDbObject.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.mongodb2;
-
-import java.util.Collections;
-
-import org.apache.logging.log4j.core.appender.nosql.NoSqlObject;
-
-import com.mongodb.BasicDBList;
-import com.mongodb.BasicDBObject;
-
-/**
- * The MongoDB implementation of {@link NoSqlObject}.
- */
-public final class MongoDbObject implements NoSqlObject<BasicDBObject> {
- private final BasicDBObject mongoObject;
-
- public MongoDbObject() {
- this.mongoObject = new BasicDBObject();
- }
-
- @Override
- public void set(final String field, final NoSqlObject<BasicDBObject> value) {
- this.mongoObject.append(field, value.unwrap());
- }
-
- @Override
- public void set(final String field, final NoSqlObject<BasicDBObject>[] values) {
- final BasicDBList list = new BasicDBList();
- for (final NoSqlObject<BasicDBObject> value : values) {
- list.add(value.unwrap());
- }
- this.mongoObject.append(field, list);
- }
-
- @Override
- public void set(final String field, final Object value) {
- this.mongoObject.append(field, value);
- }
-
- @Override
- public void set(final String field, final Object[] values) {
- final BasicDBList list = new BasicDBList();
- Collections.addAll(list, values);
- this.mongoObject.append(field, list);
- }
-
- @Override
- public BasicDBObject unwrap() {
- return this.mongoObject;
- }
-}
diff --git a/log4j-mongodb2/src/main/java/org/apache/logging/log4j/mongodb2/MongoDbProvider.java b/log4j-mongodb2/src/main/java/org/apache/logging/log4j/mongodb2/MongoDbProvider.java
deleted file mode 100644
index d5d4aa9..0000000
--- a/log4j-mongodb2/src/main/java/org/apache/logging/log4j/mongodb2/MongoDbProvider.java
+++ /dev/null
@@ -1,352 +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.mongodb2;
-
-import java.lang.reflect.Field;
-import java.lang.reflect.Method;
-import java.util.ArrayList;
-import java.util.List;
-
-import org.apache.logging.log4j.Logger;
-import org.apache.logging.log4j.core.Core;
-import org.apache.logging.log4j.core.appender.nosql.NoSqlProvider;
-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.convert.TypeConverters;
-import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required;
-import org.apache.logging.log4j.core.config.plugins.validation.constraints.ValidHost;
-import org.apache.logging.log4j.core.config.plugins.validation.constraints.ValidPort;
-import org.apache.logging.log4j.core.filter.AbstractFilterable;
-import org.apache.logging.log4j.core.util.NameUtil;
-import org.apache.logging.log4j.status.StatusLogger;
-import org.apache.logging.log4j.util.LoaderUtil;
-import org.apache.logging.log4j.util.Strings;
-
-import com.mongodb.DB;
-import com.mongodb.MongoClient;
-import com.mongodb.MongoCredential;
-import com.mongodb.ServerAddress;
-import com.mongodb.WriteConcern;
-
-/**
- * The MongoDB implementation of {@link NoSqlProvider}.
- */
-@Plugin(name = "MongoDb2", category = Core.CATEGORY_NAME, printObject = true)
-@PluginAliases("MongoDb") // Deprecated alias
-public final class MongoDbProvider implements NoSqlProvider<MongoDbConnection> {
-
- public static class Builder<B extends Builder<B>> extends AbstractFilterable.Builder<B>
- implements org.apache.logging.log4j.core.util.Builder<MongoDbProvider> {
-
- private static WriteConcern toWriteConcern(final String writeConcernConstant,
- final String writeConcernConstantClassName) {
- WriteConcern writeConcern;
- if (Strings.isNotEmpty(writeConcernConstant)) {
- if (Strings.isNotEmpty(writeConcernConstantClassName)) {
- try {
- final Class<?> writeConcernConstantClass = LoaderUtil.loadClass(writeConcernConstantClassName);
- final Field field = writeConcernConstantClass.getField(writeConcernConstant);
- writeConcern = (WriteConcern) field.get(null);
- } catch (final Exception e) {
- LOGGER.error("Write concern constant [{}.{}] not found, using default.",
- writeConcernConstantClassName, writeConcernConstant);
- writeConcern = DEFAULT_WRITE_CONCERN;
- }
- } else {
- writeConcern = WriteConcern.valueOf(writeConcernConstant);
- if (writeConcern == null) {
- LOGGER.warn("Write concern constant [{}] not found, using default.", writeConcernConstant);
- writeConcern = DEFAULT_WRITE_CONCERN;
- }
- }
- } else {
- writeConcern = DEFAULT_WRITE_CONCERN;
- }
- return writeConcern;
- }
-
- @PluginBuilderAttribute
- @ValidHost
- private String server = "localhost";
-
- @PluginBuilderAttribute
- @ValidPort
- private String port = "" + DEFAULT_PORT;
-
- @PluginBuilderAttribute
- @Required(message = "No database name provided")
- private String databaseName;
-
- @PluginBuilderAttribute
- @Required(message = "No collection name provided")
- private String collectionName;
-
- @PluginBuilderAttribute
- private String userName;
-
- @PluginBuilderAttribute(sensitive = true)
- private String password;
-
- @PluginBuilderAttribute("capped")
- private boolean isCapped = false;
-
- @PluginBuilderAttribute
- private int collectionSize = DEFAULT_COLLECTION_SIZE;
-
- @PluginBuilderAttribute
- private String factoryClassName;
-
- @PluginBuilderAttribute
- private String factoryMethodName;
-
- @PluginBuilderAttribute
- private String writeConcernConstantClassName;
-
- @PluginBuilderAttribute
- private String writeConcernConstant;
-
- @Override
- public MongoDbProvider build() {
- DB database;
- String description;
- if (Strings.isNotEmpty(factoryClassName) && Strings.isNotEmpty(factoryMethodName)) {
- try {
- final Class<?> factoryClass = LoaderUtil.loadClass(factoryClassName);
- final Method method = factoryClass.getMethod(factoryMethodName);
- final Object object = method.invoke(null);
-
- if (object instanceof DB) {
- database = (DB) object;
- } else if (object instanceof MongoClient) {
- if (Strings.isNotEmpty(databaseName)) {
- database = ((MongoClient) object).getDB(databaseName);
- } else {
- LOGGER.error("The factory method [{}.{}()] returned a MongoClient so the database name is "
- + "required.", factoryClassName, factoryMethodName);
- return null;
- }
- } else if (object == null) {
- LOGGER.error("The factory method [{}.{}()] returned null.", factoryClassName, factoryMethodName);
- return null;
- } else {
- LOGGER.error("The factory method [{}.{}()] returned an unsupported type [{}].", factoryClassName,
- factoryMethodName, object.getClass().getName());
- return null;
- }
-
- description = "database=" + database.getName();
- final List<ServerAddress> addresses = database.getMongo().getAllAddress();
- if (addresses.size() == 1) {
- description += ", server=" + addresses.get(0).getHost() + ", port=" + addresses.get(0).getPort();
- } else {
- description += ", servers=[";
- for (final ServerAddress address : addresses) {
- description += " { " + address.getHost() + ", " + address.getPort() + " } ";
- }
- description += "]";
- }
- } catch (final ClassNotFoundException e) {
- LOGGER.error("The factory class [{}] could not be loaded.", factoryClassName, e);
- return null;
- } catch (final NoSuchMethodException e) {
- LOGGER.error("The factory class [{}] does not have a no-arg method named [{}].", factoryClassName,
- factoryMethodName, e);
- return null;
- } catch (final Exception e) {
- LOGGER.error("The factory method [{}.{}()] could not be invoked.", factoryClassName, factoryMethodName,
- e);
- return null;
- }
- } else if (Strings.isNotEmpty(databaseName)) {
- final List<MongoCredential> credentials = new ArrayList<>();
- description = "database=" + databaseName;
- if (Strings.isNotEmpty(userName) && Strings.isNotEmpty(password)) {
- description += ", username=" + userName + ", passwordHash="
- + NameUtil.md5(password + MongoDbProvider.class.getName());
- credentials.add(MongoCredential.createCredential(userName, databaseName, password.toCharArray()));
- }
- try {
- final int portInt = TypeConverters.convert(port, int.class, DEFAULT_PORT);
- description += ", server=" + server + ", port=" + portInt;
- database = new MongoClient(new ServerAddress(server, portInt), credentials).getDB(databaseName);
- } catch (final Exception e) {
- LOGGER.error(
- "Failed to obtain a database instance from the MongoClient at server [{}] and " + "port [{}].",
- server, port);
- return null;
- }
- } else {
- LOGGER.error("No factory method was provided so the database name is required.");
- return null;
- }
-
- try {
- database.getCollectionNames(); // Check if the database actually requires authentication
- } catch (final Exception e) {
- LOGGER.error(
- "The database is not up, or you are not authenticated, try supplying a username and password to the MongoDB provider.",
- e);
- return null;
- }
-
- final WriteConcern writeConcern = toWriteConcern(writeConcernConstant, writeConcernConstantClassName);
-
- return new MongoDbProvider(database, writeConcern, collectionName, isCapped, collectionSize, description);
- }
-
- public B setCapped(final boolean isCapped) {
- this.isCapped = isCapped;
- return asBuilder();
- }
-
- public B setCollectionName(final String collectionName) {
- this.collectionName = collectionName;
- return asBuilder();
- }
-
- public B setCollectionSize(final int collectionSize) {
- this.collectionSize = collectionSize;
- return asBuilder();
- }
-
- public B setDatabaseName(final String databaseName) {
- this.databaseName = databaseName;
- return asBuilder();
- }
-
- public B setFactoryClassName(final String factoryClassName) {
- this.factoryClassName = factoryClassName;
- return asBuilder();
- }
-
- public B setFactoryMethodName(final String factoryMethodName) {
- this.factoryMethodName = factoryMethodName;
- return asBuilder();
- }
-
- public B setPassword(final String password) {
- this.password = password;
- return asBuilder();
- }
-
- public B setPort(final String port) {
- this.port = port;
- return asBuilder();
- }
-
- public B setServer(final String server) {
- this.server = server;
- return asBuilder();
- }
-
- public B setUserName(final String userName) {
- this.userName = userName;
- return asBuilder();
- }
-
- public B setWriteConcernConstant(final String writeConcernConstant) {
- this.writeConcernConstant = writeConcernConstant;
- return asBuilder();
- }
-
- public B setWriteConcernConstantClassName(final String writeConcernConstantClassName) {
- this.writeConcernConstantClassName = writeConcernConstantClassName;
- return asBuilder();
- }
- }
- private static final WriteConcern DEFAULT_WRITE_CONCERN = WriteConcern.ACKNOWLEDGED;
- private static final Logger LOGGER = StatusLogger.getLogger();
- private static final int DEFAULT_PORT = 27017;
-
- private static final int DEFAULT_COLLECTION_SIZE = 536870912;
- /**
- * Factory method for creating a MongoDB provider within the plugin manager.
- *
- * @param collectionName The name of the MongoDB collection to which log events should be written.
- * @param writeConcernConstant The {@link WriteConcern} constant to control writing details, defaults to
- * {@link WriteConcern#ACKNOWLEDGED}.
- * @param writeConcernConstantClassName The name of a class containing the aforementioned static WriteConcern
- * constant. Defaults to {@link WriteConcern}.
- * @param databaseName The name of the MongoDB database containing the collection to which log events should be
- * written. Mutually exclusive with {@code factoryClassName&factoryMethodName!=null}.
- * @param server The host name of the MongoDB server, defaults to localhost and mutually exclusive with
- * {@code factoryClassName&factoryMethodName!=null}.
- * @param port The port the MongoDB server is listening on, defaults to the default MongoDB port and mutually
- * exclusive with {@code factoryClassName&factoryMethodName!=null}.
- * @param userName The username to authenticate against the MongoDB server with.
- * @param password The password to authenticate against the MongoDB server with.
- * @param factoryClassName A fully qualified class name containing a static factory method capable of returning a
- * {@link DB} or a {@link MongoClient}.
- * @param factoryMethodName The name of the public static factory method belonging to the aforementioned factory
- * class.
- * @return a new MongoDB provider.
- * @deprecated in 2.8; use {@link #newBuilder()} instead.
- */
- @Deprecated
- public static MongoDbProvider createNoSqlProvider(
- final String collectionName,
- final String writeConcernConstant,
- final String writeConcernConstantClassName,
- final String databaseName,
- final String server,
- final String port,
- final String userName,
- final String password,
- final String factoryClassName,
- final String factoryMethodName) {
- LOGGER.info("createNoSqlProvider");
- return newBuilder().setCollectionName(collectionName).setWriteConcernConstant(writeConcernConstantClassName)
- .setWriteConcernConstant(writeConcernConstant).setDatabaseName(databaseName).setServer(server)
- .setPort(port).setUserName(userName).setPassword(password).setFactoryClassName(factoryClassName)
- .setFactoryMethodName(factoryMethodName).build();
- }
- @PluginBuilderFactory
- public static <B extends Builder<B>> B newBuilder() {
- return new Builder<B>().asBuilder();
- }
- private final String collectionName;
- private final DB database;
- private final String description;
-
- private final WriteConcern writeConcern;
-
- private final boolean isCapped;
-
- private final Integer collectionSize;
-
- private MongoDbProvider(final DB database, final WriteConcern writeConcern, final String collectionName,
- final boolean isCapped, final Integer collectionSize, final String description) {
- this.database = database;
- this.writeConcern = writeConcern;
- this.collectionName = collectionName;
- this.isCapped = isCapped;
- this.collectionSize = collectionSize;
- this.description = "mongoDb{ " + description + " }";
- }
-
- @Override
- public MongoDbConnection getConnection() {
- return new MongoDbConnection(this.database, this.writeConcern, this.collectionName, this.isCapped, this.collectionSize);
- }
-
- @Override
- public String toString() {
- return this.description;
- }
-}
diff --git a/log4j-mongodb2/src/main/java/org/apache/logging/log4j/mongodb2/package-info.java b/log4j-mongodb2/src/main/java/org/apache/logging/log4j/mongodb2/package-info.java
deleted file mode 100644
index bf111af..0000000
--- a/log4j-mongodb2/src/main/java/org/apache/logging/log4j/mongodb2/package-info.java
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache license, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the license for the specific language governing permissions and
- * limitations under the license.
- */
-/**
- * The classes in this package contain the MongoDB provider for the NoSQL Appender.
- */
-package org.apache.logging.log4j.mongodb2;
diff --git a/log4j-mongodb2/src/site/markdown/index.md.vm b/log4j-mongodb2/src/site/markdown/index.md.vm
deleted file mode 100644
index 0d1eb7a..0000000
--- a/log4j-mongodb2/src/site/markdown/index.md.vm
+++ /dev/null
@@ -1,48 +0,0 @@
-<!-- vim: set syn=markdown : -->
-<!--
- Licensed to the Apache Software Foundation (ASF) under one or more
- contributor license agreements. See the NOTICE file distributed with
- this work for additional information regarding copyright ownership.
- The ASF licenses this file to You under the Apache License, Version 2.0
- (the "License"); you may not use this file except in compliance with
- the License. You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-#set($h1='#')
-#set($h2='##')
-## TODO: use properties for dynamic dependency versions
-
-$h1 MongoDB appender
-
-[MongoDB](http://www.mongodb.org/) is supported through the
-[Java MongoDB Driver](http://docs.mongodb.org/ecosystem/drivers/java/).
-
-```
- <dependencyManagement>
- <dependencies>
- <dependency>
- <groupId>org.mongodb</groupId>
- <artifactId>mongo-java-driver</artifactId>
- <version>2.14.3</version>
- </dependency>
- </dependencies>
- </dependencyManagement>
- <dependencies>
- <dependency>
- <groupId>org.mongodb</groupId>
- <artifactId>mongo-java-driver</artifactId>
- </dependency>
- </dependencies>
-```
-
-$h2 Requirements
-
-The MongoDB Appender is dependent on the Log4j 2 API and implementation.
-For more information, see [Runtime Dependencies](../runtime-dependencies.html).
diff --git a/log4j-mongodb2/src/site/site.xml b/log4j-mongodb2/src/site/site.xml
deleted file mode 100644
index f5db26e..0000000
--- a/log4j-mongodb2/src/site/site.xml
+++ /dev/null
@@ -1,52 +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 name="Log4j MongoDB 2.x Appender"
- xmlns="http://maven.apache.org/DECORATION/1.4.0"
- xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
- xsi:schemaLocation="http://maven.apache.org/DECORATION/1.4.0 http://maven.apache.org/xsd/decoration-1.4.0.xsd">
- <body>
- <links>
- <item name="Apache" href="http://www.apache.org/" />
- <item name="Logging Services" href="http://logging.apache.org/"/>
- <item name="Log4j" href="../index.html"/>
- </links>
-
- <!-- Component-specific reports -->
- <menu ref="reports"/>
-
- <!-- Overall Project Info -->
- <menu name="Log4j Project Information" img="icon-info-sign">
- <item name="Dependencies" href="../dependencies.html" />
- <item name="Dependency Convergence" href="../dependency-convergence.html" />
- <item name="Dependency Management" href="../dependency-management.html" />
- <item name="Project Team" href="../team-list.html" />
- <item name="Mailing Lists" href="../mail-lists.html" />
- <item name="Issue Tracking" href="../issue-tracking.html" />
- <item name="Project License" href="../license.html" />
- <item name="Source Repository" href="../source-repository.html" />
- <item name="Project Summary" href="../project-summary.html" />
- </menu>
-
- <menu name="Log4j Project Reports" img="icon-cog">
- <item name="Changes Report" href="../changes-report.html" />
- <item name="JIRA Report" href="../jira-report.html" />
- <item name="Surefire Report" href="../surefire-report.html" />
- <item name="RAT Report" href="../rat-report.html" />
- </menu>
- </body>
-</project>
diff --git a/log4j-mongodb2/src/test/java/org/apache/logging/log4j/mongodb2/MongoDbAuthFailureTest.java b/log4j-mongodb2/src/test/java/org/apache/logging/log4j/mongodb2/MongoDbAuthFailureTest.java
deleted file mode 100644
index 0fb61c6..0000000
--- a/log4j-mongodb2/src/test/java/org/apache/logging/log4j/mongodb2/MongoDbAuthFailureTest.java
+++ /dev/null
@@ -1,72 +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.mongodb2;
-
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-import org.apache.logging.log4j.categories.Appenders;
-import org.apache.logging.log4j.junit.LoggerContextRule;
-import org.apache.logging.log4j.mongodb2.MongoDbTestRule.LoggingTarget;
-import org.apache.logging.log4j.test.AvailablePortSystemPropertyTestRule;
-import org.apache.logging.log4j.test.RuleChainFactory;
-import org.junit.Assert;
-import org.junit.ClassRule;
-import org.junit.Ignore;
-import org.junit.Test;
-import org.junit.experimental.categories.Category;
-import org.junit.rules.RuleChain;
-
-import com.mongodb.DB;
-import com.mongodb.DBCollection;
-import com.mongodb.MongoClient;
-
-/**
- * This class name does NOT end in "Test" in order to only be picked up by {@link Java8Test}.
- *
- * TODO Set up the log4j user in MongoDB.
- */
-@Ignore("TODO Set up the log4j user in MongoDB")
-@Category(Appenders.MongoDb.class)
-public class MongoDbAuthFailureTest {
-
- private static LoggerContextRule loggerContextTestRule = new LoggerContextRule("log4j2-mongodb-auth-failure.xml");
-
- private static final AvailablePortSystemPropertyTestRule mongoDbPortTestRule = AvailablePortSystemPropertyTestRule
- .create(TestConstants.SYS_PROP_NAME_PORT);
-
- private static final MongoDbTestRule mongoDbTestRule = new MongoDbTestRule(mongoDbPortTestRule.getName(), LoggingTarget.NULL);
-
- @ClassRule
- public static RuleChain ruleChain = RuleChainFactory.create(mongoDbPortTestRule, mongoDbTestRule,
- loggerContextTestRule);
-
- @Test
- public void test() {
- final Logger logger = LogManager.getLogger();
- logger.info("Hello log");
- final MongoClient mongoClient = mongoDbTestRule.getMongoClient();
- try {
- final DB database = mongoClient.getDB("test");
- Assert.assertNotNull(database);
- final DBCollection collection = database.getCollection("applog");
- Assert.assertNotNull(collection);
- Assert.assertFalse(collection.find().hasNext());
- } finally {
- mongoClient.close();
- }
- }
-}
diff --git a/log4j-mongodb2/src/test/java/org/apache/logging/log4j/mongodb2/MongoDbCappedTest.java b/log4j-mongodb2/src/test/java/org/apache/logging/log4j/mongodb2/MongoDbCappedTest.java
deleted file mode 100644
index ffd368a..0000000
--- a/log4j-mongodb2/src/test/java/org/apache/logging/log4j/mongodb2/MongoDbCappedTest.java
+++ /dev/null
@@ -1,72 +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.mongodb2;
-
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-import org.apache.logging.log4j.categories.Appenders;
-import org.apache.logging.log4j.junit.LoggerContextRule;
-import org.apache.logging.log4j.mongodb2.MongoDbTestRule.LoggingTarget;
-import org.apache.logging.log4j.test.AvailablePortSystemPropertyTestRule;
-import org.apache.logging.log4j.test.RuleChainFactory;
-import org.junit.Assert;
-import org.junit.ClassRule;
-import org.junit.Test;
-import org.junit.experimental.categories.Category;
-import org.junit.rules.RuleChain;
-
-import com.mongodb.DB;
-import com.mongodb.DBCollection;
-import com.mongodb.DBObject;
-import com.mongodb.MongoClient;
-
-/**
- * This class name does NOT end in "Test" in order to only be picked up by {@link Java8Test}.
- */
-@Category(Appenders.MongoDb.class)
-public class MongoDbCappedTest {
-
- private static LoggerContextRule loggerContextTestRule = new LoggerContextRule("log4j2-mongodb-capped.xml");
-
- private static final AvailablePortSystemPropertyTestRule mongoDbPortTestRule = AvailablePortSystemPropertyTestRule
- .create(TestConstants.SYS_PROP_NAME_PORT);
-
- private static final MongoDbTestRule mongoDbTestRule = new MongoDbTestRule(mongoDbPortTestRule.getName(), LoggingTarget.NULL);
-
- @ClassRule
- public static RuleChain ruleChain = RuleChainFactory.create(mongoDbPortTestRule, mongoDbTestRule,
- loggerContextTestRule);
-
- @Test
- public void test() {
- final Logger logger = LogManager.getLogger();
- logger.info("Hello log");
- final MongoClient mongoClient = mongoDbTestRule.getMongoClient();
- try {
- final DB database = mongoClient.getDB("test");
- Assert.assertNotNull(database);
- final DBCollection collection = database.getCollection("applog");
- Assert.assertNotNull(collection);
- Assert.assertTrue(collection.find().hasNext());
- final DBObject first = collection.find().next();
- Assert.assertNotNull(first);
- Assert.assertEquals(first.toMap().toString(), "Hello log", first.get("message"));
- } finally {
- mongoClient.close();
- }
- }
-}
diff --git a/log4j-mongodb2/src/test/java/org/apache/logging/log4j/mongodb2/MongoDbMapMessageTest.java b/log4j-mongodb2/src/test/java/org/apache/logging/log4j/mongodb2/MongoDbMapMessageTest.java
deleted file mode 100644
index 99350aa..0000000
--- a/log4j-mongodb2/src/test/java/org/apache/logging/log4j/mongodb2/MongoDbMapMessageTest.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements. See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache license, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the license for the specific language governing permissions and
- * limitations under the license.
- */
-package org.apache.logging.log4j.mongodb2;
-
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-import org.apache.logging.log4j.categories.Appenders;
-import org.apache.logging.log4j.junit.LoggerContextRule;
-import org.apache.logging.log4j.message.MapMessage;
-import org.apache.logging.log4j.mongodb2.MongoDbTestRule.LoggingTarget;
-import org.apache.logging.log4j.test.AvailablePortSystemPropertyTestRule;
-import org.apache.logging.log4j.test.RuleChainFactory;
-import org.junit.Assert;
-import org.junit.ClassRule;
-import org.junit.Test;
-import org.junit.experimental.categories.Category;
-import org.junit.rules.RuleChain;
-
-import com.mongodb.DB;
-import com.mongodb.DBCollection;
-import com.mongodb.DBObject;
-import com.mongodb.MongoClient;
-
-/**
- * This class name does NOT end in "Test" in order to only be picked up by {@link Java8Test}.
- */
-@Category(Appenders.MongoDb.class)
-public class MongoDbMapMessageTest {
-
- private static LoggerContextRule loggerContextTestRule = new LoggerContextRule("log4j2-mongodb-map-message.xml");
-
- private static final AvailablePortSystemPropertyTestRule mongoDbPortTestRule = AvailablePortSystemPropertyTestRule
- .create(TestConstants.SYS_PROP_NAME_PORT);
-
- private static final MongoDbTestRule mongoDbTestRule = new MongoDbTestRule(mongoDbPortTestRule.getName(), LoggingTarget.NULL);
-
- @ClassRule
- public static RuleChain ruleChain = RuleChainFactory.create(mongoDbPortTestRule, mongoDbTestRule,
- loggerContextTestRule);
-
- @Test
- public void test() {
- final Logger logger = LogManager.getLogger();
- final MapMessage mapMessage = new MapMessage();
- mapMessage.with("SomeName", "SomeValue");
- mapMessage.with("SomeInt", 1);
- logger.info(mapMessage);
- //
- final MongoClient mongoClient = mongoDbTestRule.getMongoClient();
- try {
- final DB database = mongoClient.getDB("test");
- Assert.assertNotNull(database);
- final DBCollection collection = database.getCollection("applog");
- Assert.assertNotNull(collection);
- final DBObject first = collection.find().next();
- Assert.assertNotNull(first);
- final String firstMapString = first.toMap().toString();
- Assert.assertEquals(firstMapString, "SomeValue", first.get("SomeName"));
- Assert.assertEquals(firstMapString, Integer.valueOf(1), first.get("SomeInt"));
- } finally {
- mongoClient.close();
- }
- }
-}
diff --git a/log4j-mongodb2/src/test/java/org/apache/logging/log4j/mongodb2/MongoDbTest.java b/log4j-mongodb2/src/test/java/org/apache/logging/log4j/mongodb2/MongoDbTest.java
deleted file mode 100644
index 2a73f8c..0000000
--- a/log4j-mongodb2/src/test/java/org/apache/logging/log4j/mongodb2/MongoDbTest.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.mongodb2;
-
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-import org.apache.logging.log4j.categories.Appenders;
-import org.apache.logging.log4j.junit.LoggerContextRule;
-import org.apache.logging.log4j.mongodb2.MongoDbTestRule.LoggingTarget;
-import org.apache.logging.log4j.test.AvailablePortSystemPropertyTestRule;
-import org.apache.logging.log4j.test.RuleChainFactory;
-import org.junit.Assert;
-import org.junit.ClassRule;
-import org.junit.Test;
-import org.junit.experimental.categories.Category;
-import org.junit.rules.RuleChain;
-
-import com.mongodb.DB;
-import com.mongodb.DBCollection;
-import com.mongodb.DBObject;
-import com.mongodb.MongoClient;
-
-/**
- * This class name does NOT end in "Test" in order to only be picked up by {@link Java8Test}.
- */
-@Category(Appenders.MongoDb.class)
-public class MongoDbTest {
-
- private static LoggerContextRule loggerContextTestRule = new LoggerContextRule("log4j2-mongodb.xml");
-
- private static final AvailablePortSystemPropertyTestRule mongoDbPortTestRule = AvailablePortSystemPropertyTestRule
- .create(TestConstants.SYS_PROP_NAME_PORT);
-
- private static final MongoDbTestRule mongoDbTestRule = new MongoDbTestRule(mongoDbPortTestRule.getName(), LoggingTarget.NULL);
-
- @ClassRule
- public static RuleChain ruleChain = RuleChainFactory.create(mongoDbPortTestRule, mongoDbTestRule,
- loggerContextTestRule);
-
- @Test
- public void test() {
- final Logger logger = LogManager.getLogger();
- logger.info("Hello log");
- final MongoClient mongoClient = mongoDbTestRule.getMongoClient();
- try {
- final DB database = mongoClient.getDB("test");
- Assert.assertNotNull(database);
- final DBCollection collection = database.getCollection("applog");
- Assert.assertNotNull(collection);
- final DBObject first = collection.find().next();
- Assert.assertNotNull(first);
- Assert.assertEquals(first.toMap().toString(), "Hello log", first.get("message"));
- } finally {
- mongoClient.close();
- }
- }
-}
diff --git a/log4j-mongodb2/src/test/java/org/apache/logging/log4j/mongodb2/MongoDbTestRule.java b/log4j-mongodb2/src/test/java/org/apache/logging/log4j/mongodb2/MongoDbTestRule.java
deleted file mode 100644
index 465fa3f..0000000
--- a/log4j-mongodb2/src/test/java/org/apache/logging/log4j/mongodb2/MongoDbTestRule.java
+++ /dev/null
@@ -1,184 +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.mongodb2;
-
-import java.util.Objects;
-
-import org.apache.commons.lang3.NotImplementedException;
-import org.junit.rules.TestRule;
-import org.junit.runner.Description;
-import org.junit.runners.model.Statement;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.mongodb.MongoClient;
-
-import de.flapdoodle.embed.mongo.Command;
-import de.flapdoodle.embed.mongo.MongodExecutable;
-import de.flapdoodle.embed.mongo.MongodProcess;
-import de.flapdoodle.embed.mongo.MongodStarter;
-import de.flapdoodle.embed.mongo.config.MongodConfigBuilder;
-import de.flapdoodle.embed.mongo.config.Net;
-import de.flapdoodle.embed.mongo.config.RuntimeConfigBuilder;
-import de.flapdoodle.embed.mongo.config.Timeout;
-import de.flapdoodle.embed.mongo.distribution.Version;
-import de.flapdoodle.embed.process.config.IRuntimeConfig;
-import de.flapdoodle.embed.process.config.io.ProcessOutput;
-import de.flapdoodle.embed.process.runtime.Network;
-
-/**
- * A JUnit test rule to manage a MongoDB embedded instance.
- *
- * TODO Move this class to Apache Commons Testing.
- */
-public class MongoDbTestRule implements TestRule {
-
- public enum LoggingTarget {
- NULL, CONSOLE
- }
-
- private static final int BUILDER_TIMEOUT_MILLIS = 30000;
-
- public static int getBuilderTimeoutMillis() {
- return BUILDER_TIMEOUT_MILLIS;
- }
-
- /**
- * Store {@link MongodStarter} (or RuntimeConfig) in a static final field if you want to use artifact store caching
- * (or else disable caching).
- * <p>
- * The test framework {@code de.flapdoodle.embed.mongo} requires Java 8.
- * </p>
- */
- protected final MongodStarter starter;
-
- protected final String portSystemPropertyName;
-
- protected MongoClient mongoClient;
- protected MongodExecutable mongodExecutable;
- protected MongodProcess mongodProcess;
- protected final LoggingTarget loggingTarget;
-
- /**
- * Constructs a new test rule.
- *
- * @param portSystemPropertyName
- * The system property name for the MongoDB port.
- * @param loggingTarget
- * The logging target
- */
- public MongoDbTestRule(final String portSystemPropertyName, final LoggingTarget loggingTarget) {
- this.portSystemPropertyName = Objects.requireNonNull(portSystemPropertyName, "portSystemPropertyName");
- this.loggingTarget = loggingTarget;
- this.starter = getMongodStarter(loggingTarget);
- }
-
- private static MongodStarter getMongodStarter(final LoggingTarget loggingTarget) {
- if (loggingTarget == null) {
- return MongodStarter.getDefaultInstance();
- }
- switch (loggingTarget) {
- case NULL:
- final Logger logger = LoggerFactory.getLogger(MongoDbTestRule.class.getName());
- final IRuntimeConfig runtimeConfig = new RuntimeConfigBuilder()
- // @formatter:off
- .defaultsWithLogger(Command.MongoD, logger)
- .processOutput(ProcessOutput.getDefaultInstanceSilent())
- .build();
- // @formatter:on
-
- return MongodStarter.getInstance(runtimeConfig);
- case CONSOLE:
- return MongodStarter.getDefaultInstance();
- default:
- throw new NotImplementedException(loggingTarget.toString());
- }
- }
-
- @Override
- public Statement apply(final Statement base, final Description description) {
- return new Statement() {
-
- @Override
- public void evaluate() throws Throwable {
- final String value = Objects.requireNonNull(System.getProperty(portSystemPropertyName),
- "System property '" + portSystemPropertyName + "' is null");
- final int port = Integer.parseInt(value);
- mongodExecutable = starter.prepare(
- // @formatter:off
- new MongodConfigBuilder()
- .version(Version.Main.PRODUCTION)
- .timeout(new Timeout(BUILDER_TIMEOUT_MILLIS))
- .net(
- new Net("localhost", port, Network.localhostIsIPv6()))
- .build());
- // @formatter:on
- mongodProcess = mongodExecutable.start();
- mongoClient = new MongoClient("localhost", port);
- try {
- base.evaluate();
- } finally {
- if (mongodProcess != null) {
- mongodProcess.stop();
- mongodProcess = null;
- }
- if (mongodExecutable != null) {
- mongodExecutable.stop();
- mongodExecutable = null;
- }
- }
- }
- };
- }
-
- public MongoClient getMongoClient() {
- return mongoClient;
- }
-
- public MongodExecutable getMongodExecutable() {
- return mongodExecutable;
- }
-
- public MongodProcess getMongodProcess() {
- return mongodProcess;
- }
-
- public MongodStarter getStarter() {
- return starter;
- }
-
- @Override
- public String toString() {
- StringBuilder builder = new StringBuilder();
- builder.append("MongoDbTestRule [starter=");
- builder.append(starter);
- builder.append(", portSystemPropertyName=");
- builder.append(portSystemPropertyName);
- builder.append(", mongoClient=");
- builder.append(mongoClient);
- builder.append(", mongodExecutable=");
- builder.append(mongodExecutable);
- builder.append(", mongodProcess=");
- builder.append(mongodProcess);
- builder.append(", loggingTarget=");
- builder.append(loggingTarget);
- builder.append("]");
- return builder.toString();
- }
-
-}
\ No newline at end of file
diff --git a/log4j-mongodb2/src/test/java/org/apache/logging/log4j/mongodb2/MongoDbTestTestRuleTest.java b/log4j-mongodb2/src/test/java/org/apache/logging/log4j/mongodb2/MongoDbTestTestRuleTest.java
deleted file mode 100644
index 68e9782..0000000
--- a/log4j-mongodb2/src/test/java/org/apache/logging/log4j/mongodb2/MongoDbTestTestRuleTest.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.mongodb2;
-
-import java.util.List;
-
-import org.apache.commons.lang3.JavaVersion;
-import org.apache.commons.lang3.SystemUtils;
-import org.apache.logging.log4j.mongodb2.MongoDbTestRule.LoggingTarget;
-import org.apache.logging.log4j.test.AvailablePortSystemPropertyTestRule;
-import org.apache.logging.log4j.test.RuleChainFactory;
-import org.junit.Assert;
-import org.junit.Assume;
-import org.junit.BeforeClass;
-import org.junit.ClassRule;
-import org.junit.Test;
-import org.junit.rules.RuleChain;
-
-/**
- * Tests {@link MongoDbTestRule}. This class name does NOT end in "Test" in order to only be picked up by {@link Java8Test}.
- * <p>
- * The test framework {@code de.flapdoodle.embed.mongo} requires Java 8.
- * </p>
- */
-public class MongoDbTestTestRuleTest {
-
- private static final AvailablePortSystemPropertyTestRule mongoDbPortTestRule = AvailablePortSystemPropertyTestRule
- .create(TestConstants.SYS_PROP_NAME_PORT);
-
- private static final MongoDbTestRule mongoDbTestRule = new MongoDbTestRule(mongoDbPortTestRule.getName(), LoggingTarget.NULL);
-
- @ClassRule
- public static RuleChain mongoDbChain = RuleChainFactory.create(mongoDbPortTestRule, mongoDbTestRule);
-
- @BeforeClass
- public static void beforeClass() {
- Assume.assumeTrue(SystemUtils.isJavaVersionAtLeast(JavaVersion.JAVA_1_8));
- }
-
- @Test
- public void testAccess() {
- final List<String> databaseNames = mongoDbTestRule.getMongoClient().getDatabaseNames();
- Assert.assertNotNull(databaseNames);
- Assert.assertFalse(databaseNames.isEmpty());
- Assert.assertNotNull(databaseNames.get(0));
- }
-
- @Test
- public void testMongoDbTestRule() {
- Assert.assertNotNull(mongoDbTestRule);
- Assert.assertNotNull(mongoDbTestRule.getStarter());
- Assert.assertNotNull(mongoDbTestRule.getMongoClient());
- Assert.assertNotNull(mongoDbTestRule.getMongodExecutable());
- Assert.assertNotNull(mongoDbTestRule.getMongodProcess());
- }
-}
diff --git a/log4j-mongodb2/src/test/java/org/apache/logging/log4j/mongodb2/TestConstants.java b/log4j-mongodb2/src/test/java/org/apache/logging/log4j/mongodb2/TestConstants.java
deleted file mode 100644
index 040209c..0000000
--- a/log4j-mongodb2/src/test/java/org/apache/logging/log4j/mongodb2/TestConstants.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.mongodb2;
-
-public class TestConstants {
-
- public static final String SYS_PROP_NAME_PORT = "MongoDBTestPort";
-
-}
diff --git a/log4j-mongodb2/src/test/resources/log4j2-mongodb-auth-failure.xml b/log4j-mongodb2/src/test/resources/log4j2-mongodb-auth-failure.xml
deleted file mode 100644
index 43d7e37..0000000
--- a/log4j-mongodb2/src/test/resources/log4j2-mongodb-auth-failure.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="UTF-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 "License"); you may not use this file except in compliance with
- the License. You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT 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>
- <NoSql name="MongoDbAppender">
- <MongoDb2 databaseName="test" collectionName="applog" server="localhost" userName="log4jUser" password="12345678"
- port="${sys:MongoDBTestPort:-27017}" />
- </NoSql>
- </Appenders>
- <Loggers>
- <Root level="ALL">
- <AppenderRef ref="MongoDbAppender" />
- </Root>
- </Loggers>
-</Configuration>
diff --git a/log4j-mongodb2/src/test/resources/log4j2-mongodb-capped.xml b/log4j-mongodb2/src/test/resources/log4j2-mongodb-capped.xml
deleted file mode 100644
index 7eda0ae..0000000
--- a/log4j-mongodb2/src/test/resources/log4j2-mongodb-capped.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="UTF-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 "License"); you may not use this file except in compliance with
- the License. You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT 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>
- <NoSql name="MongoDbAppender">
- <MongoDb2 databaseName="test" collectionName="applog" server="localhost" capped="true" collectionSize="1073741824"
- port="${sys:MongoDBTestPort:-27017}" />
- </NoSql>
- </Appenders>
- <Loggers>
- <Root level="ALL">
- <AppenderRef ref="MongoDbAppender" />
- </Root>
- </Loggers>
-</Configuration>
diff --git a/log4j-mongodb2/src/test/resources/log4j2-mongodb-map-message.xml b/log4j-mongodb2/src/test/resources/log4j2-mongodb-map-message.xml
deleted file mode 100644
index 12f957a..0000000
--- a/log4j-mongodb2/src/test/resources/log4j2-mongodb-map-message.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-<?xml version="1.0" encoding="UTF-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 "License"); you may not use this file except in compliance with
- the License. You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT 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>
- <NoSql name="MongoDbAppender">
- <MongoDb2 databaseName="test" collectionName="applog" server="localhost"
- port="${sys:MongoDBTestPort:-27017}" />
- <MessageLayout />
- </NoSql>
- </Appenders>
- <Loggers>
- <Root level="ALL">
- <AppenderRef ref="MongoDbAppender" />
- </Root>
- </Loggers>
-</Configuration>
diff --git a/log4j-mongodb2/src/test/resources/log4j2-mongodb.xml b/log4j-mongodb2/src/test/resources/log4j2-mongodb.xml
deleted file mode 100644
index 805c746..0000000
--- a/log4j-mongodb2/src/test/resources/log4j2-mongodb.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="UTF-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 "License"); you may not use this file except in compliance with
- the License. You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT 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>
- <NoSql name="MongoDbAppender">
- <MongoDb2 databaseName="test" collectionName="applog" server="localhost"
- port="${sys:MongoDBTestPort:-27017}" />
- </NoSql>
- </Appenders>
- <Loggers>
- <Root level="ALL">
- <AppenderRef ref="MongoDbAppender" />
- </Root>
- </Loggers>
-</Configuration>
diff --git a/log4j-mongodb3/pom.xml b/log4j-mongodb3/pom.xml
index 0b6ef5c..e634c50 100644
--- a/log4j-mongodb3/pom.xml
+++ b/log4j-mongodb3/pom.xml
@@ -44,10 +44,12 @@
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>mongodb-driver</artifactId>
+ <version>${mongodb3.version}</version>
</dependency>
<dependency>
<groupId>org.mongodb</groupId>
<artifactId>bson</artifactId>
+ <version>${mongodb3.version}</version>
</dependency>
<!-- Test Dependencies -->
<dependency>
@@ -63,15 +65,12 @@
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<type>test-jar</type>
+ <scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<type>test-jar</type>
- </dependency>
- <dependency>
- <groupId>org.apache.logging.log4j</groupId>
- <artifactId>log4j-slf4j-impl</artifactId>
<scope>test</scope>
</dependency>
<dependency>
@@ -146,6 +145,7 @@
project -->
<detectOfflineLinks>false</detectOfflineLinks>
<linksource>true</linksource>
+ <source>8</source>
</configuration>
<reportSets>
<reportSet>
diff --git a/log4j-mongodb3/src/main/java/org/apache/logging/log4j/mongodb3/LevelCodec.java b/log4j-mongodb3/src/main/java/org/apache/logging/log4j/mongodb3/LevelCodec.java
deleted file mode 100644
index 9bbc0b0..0000000
--- a/log4j-mongodb3/src/main/java/org/apache/logging/log4j/mongodb3/LevelCodec.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.mongodb3;
-
-import org.apache.logging.log4j.Level;
-import org.bson.BsonReader;
-import org.bson.BsonWriter;
-import org.bson.codecs.Codec;
-import org.bson.codecs.DecoderContext;
-import org.bson.codecs.EncoderContext;
-
-/**
- * A BSON Codec for Log4j {@link Level}s.
- */
-public class LevelCodec implements Codec<Level> {
-
- @Override
- public Level decode(final BsonReader reader, final DecoderContext decoderContext) {
- return Level.getLevel(reader.readString());
- }
-
- @Override
- public void encode(final BsonWriter writer, final Level level, final EncoderContext encoderContext) {
- writer.writeString(level.name());
- }
-
- @Override
- public Class<Level> getEncoderClass() {
- return Level.class;
- }
-
-}
diff --git a/log4j-mongodb3/src/main/java/org/apache/logging/log4j/mongodb3/MongoDb3Connection.java b/log4j-mongodb3/src/main/java/org/apache/logging/log4j/mongodb3/MongoDb3Connection.java
new file mode 100644
index 0000000..512d3ff
--- /dev/null
+++ b/log4j-mongodb3/src/main/java/org/apache/logging/log4j/mongodb3/MongoDb3Connection.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.mongodb3;
+
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.core.appender.AppenderLoggingException;
+import org.apache.logging.log4j.core.appender.nosql.AbstractNoSqlConnection;
+import org.apache.logging.log4j.core.appender.nosql.NoSqlConnection;
+import org.apache.logging.log4j.core.appender.nosql.NoSqlObject;
+import org.apache.logging.log4j.status.StatusLogger;
+import org.bson.Document;
+
+import com.mongodb.MongoClient;
+import com.mongodb.MongoException;
+import com.mongodb.client.MongoCollection;
+import com.mongodb.client.MongoDatabase;
+import com.mongodb.client.model.CreateCollectionOptions;
+
+/**
+ * The MongoDB implementation of {@link NoSqlConnection}.
+ */
+public final class MongoDb3Connection extends AbstractNoSqlConnection<Document, MongoDb3DocumentObject> {
+
+ private static final Logger LOGGER = StatusLogger.getLogger();
+
+ private static MongoCollection<Document> getOrCreateMongoCollection(final MongoDatabase database,
+ final String collectionName, final boolean isCapped, final Integer sizeInBytes) {
+ try {
+ LOGGER.debug("Gettting collection '{}'...", collectionName);
+ // throws IllegalArgumentException if collectionName is invalid
+ return database.getCollection(collectionName);
+ } catch (final IllegalStateException e) {
+ LOGGER.debug("Collection '{}' does not exist.", collectionName);
+ final CreateCollectionOptions options = new CreateCollectionOptions()
+ // @formatter:off
+ .capped(isCapped)
+ .sizeInBytes(sizeInBytes);
+ // @formatter:on
+ LOGGER.debug("Creating collection {} (capped = {}, sizeInBytes = {})", collectionName, isCapped,
+ sizeInBytes);
+ database.createCollection(collectionName, options);
+ return database.getCollection(collectionName);
+ }
+
+ }
+
+ private final MongoCollection<Document> collection;
+ private final MongoClient mongoClient;
+
+ public MongoDb3Connection(final MongoClient mongoClient, final MongoDatabase mongoDatabase,
+ final String collectionName, final boolean isCapped, final Integer sizeInBytes) {
+ this.mongoClient = mongoClient;
+ this.collection = getOrCreateMongoCollection(mongoDatabase, collectionName, isCapped, sizeInBytes);
+ }
+
+ @Override
+ public void closeImpl() {
+ // LOG4J2-1196
+ mongoClient.close();
+ }
+
+ @Override
+ public MongoDb3DocumentObject[] createList(final int length) {
+ return new MongoDb3DocumentObject[length];
+ }
+
+ @Override
+ public MongoDb3DocumentObject createObject() {
+ return new MongoDb3DocumentObject();
+ }
+
+ @Override
+ public void insertObject(final NoSqlObject<Document> object) {
+ try {
+ final Document unwrapped = object.unwrap();
+ LOGGER.debug("Inserting object {}", unwrapped);
+ this.collection.insertOne(unwrapped);
+ } catch (final MongoException e) {
+ throw new AppenderLoggingException("Failed to write log event to MongoDB due to error: " + e.getMessage(),
+ e);
+ }
+ }
+
+}
diff --git a/log4j-mongodb3/src/main/java/org/apache/logging/log4j/mongodb3/MongoDb3DocumentObject.java b/log4j-mongodb3/src/main/java/org/apache/logging/log4j/mongodb3/MongoDb3DocumentObject.java
new file mode 100644
index 0000000..3132d26
--- /dev/null
+++ b/log4j-mongodb3/src/main/java/org/apache/logging/log4j/mongodb3/MongoDb3DocumentObject.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.mongodb3;
+
+import java.util.Arrays;
+
+import org.apache.logging.log4j.core.appender.nosql.NoSqlObject;
+import org.bson.Document;
+
+/**
+ * The MongoDB implementation of {@link NoSqlObject} typed to a BSON {@link Document}.
+ */
+public final class MongoDb3DocumentObject implements NoSqlObject<Document> {
+ private final Document document;
+
+ public MongoDb3DocumentObject() {
+ this.document = new Document();
+ }
+
+ @Override
+ public void set(final String field, final NoSqlObject<Document> value) {
+ this.document.append(field, value.unwrap());
+ }
+
+ @Override
+ public void set(final String field, final NoSqlObject<Document>[] values) {
+ this.document.append(field, Arrays.asList(values));
+ }
+
+ @Override
+ public void set(final String field, final Object value) {
+ this.document.append(field, value);
+ }
+
+ @Override
+ public void set(final String field, final Object[] values) {
+ this.document.append(field, Arrays.asList(values));
+ }
+
+ @Override
+ public Document unwrap() {
+ return this.document;
+ }
+}
diff --git a/log4j-mongodb3/src/main/java/org/apache/logging/log4j/mongodb3/MongoDb3LevelCodec.java b/log4j-mongodb3/src/main/java/org/apache/logging/log4j/mongodb3/MongoDb3LevelCodec.java
new file mode 100644
index 0000000..0ecefcf
--- /dev/null
+++ b/log4j-mongodb3/src/main/java/org/apache/logging/log4j/mongodb3/MongoDb3LevelCodec.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.mongodb3;
+
+import org.apache.logging.log4j.Level;
+import org.bson.BsonReader;
+import org.bson.BsonWriter;
+import org.bson.codecs.Codec;
+import org.bson.codecs.DecoderContext;
+import org.bson.codecs.EncoderContext;
+
+/**
+ * A BSON Codec for Log4j {@link Level}s.
+ */
+public class MongoDb3LevelCodec implements Codec<Level> {
+
+ /**
+ * The singleton instance.
+ *
+ * @since 2.14.0
+ */
+ public static final MongoDb3LevelCodec INSTANCE = new MongoDb3LevelCodec();
+
+ @Override
+ public Level decode(final BsonReader reader, final DecoderContext decoderContext) {
+ return Level.getLevel(reader.readString());
+ }
+
+ @Override
+ public void encode(final BsonWriter writer, final Level level, final EncoderContext encoderContext) {
+ writer.writeString(level.name());
+ }
+
+ @Override
+ public Class<Level> getEncoderClass() {
+ return Level.class;
+ }
+
+}
diff --git a/log4j-mongodb3/src/main/java/org/apache/logging/log4j/mongodb3/MongoDb3Provider.java b/log4j-mongodb3/src/main/java/org/apache/logging/log4j/mongodb3/MongoDb3Provider.java
new file mode 100644
index 0000000..d38cab8
--- /dev/null
+++ b/log4j-mongodb3/src/main/java/org/apache/logging/log4j/mongodb3/MongoDb3Provider.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.mongodb3;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.core.Core;
+import org.apache.logging.log4j.core.appender.nosql.NoSqlProvider;
+import org.apache.logging.log4j.core.filter.AbstractFilterable;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginBuilderAttribute;
+import org.apache.logging.log4j.plugins.PluginFactory;
+import org.apache.logging.log4j.plugins.convert.TypeConverters;
+import org.apache.logging.log4j.plugins.validation.constraints.Required;
+import org.apache.logging.log4j.plugins.validation.constraints.ValidHost;
+import org.apache.logging.log4j.plugins.validation.constraints.ValidPort;
+import org.apache.logging.log4j.status.StatusLogger;
+import org.apache.logging.log4j.util.LoaderUtil;
+import org.apache.logging.log4j.util.NameUtil;
+import org.apache.logging.log4j.util.Strings;
+import org.bson.codecs.configuration.CodecRegistries;
+import org.bson.codecs.configuration.CodecRegistry;
+
+import com.mongodb.MongoClient;
+import com.mongodb.MongoClientOptions;
+import com.mongodb.MongoCredential;
+import com.mongodb.ServerAddress;
+import com.mongodb.WriteConcern;
+import com.mongodb.client.MongoDatabase;
+
+/**
+ * The MongoDB implementation of {@link NoSqlProvider} using the MongoDB driver version 3 API.
+ */
+@Plugin(name = "MongoDb3", category = Core.CATEGORY_NAME, printObject = true)
+public final class MongoDb3Provider implements NoSqlProvider<MongoDb3Connection> {
+
+ public static class Builder<B extends Builder<B>> extends AbstractFilterable.Builder<B>
+ implements org.apache.logging.log4j.plugins.util.Builder<MongoDb3Provider> {
+
+ // @formatter:off
+ private static final CodecRegistry CODEC_REGISTRIES = CodecRegistries.fromRegistries(
+ CodecRegistries.fromCodecs(MongoDb3LevelCodec.INSTANCE),
+ MongoClient.getDefaultCodecRegistry());
+ // @formatter:on
+
+ private static WriteConcern toWriteConcern(final String writeConcernConstant,
+ final String writeConcernConstantClassName) {
+ WriteConcern writeConcern;
+ if (Strings.isNotEmpty(writeConcernConstant)) {
+ if (Strings.isNotEmpty(writeConcernConstantClassName)) {
+ try {
+ final Class<?> writeConcernConstantClass = LoaderUtil.loadClass(writeConcernConstantClassName);
+ final Field field = writeConcernConstantClass.getField(writeConcernConstant);
+ writeConcern = (WriteConcern) field.get(null);
+ } catch (final Exception e) {
+ LOGGER.error("Write concern constant [{}.{}] not found, using default.",
+ writeConcernConstantClassName, writeConcernConstant);
+ writeConcern = DEFAULT_WRITE_CONCERN;
+ }
+ } else {
+ writeConcern = WriteConcern.valueOf(writeConcernConstant);
+ if (writeConcern == null) {
+ LOGGER.warn("Write concern constant [{}] not found, using default.", writeConcernConstant);
+ writeConcern = DEFAULT_WRITE_CONCERN;
+ }
+ }
+ } else {
+ writeConcern = DEFAULT_WRITE_CONCERN;
+ }
+ return writeConcern;
+ }
+
+ @PluginBuilderAttribute
+ @Required(message = "No collection name provided")
+ private String collectionName;
+
+ @PluginBuilderAttribute
+ private int collectionSize = DEFAULT_COLLECTION_SIZE;
+
+ @PluginBuilderAttribute
+ @Required(message = "No database name provided")
+ private String databaseName;
+
+ @PluginBuilderAttribute
+ private String factoryClassName;
+
+ @PluginBuilderAttribute
+ private String factoryMethodName;
+
+ @PluginBuilderAttribute("capped")
+ private boolean capped = false;
+
+ @PluginBuilderAttribute(sensitive = true)
+ private String password;
+
+ @PluginBuilderAttribute
+ @ValidPort
+ private String port = "" + DEFAULT_PORT;
+
+ @PluginBuilderAttribute
+ @ValidHost
+ private String server = "localhost";
+
+ @PluginBuilderAttribute
+ private String userName;
+
+ @PluginBuilderAttribute
+ private String writeConcernConstant;
+
+ @PluginBuilderAttribute
+ private String writeConcernConstantClassName;
+
+ @SuppressWarnings("resource")
+ @Override
+ public MongoDb3Provider build() {
+ MongoDatabase database;
+ String description;
+ MongoClient mongoClient = null;
+
+ if (Strings.isNotEmpty(factoryClassName) && Strings.isNotEmpty(factoryMethodName)) {
+ try {
+ final Class<?> factoryClass = LoaderUtil.loadClass(factoryClassName);
+ final Method method = factoryClass.getMethod(factoryMethodName);
+ final Object object = method.invoke(null);
+
+ if (object instanceof MongoDatabase) {
+ database = (MongoDatabase) object;
+ } else if (object instanceof MongoClient) {
+ if (Strings.isNotEmpty(databaseName)) {
+ database = ((MongoClient) object).getDatabase(databaseName);
+ } else {
+ LOGGER.error("The factory method [{}.{}()] returned a MongoClient so the database name is "
+ + "required.", factoryClassName, factoryMethodName);
+ return null;
+ }
+ } else if (object == null) {
+ LOGGER.error("The factory method [{}.{}()] returned null.", factoryClassName,
+ factoryMethodName);
+ return null;
+ } else {
+ LOGGER.error("The factory method [{}.{}()] returned an unsupported type [{}].",
+ factoryClassName, factoryMethodName, object.getClass().getName());
+ return null;
+ }
+
+ final String dbName = database.getName();
+ description = "database=" + dbName;
+ } catch (final ClassNotFoundException e) {
+ LOGGER.error("The factory class [{}] could not be loaded.", factoryClassName, e);
+ return null;
+ } catch (final NoSuchMethodException e) {
+ LOGGER.error("The factory class [{}] does not have a no-arg method named [{}].", factoryClassName,
+ factoryMethodName, e);
+ return null;
+ } catch (final Exception e) {
+ LOGGER.error("The factory method [{}.{}()] could not be invoked.", factoryClassName,
+ factoryMethodName, e);
+ return null;
+ }
+ } else if (Strings.isNotEmpty(databaseName)) {
+ MongoCredential mongoCredential = null;
+ description = "database=" + databaseName;
+ if (Strings.isNotEmpty(userName) && Strings.isNotEmpty(password)) {
+ description += ", username=" + userName + ", passwordHash="
+ + NameUtil.md5(password + MongoDb3Provider.class.getName());
+ mongoCredential = MongoCredential.createCredential(userName, databaseName, password.toCharArray());
+ }
+ try {
+ final int portInt = TypeConverters.convert(port, int.class, DEFAULT_PORT);
+ description += ", server=" + server + ", port=" + portInt;
+ final WriteConcern writeConcern = toWriteConcern(writeConcernConstant, writeConcernConstantClassName);
+ // @formatter:off
+ final MongoClientOptions options = MongoClientOptions.builder()
+ .codecRegistry(CODEC_REGISTRIES)
+ .writeConcern(writeConcern)
+ .build();
+ // @formatter:on
+ final ServerAddress serverAddress = new ServerAddress(server, portInt);
+ mongoClient = mongoCredential == null ?
+ // @formatter:off
+ new MongoClient(serverAddress, options) :
+ new MongoClient(serverAddress, mongoCredential, options);
+ // @formatter:on
+ database = mongoClient.getDatabase(databaseName);
+ } catch (final Exception e) {
+ LOGGER.error("Failed to obtain a database instance from the MongoClient at server [{}] and "
+ + "port [{}].", server, port);
+ close(mongoClient);
+ return null;
+ }
+ } else {
+ LOGGER.error("No factory method was provided so the database name is required.");
+ close(mongoClient);
+ return null;
+ }
+
+ try {
+ database.listCollectionNames().first(); // Check if the database actually requires authentication
+ } catch (final Exception e) {
+ LOGGER.error(
+ "The database is not up, or you are not authenticated, try supplying a username and password to the MongoDB provider.",
+ e);
+ close(mongoClient);
+ return null;
+ }
+
+ return new MongoDb3Provider(mongoClient, database, collectionName, capped, collectionSize, description);
+ }
+
+ private void close(final MongoClient mongoClient) {
+ if (mongoClient != null) {
+ mongoClient.close();
+ }
+ }
+
+ public B setCapped(final boolean isCapped) {
+ this.capped = isCapped;
+ return asBuilder();
+ }
+
+ public B setCollectionName(final String collectionName) {
+ this.collectionName = collectionName;
+ return asBuilder();
+ }
+
+ public B setCollectionSize(final int collectionSize) {
+ this.collectionSize = collectionSize;
+ return asBuilder();
+ }
+
+ public B setDatabaseName(final String databaseName) {
+ this.databaseName = databaseName;
+ return asBuilder();
+ }
+
+ public B setFactoryClassName(final String factoryClassName) {
+ this.factoryClassName = factoryClassName;
+ return asBuilder();
+ }
+
+ public B setFactoryMethodName(final String factoryMethodName) {
+ this.factoryMethodName = factoryMethodName;
+ return asBuilder();
+ }
+
+ public B setPassword(final String password) {
+ this.password = password;
+ return asBuilder();
+ }
+
+ public B setPort(final String port) {
+ this.port = port;
+ return asBuilder();
+ }
+
+ public B setServer(final String server) {
+ this.server = server;
+ return asBuilder();
+ }
+
+ public B setUserName(final String userName) {
+ this.userName = userName;
+ return asBuilder();
+ }
+
+ public B setWriteConcernConstant(final String writeConcernConstant) {
+ this.writeConcernConstant = writeConcernConstant;
+ return asBuilder();
+ }
+
+ public B setWriteConcernConstantClassName(final String writeConcernConstantClassName) {
+ this.writeConcernConstantClassName = writeConcernConstantClassName;
+ return asBuilder();
+ }
+ }
+
+ private static final int DEFAULT_COLLECTION_SIZE = 536870912;
+ private static final int DEFAULT_PORT = 27017;
+ private static final WriteConcern DEFAULT_WRITE_CONCERN = WriteConcern.ACKNOWLEDGED;
+
+ private static final Logger LOGGER = StatusLogger.getLogger();
+
+ @PluginFactory
+ public static <B extends Builder<B>> B newBuilder() {
+ return new Builder<B>().asBuilder();
+ }
+
+ private final String collectionName;
+ private final Integer collectionSize;
+ private final String description;
+ private final boolean isCapped;
+ private final MongoClient mongoClient;
+ private final MongoDatabase mongoDatabase;
+
+ private MongoDb3Provider(final MongoClient mongoClient, final MongoDatabase mongoDatabase,
+ final String collectionName, final boolean isCapped, final Integer collectionSize,
+ final String description) {
+ this.mongoClient = mongoClient;
+ this.mongoDatabase = mongoDatabase;
+ this.collectionName = collectionName;
+ this.isCapped = isCapped;
+ this.collectionSize = collectionSize;
+ this.description = "mongoDb{ " + description + " }";
+ }
+
+ @Override
+ public MongoDb3Connection getConnection() {
+ return new MongoDb3Connection(mongoClient, mongoDatabase, collectionName, isCapped, collectionSize);
+ }
+
+ @Override
+ public String toString() {
+ return description;
+ }
+}
diff --git a/log4j-mongodb3/src/main/java/org/apache/logging/log4j/mongodb3/MongoDbConnection.java b/log4j-mongodb3/src/main/java/org/apache/logging/log4j/mongodb3/MongoDbConnection.java
deleted file mode 100644
index e25c366..0000000
--- a/log4j-mongodb3/src/main/java/org/apache/logging/log4j/mongodb3/MongoDbConnection.java
+++ /dev/null
@@ -1,113 +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.mongodb3;
-
-import org.apache.logging.log4j.Level;
-import org.apache.logging.log4j.Logger;
-import org.apache.logging.log4j.core.appender.AppenderLoggingException;
-import org.apache.logging.log4j.core.appender.nosql.AbstractNoSqlConnection;
-import org.apache.logging.log4j.core.appender.nosql.NoSqlConnection;
-import org.apache.logging.log4j.core.appender.nosql.NoSqlObject;
-import org.apache.logging.log4j.status.StatusLogger;
-import org.bson.BSON;
-import org.bson.Document;
-import org.bson.Transformer;
-
-import com.mongodb.MongoClient;
-import com.mongodb.MongoException;
-import com.mongodb.client.MongoCollection;
-import com.mongodb.client.MongoDatabase;
-import com.mongodb.client.model.CreateCollectionOptions;
-
-/**
- * The MongoDB implementation of {@link NoSqlConnection}.
- */
-public final class MongoDbConnection extends AbstractNoSqlConnection<Document, MongoDbDocumentObject> {
-
- private static final Logger LOGGER = StatusLogger.getLogger();
-
- static {
- BSON.addEncodingHook(Level.class, new Transformer() {
- @Override
- public Object transform(final Object o) {
- if (o instanceof Level) {
- return ((Level) o).name();
- }
- return o;
- }
- });
- }
-
- private static MongoCollection<Document> getOrCreateMongoCollection(final MongoDatabase database,
- final String collectionName, final boolean isCapped, final Integer sizeInBytes) {
- try {
- LOGGER.debug("Gettting collection '{}'...", collectionName);
- // throws IllegalArgumentException if collectionName is invalid
- return database.getCollection(collectionName);
- } catch (final IllegalStateException e) {
- LOGGER.debug("Collection '{}' does not exist.", collectionName);
- final CreateCollectionOptions options = new CreateCollectionOptions()
- // @formatter:off
- .capped(isCapped)
- .sizeInBytes(sizeInBytes);
- // @formatter:on
- LOGGER.debug("Creating collection {} (capped = {}, sizeInBytes = {})", collectionName, isCapped,
- sizeInBytes);
- database.createCollection(collectionName, options);
- return database.getCollection(collectionName);
- }
-
- }
-
- private final MongoCollection<Document> collection;
- private final MongoClient mongoClient;
-
- public MongoDbConnection(final MongoClient mongoClient, final MongoDatabase mongoDatabase,
- final String collectionName, final boolean isCapped, final Integer sizeInBytes) {
- this.mongoClient = mongoClient;
- this.collection = getOrCreateMongoCollection(mongoDatabase, collectionName, isCapped, sizeInBytes);
- }
-
- @Override
- public void closeImpl() {
- // LOG4J2-1196
- mongoClient.close();
- }
-
- @Override
- public MongoDbDocumentObject[] createList(final int length) {
- return new MongoDbDocumentObject[length];
- }
-
- @Override
- public MongoDbDocumentObject createObject() {
- return new MongoDbDocumentObject();
- }
-
- @Override
- public void insertObject(final NoSqlObject<Document> object) {
- try {
- final Document unwrapped = object.unwrap();
- LOGGER.debug("Inserting object {}", unwrapped);
- this.collection.insertOne(unwrapped);
- } catch (final MongoException e) {
- throw new AppenderLoggingException("Failed to write log event to MongoDB due to error: " + e.getMessage(),
- e);
- }
- }
-
-}
diff --git a/log4j-mongodb3/src/main/java/org/apache/logging/log4j/mongodb3/MongoDbDocumentObject.java b/log4j-mongodb3/src/main/java/org/apache/logging/log4j/mongodb3/MongoDbDocumentObject.java
deleted file mode 100644
index 49bdc88..0000000
--- a/log4j-mongodb3/src/main/java/org/apache/logging/log4j/mongodb3/MongoDbDocumentObject.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.mongodb3;
-
-import java.util.Arrays;
-
-import org.apache.logging.log4j.core.appender.nosql.NoSqlObject;
-import org.bson.Document;
-
-/**
- * The MongoDB implementation of {@link NoSqlObject} typed to a BSON {@link Document}.
- */
-public final class MongoDbDocumentObject implements NoSqlObject<Document> {
- private final Document document;
-
- public MongoDbDocumentObject() {
- this.document = new Document();
- }
-
- @Override
- public void set(final String field, final NoSqlObject<Document> value) {
- this.document.append(field, value.unwrap());
- }
-
- @Override
- public void set(final String field, final NoSqlObject<Document>[] values) {
- this.document.append(field, Arrays.asList(values));
- }
-
- @Override
- public void set(final String field, final Object value) {
- this.document.append(field, value);
- }
-
- @Override
- public void set(final String field, final Object[] values) {
- this.document.append(field, Arrays.asList(values));
- }
-
- @Override
- public Document unwrap() {
- return this.document;
- }
-}
diff --git a/log4j-mongodb3/src/main/java/org/apache/logging/log4j/mongodb3/MongoDbProvider.java b/log4j-mongodb3/src/main/java/org/apache/logging/log4j/mongodb3/MongoDbProvider.java
deleted file mode 100644
index b5b437c..0000000
--- a/log4j-mongodb3/src/main/java/org/apache/logging/log4j/mongodb3/MongoDbProvider.java
+++ /dev/null
@@ -1,326 +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.mongodb3;
-
-import java.lang.reflect.Field;
-import java.lang.reflect.Method;
-
-import org.apache.logging.log4j.Logger;
-import org.apache.logging.log4j.core.Core;
-import org.apache.logging.log4j.core.appender.nosql.NoSqlProvider;
-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.convert.TypeConverters;
-import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required;
-import org.apache.logging.log4j.core.config.plugins.validation.constraints.ValidHost;
-import org.apache.logging.log4j.core.config.plugins.validation.constraints.ValidPort;
-import org.apache.logging.log4j.core.filter.AbstractFilterable;
-import org.apache.logging.log4j.core.util.NameUtil;
-import org.apache.logging.log4j.status.StatusLogger;
-import org.apache.logging.log4j.util.LoaderUtil;
-import org.apache.logging.log4j.util.Strings;
-import org.bson.codecs.configuration.CodecRegistries;
-
-import com.mongodb.MongoClient;
-import com.mongodb.MongoClientOptions;
-import com.mongodb.MongoCredential;
-import com.mongodb.ServerAddress;
-import com.mongodb.WriteConcern;
-import com.mongodb.client.MongoDatabase;
-
-/**
- * The MongoDB implementation of {@link NoSqlProvider}.
- */
-@Plugin(name = "MongoDb3", category = Core.CATEGORY_NAME, printObject = true)
-public final class MongoDbProvider implements NoSqlProvider<MongoDbConnection> {
-
- public static class Builder<B extends Builder<B>> extends AbstractFilterable.Builder<B>
- implements org.apache.logging.log4j.core.util.Builder<MongoDbProvider> {
-
- private static WriteConcern toWriteConcern(final String writeConcernConstant,
- final String writeConcernConstantClassName) {
- WriteConcern writeConcern;
- if (Strings.isNotEmpty(writeConcernConstant)) {
- if (Strings.isNotEmpty(writeConcernConstantClassName)) {
- try {
- final Class<?> writeConcernConstantClass = LoaderUtil.loadClass(writeConcernConstantClassName);
- final Field field = writeConcernConstantClass.getField(writeConcernConstant);
- writeConcern = (WriteConcern) field.get(null);
- } catch (final Exception e) {
- LOGGER.error("Write concern constant [{}.{}] not found, using default.",
- writeConcernConstantClassName, writeConcernConstant);
- writeConcern = DEFAULT_WRITE_CONCERN;
- }
- } else {
- writeConcern = WriteConcern.valueOf(writeConcernConstant);
- if (writeConcern == null) {
- LOGGER.warn("Write concern constant [{}] not found, using default.", writeConcernConstant);
- writeConcern = DEFAULT_WRITE_CONCERN;
- }
- }
- } else {
- writeConcern = DEFAULT_WRITE_CONCERN;
- }
- return writeConcern;
- }
-
- @PluginBuilderAttribute
- @Required(message = "No collection name provided")
- private String collectionName;
-
- @PluginBuilderAttribute
- private int collectionSize = DEFAULT_COLLECTION_SIZE;
-
- @PluginBuilderAttribute
- @Required(message = "No database name provided")
- private String databaseName;
-
- @PluginBuilderAttribute
- private String factoryClassName;
-
- @PluginBuilderAttribute
- private String factoryMethodName;
-
- @PluginBuilderAttribute("capped")
- private boolean capped = false;
-
- @PluginBuilderAttribute(sensitive = true)
- private String password;
-
- @PluginBuilderAttribute
- @ValidPort
- private String port = "" + DEFAULT_PORT;
-
- @PluginBuilderAttribute
- @ValidHost
- private String server = "localhost";
-
- @PluginBuilderAttribute
- private String userName;
-
- @PluginBuilderAttribute
- private String writeConcernConstant;
-
- @PluginBuilderAttribute
- private String writeConcernConstantClassName;
-
- @SuppressWarnings("resource")
- @Override
- public MongoDbProvider build() {
- MongoDatabase database;
- String description;
- MongoClient mongoClient = null;
-
- if (Strings.isNotEmpty(factoryClassName) && Strings.isNotEmpty(factoryMethodName)) {
- try {
- final Class<?> factoryClass = LoaderUtil.loadClass(factoryClassName);
- final Method method = factoryClass.getMethod(factoryMethodName);
- final Object object = method.invoke(null);
-
- if (object instanceof MongoDatabase) {
- database = (MongoDatabase) object;
- } else if (object instanceof MongoClient) {
- if (Strings.isNotEmpty(databaseName)) {
- database = ((MongoClient) object).getDatabase(databaseName);
- } else {
- LOGGER.error("The factory method [{}.{}()] returned a MongoClient so the database name is "
- + "required.", factoryClassName, factoryMethodName);
- return null;
- }
- } else if (object == null) {
- LOGGER.error("The factory method [{}.{}()] returned null.", factoryClassName,
- factoryMethodName);
- return null;
- } else {
- LOGGER.error("The factory method [{}.{}()] returned an unsupported type [{}].",
- factoryClassName, factoryMethodName, object.getClass().getName());
- return null;
- }
-
- final String databaseName = database.getName();
- description = "database=" + databaseName;
- } catch (final ClassNotFoundException e) {
- LOGGER.error("The factory class [{}] could not be loaded.", factoryClassName, e);
- return null;
- } catch (final NoSuchMethodException e) {
- LOGGER.error("The factory class [{}] does not have a no-arg method named [{}].", factoryClassName,
- factoryMethodName, e);
- return null;
- } catch (final Exception e) {
- LOGGER.error("The factory method [{}.{}()] could not be invoked.", factoryClassName,
- factoryMethodName, e);
- return null;
- }
- } else if (Strings.isNotEmpty(databaseName)) {
- MongoCredential mongoCredential = null;
- description = "database=" + databaseName;
- if (Strings.isNotEmpty(userName) && Strings.isNotEmpty(password)) {
- description += ", username=" + userName + ", passwordHash="
- + NameUtil.md5(password + MongoDbProvider.class.getName());
- mongoCredential = MongoCredential.createCredential(userName, databaseName, password.toCharArray());
- }
- try {
- final int portInt = TypeConverters.convert(port, int.class, DEFAULT_PORT);
- description += ", server=" + server + ", port=" + portInt;
- final WriteConcern writeConcern = toWriteConcern(writeConcernConstant, writeConcernConstantClassName);
- // @formatter:off
- final MongoClientOptions options = MongoClientOptions.builder()
- .codecRegistry(CodecRegistries.fromRegistries(
- CodecRegistries.fromCodecs(new LevelCodec()),
- MongoClient.getDefaultCodecRegistry()))
- .writeConcern(writeConcern)
- .build();
- // @formatter:on
- final ServerAddress serverAddress = new ServerAddress(server, portInt);
- mongoClient = mongoCredential == null ?
- // @formatter:off
- new MongoClient(serverAddress, options) :
- new MongoClient(serverAddress, mongoCredential, options);
- // @formatter:on
- database = mongoClient.getDatabase(databaseName);
- } catch (final Exception e) {
- LOGGER.error("Failed to obtain a database instance from the MongoClient at server [{}] and "
- + "port [{}].", server, port);
- close(mongoClient);
- return null;
- }
- } else {
- LOGGER.error("No factory method was provided so the database name is required.");
- close(mongoClient);
- return null;
- }
-
- try {
- database.listCollectionNames().first(); // Check if the database actually requires authentication
- } catch (final Exception e) {
- LOGGER.error(
- "The database is not up, or you are not authenticated, try supplying a username and password to the MongoDB provider.",
- e);
- close(mongoClient);
- return null;
- }
-
- return new MongoDbProvider(mongoClient, database, collectionName, capped, collectionSize, description);
- }
-
- private void close(final MongoClient mongoClient) {
- if (mongoClient != null) {
- mongoClient.close();
- }
- }
-
- public B setCapped(final boolean isCapped) {
- this.capped = isCapped;
- return asBuilder();
- }
-
- public B setCollectionName(final String collectionName) {
- this.collectionName = collectionName;
- return asBuilder();
- }
-
- public B setCollectionSize(final int collectionSize) {
- this.collectionSize = collectionSize;
- return asBuilder();
- }
-
- public B setDatabaseName(final String databaseName) {
- this.databaseName = databaseName;
- return asBuilder();
- }
-
- public B setFactoryClassName(final String factoryClassName) {
- this.factoryClassName = factoryClassName;
- return asBuilder();
- }
-
- public B setFactoryMethodName(final String factoryMethodName) {
- this.factoryMethodName = factoryMethodName;
- return asBuilder();
- }
-
- public B setPassword(final String password) {
- this.password = password;
- return asBuilder();
- }
-
- public B setPort(final String port) {
- this.port = port;
- return asBuilder();
- }
-
- public B setServer(final String server) {
- this.server = server;
- return asBuilder();
- }
-
- public B setUserName(final String userName) {
- this.userName = userName;
- return asBuilder();
- }
-
- public B setWriteConcernConstant(final String writeConcernConstant) {
- this.writeConcernConstant = writeConcernConstant;
- return asBuilder();
- }
-
- public B setWriteConcernConstantClassName(final String writeConcernConstantClassName) {
- this.writeConcernConstantClassName = writeConcernConstantClassName;
- return asBuilder();
- }
- }
-
- private static final int DEFAULT_COLLECTION_SIZE = 536870912;
- private static final int DEFAULT_PORT = 27017;
- private static final WriteConcern DEFAULT_WRITE_CONCERN = WriteConcern.ACKNOWLEDGED;
-
- private static final Logger LOGGER = StatusLogger.getLogger();
-
- @PluginBuilderFactory
- public static <B extends Builder<B>> B newBuilder() {
- return new Builder<B>().asBuilder();
- }
-
- private final String collectionName;
- private final Integer collectionSize;
- private final String description;
- private final boolean isCapped;
- private final MongoClient mongoClient;
- private final MongoDatabase mongoDatabase;
-
- private MongoDbProvider(final MongoClient mongoClient, final MongoDatabase mongoDatabase,
- final String collectionName, final boolean isCapped, final Integer collectionSize,
- final String description) {
- this.mongoClient = mongoClient;
- this.mongoDatabase = mongoDatabase;
- this.collectionName = collectionName;
- this.isCapped = isCapped;
- this.collectionSize = collectionSize;
- this.description = "mongoDb{ " + description + " }";
- }
-
- @Override
- public MongoDbConnection getConnection() {
- return new MongoDbConnection(mongoClient, mongoDatabase, collectionName, isCapped, collectionSize);
- }
-
- @Override
- public String toString() {
- return description;
- }
-}
diff --git a/log4j-mongodb3/src/test/java/org/apache/logging/log4j/mongodb3/MongoDb3AuthFailureTest.java b/log4j-mongodb3/src/test/java/org/apache/logging/log4j/mongodb3/MongoDb3AuthFailureTest.java
new file mode 100644
index 0000000..540be57
--- /dev/null
+++ b/log4j-mongodb3/src/test/java/org/apache/logging/log4j/mongodb3/MongoDb3AuthFailureTest.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.mongodb3;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.categories.Appenders;
+import org.apache.logging.log4j.junit.LoggerContextRule;
+import org.apache.logging.log4j.mongodb3.MongoDb3TestRule.LoggingTarget;
+import org.apache.logging.log4j.test.AvailablePortSystemPropertyTestRule;
+import org.apache.logging.log4j.test.RuleChainFactory;
+import org.bson.Document;
+import org.junit.Assert;
+import org.junit.ClassRule;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.rules.RuleChain;
+
+import com.mongodb.MongoClient;
+import com.mongodb.client.MongoCollection;
+import com.mongodb.client.MongoDatabase;
+
+/**
+ * This class name does NOT end in "Test" in order to only be picked up by {@link Java8Test}.
+ *
+ * TODO Set up the log4j user in MongoDB.
+ */
+@Ignore("TODO Set up the log4j user in MongoDB")
+@Category(Appenders.MongoDb.class)
+public class MongoDb3AuthFailureTest {
+
+ private static LoggerContextRule loggerContextTestRule = new LoggerContextRule("log4j2-mongodb-auth-failure.xml");
+
+ private static final AvailablePortSystemPropertyTestRule mongoDbPortTestRule = AvailablePortSystemPropertyTestRule
+ .create(MongoDb3TestConstants.SYS_PROP_NAME_PORT);
+
+ private static final MongoDb3TestRule mongoDbTestRule = new MongoDb3TestRule(mongoDbPortTestRule.getName(),
+ MongoDb3AuthFailureTest.class, LoggingTarget.NULL);
+
+ @ClassRule
+ public static RuleChain ruleChain = RuleChainFactory.create(mongoDbPortTestRule, mongoDbTestRule,
+ loggerContextTestRule);
+
+ @Test
+ public void test() {
+ final Logger logger = LogManager.getLogger();
+ logger.info("Hello log");
+ try (final MongoClient mongoClient = mongoDbTestRule.getMongoClient()) {
+ final MongoDatabase database = mongoClient.getDatabase("test");
+ Assert.assertNotNull(database);
+ final MongoCollection<Document> collection = database.getCollection("applog");
+ Assert.assertNotNull(collection);
+ final Document first = collection.find().first();
+ Assert.assertNull(first);
+ }
+ }
+}
diff --git a/log4j-mongodb3/src/test/java/org/apache/logging/log4j/mongodb3/MongoDb3CappedTest.java b/log4j-mongodb3/src/test/java/org/apache/logging/log4j/mongodb3/MongoDb3CappedTest.java
new file mode 100644
index 0000000..f03af5f
--- /dev/null
+++ b/log4j-mongodb3/src/test/java/org/apache/logging/log4j/mongodb3/MongoDb3CappedTest.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.mongodb3;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.categories.Appenders;
+import org.apache.logging.log4j.junit.LoggerContextRule;
+import org.apache.logging.log4j.mongodb3.MongoDb3TestRule.LoggingTarget;
+import org.apache.logging.log4j.test.AvailablePortSystemPropertyTestRule;
+import org.apache.logging.log4j.test.RuleChainFactory;
+import org.bson.Document;
+import org.junit.Assert;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.rules.RuleChain;
+
+import com.mongodb.MongoClient;
+import com.mongodb.client.MongoCollection;
+import com.mongodb.client.MongoDatabase;
+
+/**
+ * This class name does NOT end in "Test" in order to only be picked up by {@link Java8Test}.
+ */
+@Category(Appenders.MongoDb.class)
+public class MongoDb3CappedTest {
+
+ private static LoggerContextRule loggerContextTestRule = new LoggerContextRule("log4j2-mongodb-capped.xml");
+
+ private static final AvailablePortSystemPropertyTestRule mongoDbPortTestRule = AvailablePortSystemPropertyTestRule
+ .create(MongoDb3TestConstants.SYS_PROP_NAME_PORT);
+
+ private static final MongoDb3TestRule mongoDbTestRule = new MongoDb3TestRule(mongoDbPortTestRule.getName(),
+ MongoDb3CappedTest.class, LoggingTarget.NULL);
+
+ @ClassRule
+ public static RuleChain ruleChain = RuleChainFactory.create(mongoDbPortTestRule, mongoDbTestRule,
+ loggerContextTestRule);
+
+ @Test
+ public void test() {
+ final Logger logger = LogManager.getLogger();
+ logger.info("Hello log");
+ try (final MongoClient mongoClient = mongoDbTestRule.getMongoClient()) {
+ final MongoDatabase database = mongoClient.getDatabase("test");
+ Assert.assertNotNull(database);
+ final MongoCollection<Document> collection = database.getCollection("applog");
+ Assert.assertNotNull(collection);
+ final Document first = collection.find().first();
+ Assert.assertNotNull(first);
+ Assert.assertEquals(first.toJson(), "Hello log", first.getString("message"));
+ }
+ }
+}
diff --git a/log4j-mongodb3/src/test/java/org/apache/logging/log4j/mongodb3/MongoDb3MapMessageTest.java b/log4j-mongodb3/src/test/java/org/apache/logging/log4j/mongodb3/MongoDb3MapMessageTest.java
new file mode 100644
index 0000000..9a45ede
--- /dev/null
+++ b/log4j-mongodb3/src/test/java/org/apache/logging/log4j/mongodb3/MongoDb3MapMessageTest.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.mongodb3;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.categories.Appenders;
+import org.apache.logging.log4j.junit.LoggerContextRule;
+import org.apache.logging.log4j.message.MapMessage;
+import org.apache.logging.log4j.mongodb3.MongoDb3TestRule.LoggingTarget;
+import org.apache.logging.log4j.test.AvailablePortSystemPropertyTestRule;
+import org.apache.logging.log4j.test.RuleChainFactory;
+import org.bson.Document;
+import org.junit.Assert;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.rules.RuleChain;
+
+import com.mongodb.MongoClient;
+import com.mongodb.client.MongoCollection;
+import com.mongodb.client.MongoDatabase;
+
+/**
+ * This class name does NOT end in "Test" in order to only be picked up by {@link Java8Test}.
+ */
+@Category(Appenders.MongoDb.class)
+public class MongoDb3MapMessageTest {
+
+ private static LoggerContextRule loggerContextTestRule = new LoggerContextRule("log4j2-mongodb-map-message.xml");
+
+ private static final AvailablePortSystemPropertyTestRule mongoDbPortTestRule = AvailablePortSystemPropertyTestRule
+ .create(MongoDb3TestConstants.SYS_PROP_NAME_PORT);
+
+ private static final MongoDb3TestRule mongoDbTestRule = new MongoDb3TestRule(mongoDbPortTestRule.getName(),
+ MongoDb3MapMessageTest.class, LoggingTarget.NULL);
+
+ @ClassRule
+ public static RuleChain ruleChain = RuleChainFactory.create(mongoDbPortTestRule, mongoDbTestRule,
+ loggerContextTestRule);
+
+ @Test
+ public void test() {
+ final Logger logger = LogManager.getLogger();
+ final MapMessage<?, Object> mapMessage = new MapMessage<>();
+ mapMessage.with("SomeName", "SomeValue");
+ mapMessage.with("SomeInt", 1);
+ logger.info(mapMessage);
+ //
+ try (final MongoClient mongoClient = mongoDbTestRule.getMongoClient()) {
+ final MongoDatabase database = mongoClient.getDatabase("test");
+ Assert.assertNotNull(database);
+ final MongoCollection<Document> collection = database.getCollection("applog");
+ Assert.assertNotNull(collection);
+ final Document first = collection.find().first();
+ Assert.assertNotNull(first);
+ final String firstJson = first.toJson();
+ Assert.assertEquals(firstJson, "SomeValue", first.getString("SomeName"));
+ Assert.assertEquals(firstJson, Integer.valueOf(1), first.getInteger("SomeInt"));
+ }
+ }
+}
diff --git a/log4j-mongodb3/src/test/java/org/apache/logging/log4j/mongodb3/MongoDb3Test.java b/log4j-mongodb3/src/test/java/org/apache/logging/log4j/mongodb3/MongoDb3Test.java
new file mode 100644
index 0000000..dd0bcd2
--- /dev/null
+++ b/log4j-mongodb3/src/test/java/org/apache/logging/log4j/mongodb3/MongoDb3Test.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.mongodb3;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.categories.Appenders;
+import org.apache.logging.log4j.junit.LoggerContextRule;
+import org.apache.logging.log4j.mongodb3.MongoDb3TestRule.LoggingTarget;
+import org.apache.logging.log4j.test.AvailablePortSystemPropertyTestRule;
+import org.apache.logging.log4j.test.RuleChainFactory;
+import org.bson.Document;
+import org.junit.Assert;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.rules.RuleChain;
+
+import com.mongodb.MongoClient;
+import com.mongodb.client.MongoCollection;
+import com.mongodb.client.MongoDatabase;
+
+/**
+ * This class name does NOT end in "Test" in order to only be picked up by {@link Java8Test}.
+ */
+@Category(Appenders.MongoDb.class)
+public class MongoDb3Test {
+
+ private static LoggerContextRule loggerContextTestRule = new LoggerContextRule("log4j2-mongodb.xml");
+
+ private static final AvailablePortSystemPropertyTestRule mongoDbPortTestRule = AvailablePortSystemPropertyTestRule
+ .create(MongoDb3TestConstants.SYS_PROP_NAME_PORT);
+
+ private static final MongoDb3TestRule mongoDbTestRule = new MongoDb3TestRule(mongoDbPortTestRule.getName(),
+ MongoDb3Test.class, LoggingTarget.NULL);
+
+ @ClassRule
+ public static RuleChain ruleChain = RuleChainFactory.create(mongoDbPortTestRule, mongoDbTestRule,
+ loggerContextTestRule);
+
+ @Test
+ public void test() {
+ final Logger logger = LogManager.getLogger();
+ logger.info("Hello log");
+ try (final MongoClient mongoClient = mongoDbTestRule.getMongoClient()) {
+ final MongoDatabase database = mongoClient.getDatabase("test");
+ Assert.assertNotNull(database);
+ final MongoCollection<Document> collection = database.getCollection("applog");
+ Assert.assertNotNull(collection);
+ final Document first = collection.find().first();
+ Assert.assertNotNull(first);
+ Assert.assertEquals(first.toJson(), "Hello log", first.getString("message"));
+ Assert.assertEquals(first.toJson(), "INFO", first.getString("level"));
+ }
+ }
+}
diff --git a/log4j-mongodb3/src/test/java/org/apache/logging/log4j/mongodb3/MongoDb3TestConstants.java b/log4j-mongodb3/src/test/java/org/apache/logging/log4j/mongodb3/MongoDb3TestConstants.java
new file mode 100644
index 0000000..cfae3e4
--- /dev/null
+++ b/log4j-mongodb3/src/test/java/org/apache/logging/log4j/mongodb3/MongoDb3TestConstants.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.mongodb3;
+
+public class MongoDb3TestConstants {
+
+ public static final String SYS_PROP_NAME_PORT = "MongoDBTestPort";
+
+}
diff --git a/log4j-mongodb3/src/test/java/org/apache/logging/log4j/mongodb3/MongoDb3TestRule.java b/log4j-mongodb3/src/test/java/org/apache/logging/log4j/mongodb3/MongoDb3TestRule.java
new file mode 100644
index 0000000..51c2012
--- /dev/null
+++ b/log4j-mongodb3/src/test/java/org/apache/logging/log4j/mongodb3/MongoDb3TestRule.java
@@ -0,0 +1,192 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+
+package org.apache.logging.log4j.mongodb3;
+
+import java.util.Objects;
+
+import org.apache.commons.lang3.NotImplementedException;
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.mongodb.MongoClient;
+
+import de.flapdoodle.embed.mongo.Command;
+import de.flapdoodle.embed.mongo.MongodExecutable;
+import de.flapdoodle.embed.mongo.MongodProcess;
+import de.flapdoodle.embed.mongo.MongodStarter;
+import de.flapdoodle.embed.mongo.config.MongodConfigBuilder;
+import de.flapdoodle.embed.mongo.config.Net;
+import de.flapdoodle.embed.mongo.config.RuntimeConfigBuilder;
+import de.flapdoodle.embed.mongo.config.Timeout;
+import de.flapdoodle.embed.mongo.distribution.Version;
+import de.flapdoodle.embed.process.config.IRuntimeConfig;
+import de.flapdoodle.embed.process.config.io.ProcessOutput;
+import de.flapdoodle.embed.process.runtime.Network;
+
+/**
+ * A JUnit test rule to manage a MongoDB embedded instance.
+ *
+ * TODO Move this class to Apache Commons Testing.
+ */
+public class MongoDb3TestRule implements TestRule {
+
+ public enum LoggingTarget {
+ CONSOLE, NULL;
+
+ public static LoggingTarget getLoggingTarget(final String sysPropertyName, final LoggingTarget defaultValue) {
+ return LoggingTarget.valueOf(System.getProperty(sysPropertyName, defaultValue.name()));
+ }
+ }
+
+ private static final int BUILDER_TIMEOUT_MILLIS = 30000;
+
+ public static int getBuilderTimeoutMillis() {
+ return BUILDER_TIMEOUT_MILLIS;
+ }
+
+ private static MongodStarter getMongodStarter(final LoggingTarget loggingTarget) {
+ if (loggingTarget == null) {
+ return MongodStarter.getDefaultInstance();
+ }
+ switch (loggingTarget) {
+ case NULL:
+ final Logger logger = LoggerFactory.getLogger(MongoDb3TestRule.class.getName());
+ final IRuntimeConfig runtimeConfig = new RuntimeConfigBuilder()
+ // @formatter:off
+ .defaultsWithLogger(Command.MongoD, logger)
+ .processOutput(ProcessOutput.getDefaultInstanceSilent())
+ .build();
+ // @formatter:on
+
+ return MongodStarter.getInstance(runtimeConfig);
+ case CONSOLE:
+ return MongodStarter.getDefaultInstance();
+ default:
+ throw new NotImplementedException(loggingTarget.toString());
+ }
+ }
+
+ protected final LoggingTarget loggingTarget;
+
+ protected MongoClient mongoClient;
+ protected MongodExecutable mongodExecutable;
+ protected MongodProcess mongodProcess;
+ protected final String portSystemPropertyName;
+
+ /**
+ * Store {@link MongodStarter} (or RuntimeConfig) in a static final field if you want to use artifact store caching
+ * (or else disable caching).
+ * <p>
+ * The test framework {@code de.flapdoodle.embed.mongo} requires Java 8.
+ * </p>
+ */
+ protected final MongodStarter starter;
+
+ /**
+ * Constructs a new test rule.
+ *
+ * @param portSystemPropertyName
+ * The system property name for the MongoDB port.
+ * @param clazz
+ * The test case class.
+ * @param defaultLoggingTarget
+ * The logging target.
+ */
+ public MongoDb3TestRule(final String portSystemPropertyName, final Class<?> clazz,
+ final LoggingTarget defaultLoggingTarget) {
+ this.portSystemPropertyName = Objects.requireNonNull(portSystemPropertyName, "portSystemPropertyName");
+ this.loggingTarget = LoggingTarget.getLoggingTarget(clazz.getName() + "." + LoggingTarget.class.getSimpleName(),
+ defaultLoggingTarget);
+ this.starter = getMongodStarter(this.loggingTarget);
+ }
+
+ @Override
+ public Statement apply(final Statement base, final Description description) {
+ return new Statement() {
+
+ @Override
+ public void evaluate() throws Throwable {
+ final String value = Objects.requireNonNull(System.getProperty(portSystemPropertyName),
+ "System property '" + portSystemPropertyName + "' is null");
+ final int port = Integer.parseInt(value);
+ mongodExecutable = starter.prepare(
+ // @formatter:off
+ new MongodConfigBuilder()
+ .version(Version.Main.PRODUCTION)
+ .timeout(new Timeout(BUILDER_TIMEOUT_MILLIS))
+ .net(
+ new Net("localhost", port, Network.localhostIsIPv6()))
+ .build());
+ // @formatter:on
+ mongodProcess = mongodExecutable.start();
+ mongoClient = new MongoClient("localhost", port);
+ try {
+ base.evaluate();
+ } finally {
+ if (mongodProcess != null) {
+ mongodProcess.stop();
+ mongodProcess = null;
+ }
+ if (mongodExecutable != null) {
+ mongodExecutable.stop();
+ mongodExecutable = null;
+ }
+ }
+ }
+ };
+ }
+
+ public MongoClient getMongoClient() {
+ return mongoClient;
+ }
+
+ public MongodExecutable getMongodExecutable() {
+ return mongodExecutable;
+ }
+
+ public MongodProcess getMongodProcess() {
+ return mongodProcess;
+ }
+
+ public MongodStarter getStarter() {
+ return starter;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder builder = new StringBuilder();
+ builder.append("MongoDbTestRule [starter=");
+ builder.append(starter);
+ builder.append(", portSystemPropertyName=");
+ builder.append(portSystemPropertyName);
+ builder.append(", mongoClient=");
+ builder.append(mongoClient);
+ builder.append(", mongodExecutable=");
+ builder.append(mongodExecutable);
+ builder.append(", mongodProcess=");
+ builder.append(mongodProcess);
+ builder.append(", loggingTarget=");
+ builder.append(loggingTarget);
+ builder.append("]");
+ return builder.toString();
+ }
+
+}
diff --git a/log4j-mongodb3/src/test/java/org/apache/logging/log4j/mongodb3/MongoDb3TestTestRuleTest.java b/log4j-mongodb3/src/test/java/org/apache/logging/log4j/mongodb3/MongoDb3TestTestRuleTest.java
new file mode 100644
index 0000000..ccdf0c8
--- /dev/null
+++ b/log4j-mongodb3/src/test/java/org/apache/logging/log4j/mongodb3/MongoDb3TestTestRuleTest.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.mongodb3;
+
+import org.apache.commons.lang3.JavaVersion;
+import org.apache.commons.lang3.SystemUtils;
+import org.apache.logging.log4j.mongodb3.MongoDb3TestRule.LoggingTarget;
+import org.apache.logging.log4j.test.AvailablePortSystemPropertyTestRule;
+import org.apache.logging.log4j.test.RuleChainFactory;
+import org.junit.Assert;
+import org.junit.Assume;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.rules.RuleChain;
+
+import com.mongodb.client.MongoIterable;
+
+/**
+ * Tests MongoDbRule. This class name does NOT end in "Test" in order to only be picked up by {@link Java8Test}.
+ * <p>
+ * The test framework {@code de.flapdoodle.embed.mongo} requires Java 8.
+ * </p>
+ */
+public class MongoDb3TestTestRuleTest {
+
+ private static final AvailablePortSystemPropertyTestRule mongoDbPortTestRule = AvailablePortSystemPropertyTestRule
+ .create(MongoDb3TestConstants.SYS_PROP_NAME_PORT);
+
+ private static final MongoDb3TestRule mongoDbTestRule = new MongoDb3TestRule(mongoDbPortTestRule.getName(),
+ MongoDb3TestTestRuleTest.class, LoggingTarget.NULL);
+
+ @ClassRule
+ public static RuleChain mongoDbChain = RuleChainFactory.create(mongoDbPortTestRule, mongoDbTestRule);
+
+ @BeforeClass
+ public static void beforeClass() {
+ Assume.assumeTrue(SystemUtils.isJavaVersionAtLeast(JavaVersion.JAVA_1_8));
+ }
+
+ @Test
+ public void testAccess() {
+ final MongoIterable<String> databaseNames = mongoDbTestRule.getMongoClient().listDatabaseNames();
+ Assert.assertNotNull(databaseNames);
+ Assert.assertNotNull(databaseNames.first());
+ }
+
+ @Test
+ public void testMongoDbTestRule() {
+ Assert.assertNotNull(mongoDbTestRule);
+ Assert.assertNotNull(mongoDbTestRule.getStarter());
+ Assert.assertNotNull(mongoDbTestRule.getMongoClient());
+ Assert.assertNotNull(mongoDbTestRule.getMongodExecutable());
+ Assert.assertNotNull(mongoDbTestRule.getMongodProcess());
+ }
+}
diff --git a/log4j-mongodb3/src/test/java/org/apache/logging/log4j/mongodb3/MongoDbAuthFailureTest.java b/log4j-mongodb3/src/test/java/org/apache/logging/log4j/mongodb3/MongoDbAuthFailureTest.java
deleted file mode 100644
index 19a45c8..0000000
--- a/log4j-mongodb3/src/test/java/org/apache/logging/log4j/mongodb3/MongoDbAuthFailureTest.java
+++ /dev/null
@@ -1,72 +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.mongodb3;
-
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-import org.apache.logging.log4j.categories.Appenders;
-import org.apache.logging.log4j.junit.LoggerContextRule;
-import org.apache.logging.log4j.mongodb3.MongoDbTestRule.LoggingTarget;
-import org.apache.logging.log4j.test.AvailablePortSystemPropertyTestRule;
-import org.apache.logging.log4j.test.RuleChainFactory;
-import org.bson.Document;
-import org.junit.Assert;
-import org.junit.ClassRule;
-import org.junit.Ignore;
-import org.junit.Test;
-import org.junit.experimental.categories.Category;
-import org.junit.rules.RuleChain;
-
-import com.mongodb.MongoClient;
-import com.mongodb.client.MongoCollection;
-import com.mongodb.client.MongoDatabase;
-
-/**
- * This class name does NOT end in "Test" in order to only be picked up by {@link Java8Test}.
- *
- * TODO Set up the log4j user in MongoDB.
- */
-@Ignore("TODO Set up the log4j user in MongoDB")
-@Category(Appenders.MongoDb.class)
-public class MongoDbAuthFailureTest {
-
- private static LoggerContextRule loggerContextTestRule = new LoggerContextRule("log4j2-mongodb-auth-failure.xml");
-
- private static final AvailablePortSystemPropertyTestRule mongoDbPortTestRule = AvailablePortSystemPropertyTestRule
- .create(TestConstants.SYS_PROP_NAME_PORT);
-
- private static final MongoDbTestRule mongoDbTestRule = new MongoDbTestRule(mongoDbPortTestRule.getName(),
- MongoDbAuthFailureTest.class, LoggingTarget.NULL);
-
- @ClassRule
- public static RuleChain ruleChain = RuleChainFactory.create(mongoDbPortTestRule, mongoDbTestRule,
- loggerContextTestRule);
-
- @Test
- public void test() {
- final Logger logger = LogManager.getLogger();
- logger.info("Hello log");
- try (final MongoClient mongoClient = mongoDbTestRule.getMongoClient()) {
- final MongoDatabase database = mongoClient.getDatabase("test");
- Assert.assertNotNull(database);
- final MongoCollection<Document> collection = database.getCollection("applog");
- Assert.assertNotNull(collection);
- final Document first = collection.find().first();
- Assert.assertNull(first);
- }
- }
-}
diff --git a/log4j-mongodb3/src/test/java/org/apache/logging/log4j/mongodb3/MongoDbCappedTest.java b/log4j-mongodb3/src/test/java/org/apache/logging/log4j/mongodb3/MongoDbCappedTest.java
deleted file mode 100644
index 7611284..0000000
--- a/log4j-mongodb3/src/test/java/org/apache/logging/log4j/mongodb3/MongoDbCappedTest.java
+++ /dev/null
@@ -1,69 +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.mongodb3;
-
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-import org.apache.logging.log4j.categories.Appenders;
-import org.apache.logging.log4j.junit.LoggerContextRule;
-import org.apache.logging.log4j.mongodb3.MongoDbTestRule.LoggingTarget;
-import org.apache.logging.log4j.test.AvailablePortSystemPropertyTestRule;
-import org.apache.logging.log4j.test.RuleChainFactory;
-import org.bson.Document;
-import org.junit.Assert;
-import org.junit.ClassRule;
-import org.junit.Test;
-import org.junit.experimental.categories.Category;
-import org.junit.rules.RuleChain;
-
-import com.mongodb.MongoClient;
-import com.mongodb.client.MongoCollection;
-import com.mongodb.client.MongoDatabase;
-
-/**
- * This class name does NOT end in "Test" in order to only be picked up by {@link Java8Test}.
- */
-@Category(Appenders.MongoDb.class)
-public class MongoDbCappedTest {
-
- private static LoggerContextRule loggerContextTestRule = new LoggerContextRule("log4j2-mongodb-capped.xml");
-
- private static final AvailablePortSystemPropertyTestRule mongoDbPortTestRule = AvailablePortSystemPropertyTestRule
- .create(TestConstants.SYS_PROP_NAME_PORT);
-
- private static final MongoDbTestRule mongoDbTestRule = new MongoDbTestRule(mongoDbPortTestRule.getName(),
- MongoDbCappedTest.class, LoggingTarget.NULL);
-
- @ClassRule
- public static RuleChain ruleChain = RuleChainFactory.create(mongoDbPortTestRule, mongoDbTestRule,
- loggerContextTestRule);
-
- @Test
- public void test() {
- final Logger logger = LogManager.getLogger();
- logger.info("Hello log");
- try (final MongoClient mongoClient = mongoDbTestRule.getMongoClient()) {
- final MongoDatabase database = mongoClient.getDatabase("test");
- Assert.assertNotNull(database);
- final MongoCollection<Document> collection = database.getCollection("applog");
- Assert.assertNotNull(collection);
- final Document first = collection.find().first();
- Assert.assertNotNull(first);
- Assert.assertEquals(first.toJson(), "Hello log", first.getString("message"));
- }
- }
-}
diff --git a/log4j-mongodb3/src/test/java/org/apache/logging/log4j/mongodb3/MongoDbMapMessageTest.java b/log4j-mongodb3/src/test/java/org/apache/logging/log4j/mongodb3/MongoDbMapMessageTest.java
deleted file mode 100644
index 70f910e..0000000
--- a/log4j-mongodb3/src/test/java/org/apache/logging/log4j/mongodb3/MongoDbMapMessageTest.java
+++ /dev/null
@@ -1,76 +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.mongodb3;
-
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-import org.apache.logging.log4j.categories.Appenders;
-import org.apache.logging.log4j.junit.LoggerContextRule;
-import org.apache.logging.log4j.message.MapMessage;
-import org.apache.logging.log4j.mongodb3.MongoDbTestRule.LoggingTarget;
-import org.apache.logging.log4j.test.AvailablePortSystemPropertyTestRule;
-import org.apache.logging.log4j.test.RuleChainFactory;
-import org.bson.Document;
-import org.junit.Assert;
-import org.junit.ClassRule;
-import org.junit.Test;
-import org.junit.experimental.categories.Category;
-import org.junit.rules.RuleChain;
-
-import com.mongodb.MongoClient;
-import com.mongodb.client.MongoCollection;
-import com.mongodb.client.MongoDatabase;
-
-/**
- * This class name does NOT end in "Test" in order to only be picked up by {@link Java8Test}.
- */
-@Category(Appenders.MongoDb.class)
-public class MongoDbMapMessageTest {
-
- private static LoggerContextRule loggerContextTestRule = new LoggerContextRule("log4j2-mongodb-map-message.xml");
-
- private static final AvailablePortSystemPropertyTestRule mongoDbPortTestRule = AvailablePortSystemPropertyTestRule
- .create(TestConstants.SYS_PROP_NAME_PORT);
-
- private static final MongoDbTestRule mongoDbTestRule = new MongoDbTestRule(mongoDbPortTestRule.getName(),
- MongoDbMapMessageTest.class, LoggingTarget.NULL);
-
- @ClassRule
- public static RuleChain ruleChain = RuleChainFactory.create(mongoDbPortTestRule, mongoDbTestRule,
- loggerContextTestRule);
-
- @Test
- public void test() {
- final Logger logger = LogManager.getLogger();
- final MapMessage mapMessage = new MapMessage();
- mapMessage.with("SomeName", "SomeValue");
- mapMessage.with("SomeInt", 1);
- logger.info(mapMessage);
- //
- try (final MongoClient mongoClient = mongoDbTestRule.getMongoClient()) {
- final MongoDatabase database = mongoClient.getDatabase("test");
- Assert.assertNotNull(database);
- final MongoCollection<Document> collection = database.getCollection("applog");
- Assert.assertNotNull(collection);
- final Document first = collection.find().first();
- Assert.assertNotNull(first);
- final String firstJson = first.toJson();
- Assert.assertEquals(firstJson, "SomeValue", first.getString("SomeName"));
- Assert.assertEquals(firstJson, Integer.valueOf(1), first.getInteger("SomeInt"));
- }
- }
-}
diff --git a/log4j-mongodb3/src/test/java/org/apache/logging/log4j/mongodb3/MongoDbTest.java b/log4j-mongodb3/src/test/java/org/apache/logging/log4j/mongodb3/MongoDbTest.java
deleted file mode 100644
index eab64b3..0000000
--- a/log4j-mongodb3/src/test/java/org/apache/logging/log4j/mongodb3/MongoDbTest.java
+++ /dev/null
@@ -1,69 +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.mongodb3;
-
-import org.apache.logging.log4j.LogManager;
-import org.apache.logging.log4j.Logger;
-import org.apache.logging.log4j.categories.Appenders;
-import org.apache.logging.log4j.junit.LoggerContextRule;
-import org.apache.logging.log4j.mongodb3.MongoDbTestRule.LoggingTarget;
-import org.apache.logging.log4j.test.AvailablePortSystemPropertyTestRule;
-import org.apache.logging.log4j.test.RuleChainFactory;
-import org.bson.Document;
-import org.junit.Assert;
-import org.junit.ClassRule;
-import org.junit.Test;
-import org.junit.experimental.categories.Category;
-import org.junit.rules.RuleChain;
-
-import com.mongodb.MongoClient;
-import com.mongodb.client.MongoCollection;
-import com.mongodb.client.MongoDatabase;
-
-/**
- * This class name does NOT end in "Test" in order to only be picked up by {@link Java8Test}.
- */
-@Category(Appenders.MongoDb.class)
-public class MongoDbTest {
-
- private static LoggerContextRule loggerContextTestRule = new LoggerContextRule("log4j2-mongodb.xml");
-
- private static final AvailablePortSystemPropertyTestRule mongoDbPortTestRule = AvailablePortSystemPropertyTestRule
- .create(TestConstants.SYS_PROP_NAME_PORT);
-
- private static final MongoDbTestRule mongoDbTestRule = new MongoDbTestRule(mongoDbPortTestRule.getName(),
- MongoDbTest.class, LoggingTarget.NULL);
-
- @ClassRule
- public static RuleChain ruleChain = RuleChainFactory.create(mongoDbPortTestRule, mongoDbTestRule,
- loggerContextTestRule);
-
- @Test
- public void test() {
- final Logger logger = LogManager.getLogger();
- logger.info("Hello log");
- try (final MongoClient mongoClient = mongoDbTestRule.getMongoClient()) {
- final MongoDatabase database = mongoClient.getDatabase("test");
- Assert.assertNotNull(database);
- final MongoCollection<Document> collection = database.getCollection("applog");
- Assert.assertNotNull(collection);
- final Document first = collection.find().first();
- Assert.assertNotNull(first);
- Assert.assertEquals(first.toJson(), "Hello log", first.getString("message"));
- }
- }
-}
diff --git a/log4j-mongodb3/src/test/java/org/apache/logging/log4j/mongodb3/MongoDbTestRule.java b/log4j-mongodb3/src/test/java/org/apache/logging/log4j/mongodb3/MongoDbTestRule.java
deleted file mode 100644
index 96a903b..0000000
--- a/log4j-mongodb3/src/test/java/org/apache/logging/log4j/mongodb3/MongoDbTestRule.java
+++ /dev/null
@@ -1,192 +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.mongodb3;
-
-import java.util.Objects;
-
-import org.apache.commons.lang3.NotImplementedException;
-import org.junit.rules.TestRule;
-import org.junit.runner.Description;
-import org.junit.runners.model.Statement;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.mongodb.MongoClient;
-
-import de.flapdoodle.embed.mongo.Command;
-import de.flapdoodle.embed.mongo.MongodExecutable;
-import de.flapdoodle.embed.mongo.MongodProcess;
-import de.flapdoodle.embed.mongo.MongodStarter;
-import de.flapdoodle.embed.mongo.config.MongodConfigBuilder;
-import de.flapdoodle.embed.mongo.config.Net;
-import de.flapdoodle.embed.mongo.config.RuntimeConfigBuilder;
-import de.flapdoodle.embed.mongo.config.Timeout;
-import de.flapdoodle.embed.mongo.distribution.Version;
-import de.flapdoodle.embed.process.config.IRuntimeConfig;
-import de.flapdoodle.embed.process.config.io.ProcessOutput;
-import de.flapdoodle.embed.process.runtime.Network;
-
-/**
- * A JUnit test rule to manage a MongoDB embedded instance.
- *
- * TODO Move this class to Apache Commons Testing.
- */
-public class MongoDbTestRule implements TestRule {
-
- public enum LoggingTarget {
- CONSOLE, NULL;
-
- public static LoggingTarget getLoggingTarget(final String sysPropertyName, final LoggingTarget defaultValue) {
- return LoggingTarget.valueOf(System.getProperty(sysPropertyName, defaultValue.name()));
- }
- }
-
- private static final int BUILDER_TIMEOUT_MILLIS = 30000;
-
- public static int getBuilderTimeoutMillis() {
- return BUILDER_TIMEOUT_MILLIS;
- }
-
- private static MongodStarter getMongodStarter(final LoggingTarget loggingTarget) {
- if (loggingTarget == null) {
- return MongodStarter.getDefaultInstance();
- }
- switch (loggingTarget) {
- case NULL:
- final Logger logger = LoggerFactory.getLogger(MongoDbTestRule.class.getName());
- final IRuntimeConfig runtimeConfig = new RuntimeConfigBuilder()
- // @formatter:off
- .defaultsWithLogger(Command.MongoD, logger)
- .processOutput(ProcessOutput.getDefaultInstanceSilent())
- .build();
- // @formatter:on
-
- return MongodStarter.getInstance(runtimeConfig);
- case CONSOLE:
- return MongodStarter.getDefaultInstance();
- default:
- throw new NotImplementedException(loggingTarget.toString());
- }
- }
-
- protected final LoggingTarget loggingTarget;
-
- protected MongoClient mongoClient;
- protected MongodExecutable mongodExecutable;
- protected MongodProcess mongodProcess;
- protected final String portSystemPropertyName;
-
- /**
- * Store {@link MongodStarter} (or RuntimeConfig) in a static final field if you want to use artifact store caching
- * (or else disable caching).
- * <p>
- * The test framework {@code de.flapdoodle.embed.mongo} requires Java 8.
- * </p>
- */
- protected final MongodStarter starter;
-
- /**
- * Constructs a new test rule.
- *
- * @param portSystemPropertyName
- * The system property name for the MongoDB port.
- * @param clazz
- * The test case class.
- * @param defaultLoggingTarget
- * The logging target.
- */
- public MongoDbTestRule(final String portSystemPropertyName, final Class<?> clazz,
- final LoggingTarget defaultLoggingTarget) {
- this.portSystemPropertyName = Objects.requireNonNull(portSystemPropertyName, "portSystemPropertyName");
- this.loggingTarget = LoggingTarget.getLoggingTarget(clazz.getName() + "." + LoggingTarget.class.getSimpleName(),
- defaultLoggingTarget);
- this.starter = getMongodStarter(this.loggingTarget);
- }
-
- @Override
- public Statement apply(final Statement base, final Description description) {
- return new Statement() {
-
- @Override
- public void evaluate() throws Throwable {
- final String value = Objects.requireNonNull(System.getProperty(portSystemPropertyName),
- "System property '" + portSystemPropertyName + "' is null");
- final int port = Integer.parseInt(value);
- mongodExecutable = starter.prepare(
- // @formatter:off
- new MongodConfigBuilder()
- .version(Version.Main.PRODUCTION)
- .timeout(new Timeout(BUILDER_TIMEOUT_MILLIS))
- .net(
- new Net("localhost", port, Network.localhostIsIPv6()))
- .build());
- // @formatter:on
- mongodProcess = mongodExecutable.start();
- mongoClient = new MongoClient("localhost", port);
- try {
- base.evaluate();
- } finally {
- if (mongodProcess != null) {
- mongodProcess.stop();
- mongodProcess = null;
- }
- if (mongodExecutable != null) {
- mongodExecutable.stop();
- mongodExecutable = null;
- }
- }
- }
- };
- }
-
- public MongoClient getMongoClient() {
- return mongoClient;
- }
-
- public MongodExecutable getMongodExecutable() {
- return mongodExecutable;
- }
-
- public MongodProcess getMongodProcess() {
- return mongodProcess;
- }
-
- public MongodStarter getStarter() {
- return starter;
- }
-
- @Override
- public String toString() {
- StringBuilder builder = new StringBuilder();
- builder.append("MongoDbTestRule [starter=");
- builder.append(starter);
- builder.append(", portSystemPropertyName=");
- builder.append(portSystemPropertyName);
- builder.append(", mongoClient=");
- builder.append(mongoClient);
- builder.append(", mongodExecutable=");
- builder.append(mongodExecutable);
- builder.append(", mongodProcess=");
- builder.append(mongodProcess);
- builder.append(", loggingTarget=");
- builder.append(loggingTarget);
- builder.append("]");
- return builder.toString();
- }
-
-}
\ No newline at end of file
diff --git a/log4j-mongodb3/src/test/java/org/apache/logging/log4j/mongodb3/MongoDbTestTestRuleTest.java b/log4j-mongodb3/src/test/java/org/apache/logging/log4j/mongodb3/MongoDbTestTestRuleTest.java
deleted file mode 100644
index e19980b..0000000
--- a/log4j-mongodb3/src/test/java/org/apache/logging/log4j/mongodb3/MongoDbTestTestRuleTest.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.mongodb3;
-
-import org.apache.commons.lang3.JavaVersion;
-import org.apache.commons.lang3.SystemUtils;
-import org.apache.logging.log4j.mongodb3.MongoDbTestRule.LoggingTarget;
-import org.apache.logging.log4j.test.AvailablePortSystemPropertyTestRule;
-import org.apache.logging.log4j.test.RuleChainFactory;
-import org.junit.Assert;
-import org.junit.Assume;
-import org.junit.BeforeClass;
-import org.junit.ClassRule;
-import org.junit.Test;
-import org.junit.rules.RuleChain;
-
-import com.mongodb.client.MongoIterable;
-
-/**
- * Tests MongoDbRule. This class name does NOT end in "Test" in order to only be picked up by {@link Java8Test}.
- * <p>
- * The test framework {@code de.flapdoodle.embed.mongo} requires Java 8.
- * </p>
- */
-public class MongoDbTestTestRuleTest {
-
- private static final AvailablePortSystemPropertyTestRule mongoDbPortTestRule = AvailablePortSystemPropertyTestRule
- .create(TestConstants.SYS_PROP_NAME_PORT);
-
- private static final MongoDbTestRule mongoDbTestRule = new MongoDbTestRule(mongoDbPortTestRule.getName(),
- MongoDbTestTestRuleTest.class, LoggingTarget.NULL);
-
- @ClassRule
- public static RuleChain mongoDbChain = RuleChainFactory.create(mongoDbPortTestRule, mongoDbTestRule);
-
- @BeforeClass
- public static void beforeClass() {
- Assume.assumeTrue(SystemUtils.isJavaVersionAtLeast(JavaVersion.JAVA_1_8));
- }
-
- @Test
- public void testAccess() {
- final MongoIterable<String> databaseNames = mongoDbTestRule.getMongoClient().listDatabaseNames();
- Assert.assertNotNull(databaseNames);
- Assert.assertNotNull(databaseNames.first());
- }
-
- @Test
- public void testMongoDbTestRule() {
- Assert.assertNotNull(mongoDbTestRule);
- Assert.assertNotNull(mongoDbTestRule.getStarter());
- Assert.assertNotNull(mongoDbTestRule.getMongoClient());
- Assert.assertNotNull(mongoDbTestRule.getMongodExecutable());
- Assert.assertNotNull(mongoDbTestRule.getMongodProcess());
- }
-}
diff --git a/log4j-mongodb3/src/test/java/org/apache/logging/log4j/mongodb3/TestConstants.java b/log4j-mongodb3/src/test/java/org/apache/logging/log4j/mongodb3/TestConstants.java
deleted file mode 100644
index 3e7e0f3..0000000
--- a/log4j-mongodb3/src/test/java/org/apache/logging/log4j/mongodb3/TestConstants.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.mongodb3;
-
-public class TestConstants {
-
- public static final String SYS_PROP_NAME_PORT = "MongoDBTestPort";
-
-}
diff --git a/log4j-mongodb4/pom.xml b/log4j-mongodb4/pom.xml
new file mode 100644
index 0000000..bbd9629
--- /dev/null
+++ b/log4j-mongodb4/pom.xml
@@ -0,0 +1,199 @@
+<?xml version="1.0" encoding="UTF-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 "License"); you may not use this file except in compliance with
+ ~ the License. You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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 xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <parent>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j</artifactId>
+ <version>3.0.0-SNAPSHOT</version>
+ </parent>
+ <modelVersion>4.0.0</modelVersion>
+
+ <artifactId>log4j-mongodb4</artifactId>
+ <name>Apache Log4j MongoDB 4</name>
+ <description>
+ MongoDB appender for Log4j using the MongoDB 4 driver API.
+ </description>
+ <properties>
+ <log4jParentDir>${basedir}/..</log4jParentDir>
+ <docLabel>MongoDB 4 Documentation</docLabel>
+ <projectDir>/log4j-mongodb4</projectDir>
+ <module.name>org.apache.logging.log4j.mongodb4</module.name>
+ </properties>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.mongodb</groupId>
+ <artifactId>mongodb-driver-sync</artifactId>
+ <version>${mongodb4.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.mongodb</groupId>
+ <artifactId>bson</artifactId>
+ <version>${mongodb4.version}</version>
+ </dependency>
+ <!-- Test Dependencies -->
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-core</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-api</artifactId>
+ <type>test-jar</type>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-core</artifactId>
+ <type>test-jar</type>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>de.flapdoodle.embed</groupId>
+ <artifactId>de.flapdoodle.embed.mongo</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <configuration>
+ <instructions>
+ <Fragment-Host>org.apache.logging.log4j.core</Fragment-Host>
+ <Export-Package>*</Export-Package>
+ </instructions>
+ </configuration>
+ </plugin>
+ <!-- workaround flaky "Operation not permitted" failures when running tests in parallel -->
+ <plugin>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <forkCount>1</forkCount>
+ <reuseForks>false</reuseForks>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ <reporting>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-changes-plugin</artifactId>
+ <version>${changes.plugin.version}</version>
+ <reportSets>
+ <reportSet>
+ <reports>
+ <report>changes-report</report>
+ </reports>
+ </reportSet>
+ </reportSets>
+ <configuration>
+ <issueLinkTemplate>%URL%/show_bug.cgi?id=%ISSUE%</issueLinkTemplate>
+ <useJql>true</useJql>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-checkstyle-plugin</artifactId>
+ <version>${checkstyle.plugin.version}</version>
+ <configuration>
+ <!--<propertiesLocation>${vfs.parent.dir}/checkstyle.properties</propertiesLocation> -->
+ <configLocation>${log4jParentDir}/checkstyle.xml</configLocation>
+ <suppressionsLocation>${log4jParentDir}/checkstyle-suppressions.xml</suppressionsLocation>
+ <enableRulesSummary>false</enableRulesSummary>
+ <propertyExpansion>basedir=${basedir}</propertyExpansion>
+ <propertyExpansion>licensedir=${log4jParentDir}/checkstyle-header.txt</propertyExpansion>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-javadoc-plugin</artifactId>
+ <version>${javadoc.plugin.version}</version>
+ <configuration>
+ <bottom><![CDATA[<p align="center">Copyright © {inceptionYear}-{currentYear} {organizationName}. All Rights Reserved.<br />
+ Apache Logging, Apache Log4j, Log4j, Apache, the Apache feather logo, the Apache Logging project logo,
+ and the Apache Log4j logo are trademarks of The Apache Software Foundation.</p>]]></bottom>
+ <!-- module link generation is completely broken in the javadoc plugin for a multi-module non-aggregating
+ project -->
+ <detectOfflineLinks>false</detectOfflineLinks>
+ <linksource>true</linksource>
+ <source>8</source>
+ </configuration>
+ <reportSets>
+ <reportSet>
+ <id>non-aggregate</id>
+ <reports>
+ <report>javadoc</report>
+ </reports>
+ </reportSet>
+ </reportSets>
+ </plugin>
+ <plugin>
+ <groupId>com.github.spotbugs</groupId>
+ <artifactId>spotbugs-maven-plugin</artifactId>
+ <configuration>
+ <fork>true</fork>
+ <jvmArgs>-Duser.language=en</jvmArgs>
+ <threshold>Normal</threshold>
+ <effort>Default</effort>
+ <excludeFilterFile>${log4jParentDir}/spotbugs-exclude-filter.xml</excludeFilterFile>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jxr-plugin</artifactId>
+ <version>${jxr.plugin.version}</version>
+ <reportSets>
+ <reportSet>
+ <id>non-aggregate</id>
+ <reports>
+ <report>jxr</report>
+ </reports>
+ </reportSet>
+ <reportSet>
+ <id>aggregate</id>
+ <reports>
+ <report>aggregate</report>
+ </reports>
+ </reportSet>
+ </reportSets>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-pmd-plugin</artifactId>
+ <version>${pmd.plugin.version}</version>
+ <configuration>
+ <targetJdk>${maven.compiler.target}</targetJdk>
+ </configuration>
+ </plugin>
+ </plugins>
+ </reporting>
+</project>
diff --git a/log4j-mongodb4/src/main/java/org/apache/logging/log4j/mongodb4/MongoDb4Connection.java b/log4j-mongodb4/src/main/java/org/apache/logging/log4j/mongodb4/MongoDb4Connection.java
new file mode 100644
index 0000000..509003f
--- /dev/null
+++ b/log4j-mongodb4/src/main/java/org/apache/logging/log4j/mongodb4/MongoDb4Connection.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.mongodb4;
+
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.core.appender.AppenderLoggingException;
+import org.apache.logging.log4j.core.appender.nosql.AbstractNoSqlConnection;
+import org.apache.logging.log4j.core.appender.nosql.NoSqlConnection;
+import org.apache.logging.log4j.core.appender.nosql.NoSqlObject;
+import org.apache.logging.log4j.status.StatusLogger;
+import org.bson.Document;
+
+import com.mongodb.ConnectionString;
+import com.mongodb.MongoException;
+import com.mongodb.client.MongoClient;
+import com.mongodb.client.MongoCollection;
+import com.mongodb.client.MongoDatabase;
+import com.mongodb.client.model.CreateCollectionOptions;
+import com.mongodb.client.result.InsertOneResult;
+
+/**
+ * The MongoDB implementation of {@link NoSqlConnection}.
+ */
+public final class MongoDb4Connection extends AbstractNoSqlConnection<Document, MongoDb4DocumentObject> {
+
+ private static final Logger LOGGER = StatusLogger.getLogger();
+
+ private static MongoCollection<Document> getOrCreateMongoCollection(final MongoDatabase database,
+ final String collectionName, final boolean isCapped, final Integer sizeInBytes) {
+ try {
+ LOGGER.debug("Gettting collection '{}'...", collectionName);
+ // throws IllegalArgumentException if collectionName is invalid
+ final MongoCollection<Document> found = database.getCollection(collectionName);
+ LOGGER.debug("Got collection {}", found);
+ return found;
+ } catch (final IllegalStateException e) {
+ LOGGER.debug("Collection '{}' does not exist.", collectionName);
+ final CreateCollectionOptions options = new CreateCollectionOptions().capped(isCapped)
+ .sizeInBytes(sizeInBytes);
+ LOGGER.debug("Creating collection '{}' with options {}...", collectionName, options);
+ database.createCollection(collectionName, options);
+ LOGGER.debug("Created collection.");
+ final MongoCollection<Document> created = database.getCollection(collectionName);
+ LOGGER.debug("Got created collection {}", created);
+ return created;
+ }
+
+ }
+
+ private final ConnectionString connectionString;
+ private final MongoCollection<Document> collection;
+ private final MongoClient mongoClient;
+
+ public MongoDb4Connection(final ConnectionString connectionString, final MongoClient mongoClient,
+ final MongoDatabase mongoDatabase, final boolean isCapped, final Integer sizeInBytes) {
+ this.connectionString = connectionString;
+ this.mongoClient = mongoClient;
+ this.collection = getOrCreateMongoCollection(mongoDatabase, connectionString.getCollection(), isCapped,
+ sizeInBytes);
+ }
+
+ @Override
+ public void closeImpl() {
+ // LOG4J2-1196
+ mongoClient.close();
+ }
+
+ @Override
+ public MongoDb4DocumentObject[] createList(final int length) {
+ return new MongoDb4DocumentObject[length];
+ }
+
+ @Override
+ public MongoDb4DocumentObject createObject() {
+ return new MongoDb4DocumentObject();
+ }
+
+ @Override
+ public void insertObject(final NoSqlObject<Document> object) {
+ try {
+ final Document unwrapped = object.unwrap();
+ LOGGER.debug("Inserting BSON Document {}", unwrapped);
+ InsertOneResult insertOneResult = this.collection.insertOne(unwrapped);
+ LOGGER.debug("Insert MongoDb result {}", insertOneResult);
+ } catch (final MongoException e) {
+ throw new AppenderLoggingException("Failed to write log event to MongoDB due to error: " + e.getMessage(),
+ e);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return String.format("Mongo4Connection [connectionString=%s, collection=%s, mongoClient=%s]", connectionString,
+ collection, mongoClient);
+ }
+
+}
diff --git a/log4j-mongodb4/src/main/java/org/apache/logging/log4j/mongodb4/MongoDb4DocumentObject.java b/log4j-mongodb4/src/main/java/org/apache/logging/log4j/mongodb4/MongoDb4DocumentObject.java
new file mode 100644
index 0000000..707479d
--- /dev/null
+++ b/log4j-mongodb4/src/main/java/org/apache/logging/log4j/mongodb4/MongoDb4DocumentObject.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.mongodb4;
+
+import java.util.Arrays;
+
+import org.apache.logging.log4j.core.appender.nosql.NoSqlObject;
+import org.bson.Document;
+
+/**
+ * The MongoDB implementation of {@link NoSqlObject} typed to a BSON
+ * {@link Document}.
+ */
+public final class MongoDb4DocumentObject implements NoSqlObject<Document> {
+ private final Document document;
+
+ public MongoDb4DocumentObject() {
+ this.document = new Document();
+ }
+
+ @Override
+ public void set(final String field, final NoSqlObject<Document> value) {
+ this.document.append(field, value.unwrap());
+ }
+
+ @Override
+ public void set(final String field, final NoSqlObject<Document>[] values) {
+ this.document.append(field, Arrays.asList(values));
+ }
+
+ @Override
+ public void set(final String field, final Object value) {
+ this.document.append(field, value);
+ }
+
+ @Override
+ public void set(final String field, final Object[] values) {
+ this.document.append(field, Arrays.asList(values));
+ }
+
+ @Override
+ public String toString() {
+ return String.format("Mongo4DocumentObject [document=%s]", document);
+ }
+
+ @Override
+ public Document unwrap() {
+ return this.document;
+ }
+}
diff --git a/log4j-mongodb4/src/main/java/org/apache/logging/log4j/mongodb4/MongoDb4LevelCodec.java b/log4j-mongodb4/src/main/java/org/apache/logging/log4j/mongodb4/MongoDb4LevelCodec.java
new file mode 100644
index 0000000..ea1b5ba
--- /dev/null
+++ b/log4j-mongodb4/src/main/java/org/apache/logging/log4j/mongodb4/MongoDb4LevelCodec.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.mongodb4;
+
+import org.apache.logging.log4j.Level;
+import org.bson.BsonReader;
+import org.bson.BsonWriter;
+import org.bson.codecs.Codec;
+import org.bson.codecs.DecoderContext;
+import org.bson.codecs.EncoderContext;
+
+/**
+ * A BSON {@link Codec} for Log4j {@link Level}s.
+ */
+public class MongoDb4LevelCodec implements Codec<Level> {
+
+ /**
+ * The singleton instance.
+ */
+ public static final MongoDb4LevelCodec INSTANCE = new MongoDb4LevelCodec();
+
+ @Override
+ public Level decode(final BsonReader reader, final DecoderContext decoderContext) {
+ return Level.getLevel(reader.readString());
+ }
+
+ @Override
+ public void encode(final BsonWriter writer, final Level level, final EncoderContext encoderContext) {
+ writer.writeString(level.name());
+ }
+
+ @Override
+ public Class<Level> getEncoderClass() {
+ return Level.class;
+ }
+
+}
diff --git a/log4j-mongodb4/src/main/java/org/apache/logging/log4j/mongodb4/MongoDb4Provider.java b/log4j-mongodb4/src/main/java/org/apache/logging/log4j/mongodb4/MongoDb4Provider.java
new file mode 100644
index 0000000..3f7fb2f
--- /dev/null
+++ b/log4j-mongodb4/src/main/java/org/apache/logging/log4j/mongodb4/MongoDb4Provider.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.mongodb4;
+
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.core.Core;
+import org.apache.logging.log4j.core.appender.nosql.NoSqlProvider;
+import org.apache.logging.log4j.core.config.plugins.PluginBuilderAttribute;
+import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
+import org.apache.logging.log4j.core.filter.AbstractFilterable;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.validation.constraints.Required;
+import org.apache.logging.log4j.status.StatusLogger;
+import org.bson.codecs.configuration.CodecRegistries;
+import org.bson.codecs.configuration.CodecRegistry;
+
+import com.mongodb.ConnectionString;
+import com.mongodb.MongoClientSettings;
+import com.mongodb.client.MongoClient;
+import com.mongodb.client.MongoClients;
+import com.mongodb.client.MongoDatabase;
+
+/**
+ * The MongoDB implementation of {@link NoSqlProvider} using the MongoDB driver
+ * version 4 API.
+ */
+@Plugin(name = "MongoDb4", category = Core.CATEGORY_NAME, printObject = true)
+public final class MongoDb4Provider implements NoSqlProvider<MongoDb4Connection> {
+
+ public static class Builder<B extends Builder<B>> extends AbstractFilterable.Builder<B>
+ implements org.apache.logging.log4j.core.util.Builder<MongoDb4Provider> {
+
+ @PluginBuilderAttribute(value = "connection")
+ @Required(message = "No connection string provided")
+ private String connection;
+
+ @PluginBuilderAttribute
+ private int collectionSize = DEFAULT_COLLECTION_SIZE;
+
+ @PluginBuilderAttribute("capped")
+ private boolean capped = false;
+
+ @Override
+ public MongoDb4Provider build() {
+ return new MongoDb4Provider(connection, capped, collectionSize);
+ }
+
+ public B setCapped(final boolean isCapped) {
+ this.capped = isCapped;
+ return asBuilder();
+ }
+
+ public B setCollectionSize(final int collectionSize) {
+ this.collectionSize = collectionSize;
+ return asBuilder();
+ }
+ }
+
+ private static final Logger LOGGER = StatusLogger.getLogger();
+
+ // @formatter:off
+ private static final CodecRegistry CODEC_REGISTRIES = CodecRegistries.fromRegistries(
+ MongoClientSettings.getDefaultCodecRegistry(),
+ CodecRegistries.fromCodecs(MongoDb4LevelCodec.INSTANCE));
+ // @formatter:on
+
+ // TODO Where does this number come from?
+ private static final int DEFAULT_COLLECTION_SIZE = 536_870_912;
+
+ @PluginBuilderFactory
+ public static <B extends Builder<B>> B newBuilder() {
+ return new Builder<B>().asBuilder();
+ }
+
+ private final Integer collectionSize;
+ private final boolean isCapped;
+ private final MongoClient mongoClient;
+ private final MongoDatabase mongoDatabase;
+ private final ConnectionString connectionString;
+
+ private MongoDb4Provider(final String connectionStringSource, final boolean isCapped,
+ final Integer collectionSize) {
+ LOGGER.debug("Creating ConnectionString {}...", connectionStringSource);
+ this.connectionString = new ConnectionString(connectionStringSource);
+ LOGGER.debug("Created ConnectionString {}", connectionString);
+ LOGGER.debug("Creating MongoClientSettings...");
+ // @formatter:off
+ final MongoClientSettings settings = MongoClientSettings.builder()
+ .applyConnectionString(this.connectionString)
+ .codecRegistry(CODEC_REGISTRIES)
+ .build();
+ // @formatter:on
+ LOGGER.debug("Created MongoClientSettings {}", settings);
+ LOGGER.debug("Creating MongoClient {}...", settings);
+ this.mongoClient = MongoClients.create(settings);
+ LOGGER.debug("Created MongoClient {}", mongoClient);
+ String databaseName = this.connectionString.getDatabase();
+ LOGGER.debug("Getting MongoDatabase {}...", databaseName);
+ this.mongoDatabase = this.mongoClient.getDatabase(databaseName);
+ LOGGER.debug("Got MongoDatabase {}", mongoDatabase);
+ this.isCapped = isCapped;
+ this.collectionSize = collectionSize;
+ }
+
+ @Override
+ public MongoDb4Connection getConnection() {
+ return new MongoDb4Connection(connectionString, mongoClient, mongoDatabase, isCapped, collectionSize);
+ }
+
+ @Override
+ public String toString() {
+ return String.format(
+ "%s [connectionString=%s, collectionSize=%s, isCapped=%s, mongoClient=%s, mongoDatabase=%s]",
+ MongoDb4Provider.class.getSimpleName(), connectionString, collectionSize, isCapped, mongoClient,
+ mongoDatabase);
+ }
+
+}
diff --git a/log4j-mongodb4/src/main/java/org/apache/logging/log4j/mongodb4/package-info.java b/log4j-mongodb4/src/main/java/org/apache/logging/log4j/mongodb4/package-info.java
new file mode 100644
index 0000000..8f380b3
--- /dev/null
+++ b/log4j-mongodb4/src/main/java/org/apache/logging/log4j/mongodb4/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+/**
+ * The classes in this package contain the MongoDB provider for the NoSQL
+ * Appender.
+ */
+package org.apache.logging.log4j.mongodb4;
diff --git a/log4j-mongodb4/src/site/markdown/index.md.vm b/log4j-mongodb4/src/site/markdown/index.md.vm
new file mode 100644
index 0000000..1bdd5a5
--- /dev/null
+++ b/log4j-mongodb4/src/site/markdown/index.md.vm
@@ -0,0 +1,48 @@
+<!-- vim: set syn=markdown : -->
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+#set($h1='#')
+#set($h2='##')
+## TODO: use properties for dynamic dependency versions
+
+$h1 MongoDB appender
+
+[MongoDB](http://www.mongodb.org/) is supported through the
+[Java MongoDB Driver](http://docs.mongodb.org/ecosystem/drivers/java/).
+
+```
+ <dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>org.mongodb</groupId>
+ <artifactId>mongo-java-driver</artifactId>
+ <version>2.12.3</version>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>org.mongodb</groupId>
+ <artifactId>mongo-java-driver</artifactId>
+ </dependency>
+ </dependencies>
+```
+
+$h2 Requirements
+
+The MongoDB Appender is dependent on the Log4j 2 API and implementation.
+For more information, see [Runtime Dependencies](../runtime-dependencies.html).
diff --git a/log4j-mongodb4/src/site/site.xml b/log4j-mongodb4/src/site/site.xml
new file mode 100644
index 0000000..54ea9be
--- /dev/null
+++ b/log4j-mongodb4/src/site/site.xml
@@ -0,0 +1,52 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+-->
+<project name="Log4j MongoDB Appender"
+ xmlns="http://maven.apache.org/DECORATION/1.4.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/DECORATION/1.4.0 http://maven.apache.org/xsd/decoration-1.4.0.xsd">
+ <body>
+ <links>
+ <item name="Apache" href="http://www.apache.org/" />
+ <item name="Logging Services" href="http://logging.apache.org/"/>
+ <item name="Log4j" href="../index.html"/>
+ </links>
+
+ <!-- Component-specific reports -->
+ <menu ref="reports"/>
+
+ <!-- Overall Project Info -->
+ <menu name="Log4j Project Information" img="icon-info-sign">
+ <item name="Dependencies" href="../dependencies.html" />
+ <item name="Dependency Convergence" href="../dependency-convergence.html" />
+ <item name="Dependency Management" href="../dependency-management.html" />
+ <item name="Project Team" href="../team-list.html" />
+ <item name="Mailing Lists" href="../mail-lists.html" />
+ <item name="Issue Tracking" href="../issue-tracking.html" />
+ <item name="Project License" href="../license.html" />
+ <item name="Source Repository" href="../source-repository.html" />
+ <item name="Project Summary" href="../project-summary.html" />
+ </menu>
+
+ <menu name="Log4j Project Reports" img="icon-cog">
+ <item name="Changes Report" href="../changes-report.html" />
+ <item name="JIRA Report" href="../jira-report.html" />
+ <item name="Surefire Report" href="../surefire-report.html" />
+ <item name="RAT Report" href="../rat-report.html" />
+ </menu>
+ </body>
+</project>
diff --git a/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4AuthFailureTest.java b/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4AuthFailureTest.java
new file mode 100644
index 0000000..c84d108
--- /dev/null
+++ b/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4AuthFailureTest.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.mongodb4;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.categories.Appenders;
+import org.apache.logging.log4j.junit.LoggerContextRule;
+import org.apache.logging.log4j.mongodb4.MongoDb4TestRule.LoggingTarget;
+import org.apache.logging.log4j.test.AvailablePortSystemPropertyTestRule;
+import org.apache.logging.log4j.test.RuleChainFactory;
+import org.bson.Document;
+import org.junit.Assert;
+import org.junit.ClassRule;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.rules.RuleChain;
+
+import com.mongodb.client.MongoClient;
+import com.mongodb.client.MongoCollection;
+import com.mongodb.client.MongoDatabase;
+
+/**
+ *
+ *
+ * TODO Set up the log4j user in MongoDB.
+ */
+@Ignore("TODO Set up the log4j user in MongoDB")
+@Category(Appenders.MongoDb.class)
+public class MongoDb4AuthFailureTest {
+
+ private static LoggerContextRule loggerContextTestRule = new LoggerContextRule("log4j2-mongodb-auth-failure.xml");
+
+ private static final AvailablePortSystemPropertyTestRule mongoDbPortTestRule = AvailablePortSystemPropertyTestRule
+ .create(MongoDb4TestConstants.SYS_PROP_NAME_PORT);
+
+ private static final MongoDb4TestRule mongoDbTestRule = new MongoDb4TestRule(mongoDbPortTestRule.getName(),
+ MongoDb4AuthFailureTest.class, LoggingTarget.NULL);
+
+ @ClassRule
+ public static RuleChain ruleChain = RuleChainFactory.create(mongoDbPortTestRule, mongoDbTestRule,
+ loggerContextTestRule);
+
+ @Test
+ public void test() {
+ final Logger logger = LogManager.getLogger();
+ logger.info("Hello log");
+ try (final MongoClient mongoClient = mongoDbTestRule.getMongoClient()) {
+ final MongoDatabase database = mongoClient.getDatabase("testDb");
+ Assert.assertNotNull(database);
+ final MongoCollection<Document> collection = database.getCollection("testCollection");
+ Assert.assertNotNull(collection);
+ final Document first = collection.find().first();
+ Assert.assertNull(first);
+ }
+ }
+}
diff --git a/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4CappedTest.java b/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4CappedTest.java
new file mode 100644
index 0000000..fcd7cd7
--- /dev/null
+++ b/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4CappedTest.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.mongodb4;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.categories.Appenders;
+import org.apache.logging.log4j.junit.LoggerContextRule;
+import org.apache.logging.log4j.mongodb4.MongoDb4TestRule.LoggingTarget;
+import org.apache.logging.log4j.test.AvailablePortSystemPropertyTestRule;
+import org.apache.logging.log4j.test.RuleChainFactory;
+import org.bson.Document;
+import org.junit.Assert;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.rules.RuleChain;
+
+import com.mongodb.client.MongoClient;
+import com.mongodb.client.MongoCollection;
+import com.mongodb.client.MongoDatabase;
+
+/**
+ *
+ */
+@Category(Appenders.MongoDb.class)
+public class MongoDb4CappedTest {
+
+ private static LoggerContextRule loggerContextTestRule = new LoggerContextRule("log4j2-mongodb-capped.xml");
+
+ private static final AvailablePortSystemPropertyTestRule mongoDbPortTestRule = AvailablePortSystemPropertyTestRule
+ .create(MongoDb4TestConstants.SYS_PROP_NAME_PORT);
+
+ private static final MongoDb4TestRule mongoDbTestRule = new MongoDb4TestRule(mongoDbPortTestRule.getName(),
+ MongoDb4CappedTest.class, LoggingTarget.NULL);
+
+ @ClassRule
+ public static RuleChain ruleChain = RuleChainFactory.create(mongoDbPortTestRule, mongoDbTestRule,
+ loggerContextTestRule);
+
+ @Test
+ public void test() {
+ final Logger logger = LogManager.getLogger();
+ logger.info("Hello log");
+ try (final MongoClient mongoClient = mongoDbTestRule.getMongoClient()) {
+ final MongoDatabase database = mongoClient.getDatabase("testDb");
+ Assert.assertNotNull(database);
+ final MongoCollection<Document> collection = database.getCollection("testCollection");
+ Assert.assertNotNull(collection);
+ final Document first = collection.find().first();
+ Assert.assertNotNull(first);
+ Assert.assertEquals(first.toJson(), "Hello log", first.getString("message"));
+ }
+ }
+}
diff --git a/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4MapMessageTest.java b/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4MapMessageTest.java
new file mode 100644
index 0000000..70ab9a7
--- /dev/null
+++ b/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4MapMessageTest.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.mongodb4;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.categories.Appenders;
+import org.apache.logging.log4j.junit.LoggerContextRule;
+import org.apache.logging.log4j.message.MapMessage;
+import org.apache.logging.log4j.mongodb4.MongoDb4TestRule.LoggingTarget;
+import org.apache.logging.log4j.test.AvailablePortSystemPropertyTestRule;
+import org.apache.logging.log4j.test.RuleChainFactory;
+import org.bson.Document;
+import org.junit.Assert;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.rules.RuleChain;
+
+import com.mongodb.client.MongoClient;
+import com.mongodb.client.MongoCollection;
+import com.mongodb.client.MongoDatabase;
+
+/**
+ *
+ */
+@Category(Appenders.MongoDb.class)
+public class MongoDb4MapMessageTest {
+
+ private static LoggerContextRule loggerContextTestRule = new LoggerContextRule("log4j2-mongodb-map-message.xml");
+
+ private static final AvailablePortSystemPropertyTestRule mongoDbPortTestRule = AvailablePortSystemPropertyTestRule
+ .create(MongoDb4TestConstants.SYS_PROP_NAME_PORT);
+
+ private static final MongoDb4TestRule mongoDbTestRule = new MongoDb4TestRule(mongoDbPortTestRule.getName(),
+ MongoDb4MapMessageTest.class, LoggingTarget.NULL);
+
+ @ClassRule
+ public static RuleChain ruleChain = RuleChainFactory.create(mongoDbPortTestRule, mongoDbTestRule,
+ loggerContextTestRule);
+
+ @Test
+ public void test() {
+ final Logger logger = LogManager.getLogger();
+ final MapMessage<?, Object> mapMessage = new MapMessage<>();
+ mapMessage.with("SomeName", "SomeValue");
+ mapMessage.with("SomeInt", 1);
+ logger.info(mapMessage);
+ //
+ try (final MongoClient mongoClient = mongoDbTestRule.getMongoClient()) {
+ final MongoDatabase database = mongoClient.getDatabase("testDb");
+ Assert.assertNotNull(database);
+ final MongoCollection<Document> collection = database.getCollection("testCollection");
+ Assert.assertNotNull(collection);
+ final Document first = collection.find().first();
+ Assert.assertNotNull(first);
+ final String firstJson = first.toJson();
+ Assert.assertEquals(firstJson, "SomeValue", first.getString("SomeName"));
+ Assert.assertEquals(firstJson, Integer.valueOf(1), first.getInteger("SomeInt"));
+ }
+ }
+}
diff --git a/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4Test.java b/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4Test.java
new file mode 100644
index 0000000..e814f1f
--- /dev/null
+++ b/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4Test.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.mongodb4;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.categories.Appenders;
+import org.apache.logging.log4j.junit.LoggerContextRule;
+import org.apache.logging.log4j.mongodb4.MongoDb4TestRule.LoggingTarget;
+import org.apache.logging.log4j.test.AvailablePortSystemPropertyTestRule;
+import org.apache.logging.log4j.test.RuleChainFactory;
+import org.bson.Document;
+import org.junit.Assert;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.experimental.categories.Category;
+import org.junit.rules.RuleChain;
+
+import com.mongodb.client.MongoClient;
+import com.mongodb.client.MongoCollection;
+import com.mongodb.client.MongoDatabase;
+
+/**
+ *
+ */
+@Category(Appenders.MongoDb.class)
+public class MongoDb4Test {
+
+ private static LoggerContextRule loggerContextTestRule = new LoggerContextRule("log4j2-mongodb.xml");
+
+ private static final AvailablePortSystemPropertyTestRule mongoDbPortTestRule = AvailablePortSystemPropertyTestRule
+ .create(MongoDb4TestConstants.SYS_PROP_NAME_PORT);
+
+ private static final MongoDb4TestRule mongoDbTestRule = new MongoDb4TestRule(mongoDbPortTestRule.getName(),
+ MongoDb4Test.class, LoggingTarget.NULL);
+
+ @ClassRule
+ public static RuleChain ruleChain = RuleChainFactory.create(mongoDbPortTestRule, mongoDbTestRule,
+ loggerContextTestRule);
+
+ @Test
+ public void test() {
+ final Logger logger = LogManager.getLogger();
+ logger.info("Hello log");
+ try (final MongoClient mongoClient = mongoDbTestRule.getMongoClient()) {
+ final MongoDatabase database = mongoClient.getDatabase("testDb");
+ Assert.assertNotNull(database);
+ final MongoCollection<Document> collection = database.getCollection("testCollection");
+ Assert.assertNotNull(collection);
+ final Document first = collection.find().first();
+ Assert.assertNotNull(first);
+ Assert.assertEquals(first.toJson(), "Hello log", first.getString("message"));
+ Assert.assertEquals(first.toJson(), "INFO", first.getString("level"));
+ }
+ }
+}
diff --git a/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4TestConstants.java b/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4TestConstants.java
new file mode 100644
index 0000000..0d398f0
--- /dev/null
+++ b/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4TestConstants.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.mongodb4;
+
+public class MongoDb4TestConstants {
+
+ public static final String SYS_PROP_NAME_PORT = "MongoDBTestPort";
+
+}
diff --git a/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4TestRule.java b/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4TestRule.java
new file mode 100644
index 0000000..035ef53
--- /dev/null
+++ b/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4TestRule.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.mongodb4;
+
+import java.util.Objects;
+
+import org.apache.commons.lang3.NotImplementedException;
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.mongodb.client.MongoClient;
+import com.mongodb.client.MongoClients;
+
+import de.flapdoodle.embed.mongo.Command;
+import de.flapdoodle.embed.mongo.MongodExecutable;
+import de.flapdoodle.embed.mongo.MongodProcess;
+import de.flapdoodle.embed.mongo.MongodStarter;
+import de.flapdoodle.embed.mongo.config.MongodConfigBuilder;
+import de.flapdoodle.embed.mongo.config.Net;
+import de.flapdoodle.embed.mongo.config.RuntimeConfigBuilder;
+import de.flapdoodle.embed.mongo.config.Timeout;
+import de.flapdoodle.embed.mongo.distribution.Version;
+import de.flapdoodle.embed.process.config.IRuntimeConfig;
+import de.flapdoodle.embed.process.config.io.ProcessOutput;
+import de.flapdoodle.embed.process.runtime.Network;
+
+/**
+ * A JUnit test rule to manage a MongoDB embedded instance.
+ *
+ * TODO Move this class to Apache Commons Testing.
+ */
+public class MongoDb4TestRule implements TestRule {
+
+ public enum LoggingTarget {
+ CONSOLE, NULL;
+
+ public static LoggingTarget getLoggingTarget(final String sysPropertyName, final LoggingTarget defaultValue) {
+ return LoggingTarget.valueOf(System.getProperty(sysPropertyName, defaultValue.name()));
+ }
+ }
+
+ private static final int BUILDER_TIMEOUT_MILLIS = 30000;
+
+ public static int getBuilderTimeoutMillis() {
+ return BUILDER_TIMEOUT_MILLIS;
+ }
+
+ private static MongodStarter getMongodStarter(final LoggingTarget loggingTarget) {
+ if (loggingTarget == null) {
+ return MongodStarter.getDefaultInstance();
+ }
+ switch (loggingTarget) {
+ case NULL:
+ final Logger logger = LoggerFactory.getLogger(MongoDb4TestRule.class.getName());
+ final IRuntimeConfig runtimeConfig = new RuntimeConfigBuilder()
+ // @formatter:off
+ .defaultsWithLogger(Command.MongoD, logger).processOutput(ProcessOutput.getDefaultInstanceSilent())
+ .build();
+ // @formatter:on
+
+ return MongodStarter.getInstance(runtimeConfig);
+ case CONSOLE:
+ return MongodStarter.getDefaultInstance();
+ default:
+ throw new NotImplementedException(loggingTarget.toString());
+ }
+ }
+
+ protected final LoggingTarget loggingTarget;
+
+ protected MongoClient mongoClient;
+ protected MongodExecutable mongodExecutable;
+ protected MongodProcess mongodProcess;
+ protected final String portSystemPropertyName;
+
+ /**
+ * Store {@link MongodStarter} (or RuntimeConfig) in a static final field if you
+ * want to use artifact store caching (or else disable caching).
+ * <p>
+ * The test framework {@code de.flapdoodle.embed.mongo} requires Java 8.
+ * </p>
+ */
+ protected final MongodStarter starter;
+
+ /**
+ * Constructs a new test rule.
+ *
+ * @param portSystemPropertyName The system property name for the MongoDB port.
+ * @param clazz The test case class.
+ * @param defaultLoggingTarget The logging target.
+ */
+ public MongoDb4TestRule(final String portSystemPropertyName, final Class<?> clazz,
+ final LoggingTarget defaultLoggingTarget) {
+ this.portSystemPropertyName = Objects.requireNonNull(portSystemPropertyName, "portSystemPropertyName");
+ this.loggingTarget = LoggingTarget.getLoggingTarget(clazz.getName() + "." + LoggingTarget.class.getSimpleName(),
+ defaultLoggingTarget);
+ this.starter = getMongodStarter(this.loggingTarget);
+ }
+
+ @Override
+ public Statement apply(final Statement base, final Description description) {
+ return new Statement() {
+
+ @Override
+ public void evaluate() throws Throwable {
+ final String value = Objects.requireNonNull(System.getProperty(portSystemPropertyName),
+ "System property '" + portSystemPropertyName + "' is null");
+ final int port = Integer.parseInt(value);
+ mongodExecutable = starter.prepare(
+ // @formatter:off
+ new MongodConfigBuilder().version(Version.Main.PRODUCTION)
+ .timeout(new Timeout(BUILDER_TIMEOUT_MILLIS))
+ .net(new Net("localhost", port, Network.localhostIsIPv6())).build());
+ // @formatter:on
+ mongodProcess = mongodExecutable.start();
+ mongoClient = MongoClients.create("mongodb://localhost:" + port);
+ try {
+ base.evaluate();
+ } finally {
+ if (mongodProcess != null) {
+ mongodProcess.stop();
+ mongodProcess = null;
+ }
+ if (mongodExecutable != null) {
+ mongodExecutable.stop();
+ mongodExecutable = null;
+ }
+ }
+ }
+ };
+ }
+
+ public MongoClient getMongoClient() {
+ return mongoClient;
+ }
+
+ public MongodExecutable getMongodExecutable() {
+ return mongodExecutable;
+ }
+
+ public MongodProcess getMongodProcess() {
+ return mongodProcess;
+ }
+
+ public MongodStarter getStarter() {
+ return starter;
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder builder = new StringBuilder();
+ builder.append("Mongo4TestRule [starter=");
+ builder.append(starter);
+ builder.append(", portSystemPropertyName=");
+ builder.append(portSystemPropertyName);
+ builder.append(", mongoClient=");
+ builder.append(mongoClient);
+ builder.append(", mongodExecutable=");
+ builder.append(mongodExecutable);
+ builder.append(", mongodProcess=");
+ builder.append(mongodProcess);
+ builder.append(", loggingTarget=");
+ builder.append(loggingTarget);
+ builder.append("]");
+ return builder.toString();
+ }
+
+}
\ No newline at end of file
diff --git a/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4TestTestRuleTest.java b/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4TestTestRuleTest.java
new file mode 100644
index 0000000..086af07
--- /dev/null
+++ b/log4j-mongodb4/src/test/java/org/apache/logging/log4j/mongodb4/MongoDb4TestTestRuleTest.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.mongodb4;
+
+import org.apache.commons.lang3.JavaVersion;
+import org.apache.commons.lang3.SystemUtils;
+import org.apache.logging.log4j.mongodb4.MongoDb4TestRule.LoggingTarget;
+import org.apache.logging.log4j.test.AvailablePortSystemPropertyTestRule;
+import org.apache.logging.log4j.test.RuleChainFactory;
+import org.junit.Assert;
+import org.junit.Assume;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.rules.RuleChain;
+
+import com.mongodb.client.MongoIterable;
+
+/**
+ * Tests MongoDbRule.
+ * <p>
+ * The test framework {@code de.flapdoodle.embed.mongo} requires Java 8.
+ * </p>
+ */
+public class MongoDb4TestTestRuleTest {
+
+ private static final AvailablePortSystemPropertyTestRule mongoDbPortTestRule = AvailablePortSystemPropertyTestRule
+ .create(MongoDb4TestConstants.SYS_PROP_NAME_PORT);
+
+ private static final MongoDb4TestRule mongoDbTestRule = new MongoDb4TestRule(mongoDbPortTestRule.getName(),
+ MongoDb4TestTestRuleTest.class, LoggingTarget.NULL);
+
+ @ClassRule
+ public static RuleChain mongoDbChain = RuleChainFactory.create(mongoDbPortTestRule, mongoDbTestRule);
+
+ @BeforeClass
+ public static void beforeClass() {
+ Assume.assumeTrue(SystemUtils.isJavaVersionAtLeast(JavaVersion.JAVA_1_8));
+ }
+
+ @Test
+ public void testAccess() {
+ @SuppressWarnings("resource")
+ final MongoIterable<String> databaseNames = mongoDbTestRule.getMongoClient().listDatabaseNames();
+ Assert.assertNotNull(databaseNames);
+ Assert.assertNotNull(databaseNames.first());
+ }
+
+ @SuppressWarnings("resource")
+ @Test
+ public void testMongoDbTestRule() {
+ Assert.assertNotNull(mongoDbTestRule);
+ Assert.assertNotNull(mongoDbTestRule.getStarter());
+ Assert.assertNotNull(mongoDbTestRule.getMongoClient());
+ Assert.assertNotNull(mongoDbTestRule.getMongodExecutable());
+ Assert.assertNotNull(mongoDbTestRule.getMongodProcess());
+ }
+}
diff --git a/log4j-mongodb4/src/test/resources/log4j2-mongodb-auth-failure.xml b/log4j-mongodb4/src/test/resources/log4j2-mongodb-auth-failure.xml
new file mode 100644
index 0000000..34be399
--- /dev/null
+++ b/log4j-mongodb4/src/test/resources/log4j2-mongodb-auth-failure.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-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 "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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>
+ <NoSql name="MongoDbAppender">
+ <MongoDb4
+ connection="mongodb://log4jUser:12345678@localhost:${sys:MongoDBTestPort:-27017}/testDb.testCollection" />
+ </NoSql>
+ </Appenders>
+ <Loggers>
+ <Root level="ALL">
+ <AppenderRef ref="MongoDbAppender" />
+ </Root>
+ </Loggers>
+</Configuration>
diff --git a/log4j-mongodb4/src/test/resources/log4j2-mongodb-capped.xml b/log4j-mongodb4/src/test/resources/log4j2-mongodb-capped.xml
new file mode 100644
index 0000000..d5f5651
--- /dev/null
+++ b/log4j-mongodb4/src/test/resources/log4j2-mongodb-capped.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-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 "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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>
+ <NoSql name="MongoDbAppender">
+ <MongoDb4
+ connection="mongodb://localhost:${sys:MongoDBTestPort:-27017}/testDb.testCollection"
+ capped="true"
+ collectionSize="1073741824"/>
+ </NoSql>
+ </Appenders>
+ <Loggers>
+ <Root level="ALL">
+ <AppenderRef ref="MongoDbAppender" />
+ </Root>
+ </Loggers>
+</Configuration>
diff --git a/log4j-mongodb4/src/test/resources/log4j2-mongodb-map-message.xml b/log4j-mongodb4/src/test/resources/log4j2-mongodb-map-message.xml
new file mode 100644
index 0000000..7534477
--- /dev/null
+++ b/log4j-mongodb4/src/test/resources/log4j2-mongodb-map-message.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-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 "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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>
+ <NoSql name="MongoDbAppender">
+ <MongoDb4 connection="mongodb://localhost:${sys:MongoDBTestPort:-27017}/testDb.testCollection" />
+ <MessageLayout />
+ </NoSql>
+ </Appenders>
+ <Loggers>
+ <Root level="ALL">
+ <AppenderRef ref="MongoDbAppender" />
+ </Root>
+ </Loggers>
+</Configuration>
diff --git a/log4j-mongodb4/src/test/resources/log4j2-mongodb.xml b/log4j-mongodb4/src/test/resources/log4j2-mongodb.xml
new file mode 100644
index 0000000..514bc8c
--- /dev/null
+++ b/log4j-mongodb4/src/test/resources/log4j2-mongodb.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-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 "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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>
+ <NoSql name="MongoDbAppender">
+ <MongoDb4 connection="mongodb://localhost:${sys:MongoDBTestPort:-27017}/testDb.testCollection" />
+ </NoSql>
+ </Appenders>
+ <Loggers>
+ <Root level="ALL">
+ <AppenderRef ref="MongoDbAppender" />
+ </Root>
+ </Loggers>
+</Configuration>
diff --git a/log4j-osgi/pom.xml b/log4j-osgi/pom.xml
index 7dbcc7b..f05d0d4 100644
--- a/log4j-osgi/pom.xml
+++ b/log4j-osgi/pom.xml
@@ -111,11 +111,6 @@
<reporting>
<plugins>
<plugin>
- <groupId>org.codehaus.mojo</groupId>
- <artifactId>clirr-maven-plugin</artifactId>
- <version>${clirr.plugin.version}</version>
- </plugin>
- <plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-changes-plugin</artifactId>
<version>${changes.plugin.version}</version>
@@ -156,6 +151,7 @@
project -->
<detectOfflineLinks>false</detectOfflineLinks>
<linksource>true</linksource>
+ <source>8</source>
<links>
<link>http://www.osgi.org/javadoc/r4v43/core/</link>
</links>
diff --git a/log4j-osgi/src/test/java/org/apache/logging/log4j/osgi/tests/AbstractLoadBundleTest.java b/log4j-osgi/src/test/java/org/apache/logging/log4j/osgi/tests/AbstractLoadBundleTest.java
index 529f6e0..505732f 100644
--- a/log4j-osgi/src/test/java/org/apache/logging/log4j/osgi/tests/AbstractLoadBundleTest.java
+++ b/log4j-osgi/src/test/java/org/apache/logging/log4j/osgi/tests/AbstractLoadBundleTest.java
@@ -37,6 +37,11 @@
return getBundleContext().installBundle(apiPath.toUri().toString());
}
+ private Bundle getPluginsBundle() throws BundleException {
+ final Path apiPath = getHere().resolveSibling("log4j-plugins").resolve("target").resolve(getBundleTestInfo().buildJarFileName("log4j-plugins"));
+ return getBundleContext().installBundle(apiPath.toUri().toString());
+ }
+
private Bundle getCoreBundle() throws BundleException {
final Path corePath = getHere().resolveSibling("log4j-core").resolve("target").resolve(getBundleTestInfo().buildJarFileName("log4j-core"));
@@ -93,21 +98,24 @@
return oldStream;
}
- private void start(final Bundle api, final Bundle core, final Bundle dummy) throws BundleException {
+ private void start(final Bundle api, final Bundle plugins, final Bundle core, final Bundle dummy) throws BundleException {
api.start();
+ plugins.start();
core.start();
dummy.start();
}
- private void stop(final Bundle api, final Bundle core, final Bundle dummy) throws BundleException {
+ private void stop(final Bundle api, final Bundle plugins, final Bundle core, final Bundle dummy) throws BundleException {
dummy.stop();
core.stop();
+ plugins.stop();
api.stop();
}
- private void uninstall(final Bundle api, final Bundle core, final Bundle dummy) throws BundleException {
+ private void uninstall(final Bundle api, final Bundle plugins, final Bundle core, final Bundle dummy) throws BundleException {
dummy.uninstall();
core.uninstall();
+ plugins.uninstall();
api.uninstall();
}
@@ -118,39 +126,51 @@
public void testApiCoreStartStopStartStop() throws BundleException {
final Bundle api = getApiBundle();
+ final Bundle plugins = getPluginsBundle();
final Bundle core = getCoreBundle();
Assert.assertEquals("api is not in INSTALLED state", Bundle.INSTALLED, api.getState());
+ Assert.assertEquals("plugins is not in INSTALLED state", Bundle.INSTALLED, plugins.getState());
Assert.assertEquals("core is not in INSTALLED state", Bundle.INSTALLED, core.getState());
api.start();
+ plugins.start();
core.start();
- Assert.assertEquals("api is not in ACTIVE state", Bundle.ACTIVE, api.getState());
+ Assert.assertEquals("api is not in ACTIVE state", Bundle.ACTIVE, api.getState());
+ Assert.assertEquals("plugins is not in ACTIVE state", Bundle.ACTIVE, plugins.getState());
Assert.assertEquals("core is not in ACTIVE state", Bundle.ACTIVE, core.getState());
core.stop();
+ plugins.stop();
api.stop();
Assert.assertEquals("api is not in RESOLVED state", Bundle.RESOLVED, api.getState());
+ Assert.assertEquals("plugins is not in RESOLVED state", Bundle.RESOLVED, plugins.getState());
Assert.assertEquals("core is not in RESOLVED state", Bundle.RESOLVED, core.getState());
-
+
api.start();
+ plugins.start();
core.start();
-
- Assert.assertEquals("api is not in ACTIVE state", Bundle.ACTIVE, api.getState());
- Assert.assertEquals("core is not in ACTIVE state", Bundle.ACTIVE, core.getState());
-
+
+ Assert.assertEquals("api is not in ACTIVE state", Bundle.ACTIVE, api.getState());
+ Assert.assertEquals("plugins is not in ACTIVE state", Bundle.ACTIVE, plugins.getState());
+ Assert.assertEquals("core is not in ACTIVE state", Bundle.ACTIVE, core.getState());
+
core.stop();
+ plugins.stop();
api.stop();
-
+
Assert.assertEquals("api is not in RESOLVED state", Bundle.RESOLVED, api.getState());
+ Assert.assertEquals("plugins is not in RESOLVED state", Bundle.RESOLVED, plugins.getState());
Assert.assertEquals("core is not in RESOLVED state", Bundle.RESOLVED, core.getState());
core.uninstall();
+ plugins.uninstall();
api.uninstall();
Assert.assertEquals("api is not in UNINSTALLED state", Bundle.UNINSTALLED, api.getState());
+ Assert.assertEquals("plugins is not in UNINSTALLED state", Bundle.UNINSTALLED, plugins.getState());
Assert.assertEquals("core is not in UNINSTALLED state", Bundle.UNINSTALLED, core.getState());
}
@@ -161,9 +181,11 @@
public void testClassNotFoundErrorLogger() throws BundleException {
final Bundle api = getApiBundle();
+ final Bundle plugins = getPluginsBundle();
final Bundle core = getCoreBundle();
api.start();
+ plugins.start();
// fails if LOG4J2-1637 is not fixed
try {
core.start();
@@ -187,9 +209,11 @@
}
core.stop();
+ plugins.stop();
api.stop();
core.uninstall();
+ plugins.uninstall();
api.uninstall();
}
@@ -197,13 +221,14 @@
* Tests LOG4J2-920.
*/
@Test
- public void testMissingImportOfCoreOsgiPackage() throws BundleException, ReflectiveOperationException {
+ public void testLoadingOfConfigurableCoreClasses() throws BundleException, ReflectiveOperationException {
final Bundle api = getApiBundle();
+ final Bundle plugins = getPluginsBundle();
final Bundle core = getCoreBundle();
final Bundle dummy = getDummyBundle();
- start(api, core, dummy);
+ start(api, plugins, core, dummy);
final ByteArrayOutputStream baos = new ByteArrayOutputStream();
final PrintStream logStream = new PrintStream(baos);
@@ -214,13 +239,12 @@
setupStream(api, bakStream);
- final boolean result = baos.toString().contains(
- "ERROR StatusLogger Unable to create context org.apache.logging.log4j.core.osgi.BundleContextSelector");
- Assert.assertFalse(
- "org.apache.logging.log4j.core.osgi;resolution:=optional is missing in Import-Package in the POM", result);
+ // org.apache.logging.log4j.core.osgi.BundleContextSelector cannot be found by org.apache.logging.log4j.api
+ final boolean result = baos.toString().contains("BundleContextSelector cannot be found");
+ Assert.assertFalse("Core class BundleContextSelector cannot be loaded in OSGI setup", result);
- stop(api, core, dummy);
- uninstall(api, core, dummy);
+ stop(api, plugins, core, dummy);
+ uninstall(api, plugins, core, dummy);
}
/**
@@ -230,10 +254,11 @@
public void testSimpleLogInAnOsgiContext() throws BundleException, ReflectiveOperationException {
final Bundle api = getApiBundle();
+ final Bundle plugins = getPluginsBundle();
final Bundle core = getCoreBundle();
final Bundle dummy = getDummyBundle();
- start(api, core, dummy);
+ start(api, plugins, core, dummy);
final PrintStream bakStream = System.out;
try {
@@ -245,14 +270,15 @@
final String result = baos.toString().substring(
12).trim(); // remove the instant then the spaces at start and end, that are non constant
- Assert.assertEquals("[main] ERROR org.apache.logging.log4j.configuration.CustomConfiguration - Test OK",
- result);
+ String expected = "[main] ERROR org.apache.logging.log4j.configuration.CustomConfiguration - Test OK";
+ Assert.assertTrue("Incorrect string. Expected string ends with: " + expected + " Actual: " + result,
+ result.endsWith(expected));
} finally {
System.setOut(bakStream);
}
- stop(api, core, dummy);
- uninstall(api, core, dummy);
+ stop(api, plugins, core, dummy);
+ uninstall(api, plugins, core, dummy);
}
@@ -264,10 +290,12 @@
public void testLog4J12Fragement() throws BundleException, ReflectiveOperationException {
final Bundle api = getApiBundle();
+ final Bundle plugins = getPluginsBundle();
final Bundle core = getCoreBundle();
final Bundle compat = get12ApiBundle();
api.start();
+ plugins.start();
core.start();
final Class<?> coreClassFromCore = core.loadClass("org.apache.logging.log4j.core.Core");
@@ -280,7 +308,7 @@
core.stop();
api.stop();
- uninstall(api, core, compat);
+ uninstall(api, plugins, core, compat);
}
}
diff --git a/log4j-perf/pom.xml b/log4j-perf/pom.xml
index 4316d40..b21f058 100644
--- a/log4j-perf/pom.xml
+++ b/log4j-perf/pom.xml
@@ -38,9 +38,10 @@
<log4jParentDir>${basedir}/..</log4jParentDir>
<docLabel>Apache Log4J Performance Tests</docLabel>
<projectDir>/log4j-perf</projectDir>
- <jmh.version>1.19</jmh.version>
+ <jmh.version>1.21</jmh.version>
<javac.target>1.7</javac.target>
<uberjar.name>benchmarks</uberjar.name>
+ <revapi.skip>true</revapi.skip>
</properties>
<dependencies>
@@ -61,6 +62,10 @@
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-plugins</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
</dependency>
<dependency>
@@ -74,6 +79,22 @@
<version>${project.version}</version>
</dependency>
<dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-layout-jackson-json</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-layout-json-template</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-layout-json-template</artifactId>
+ <version>${project.version}</version>
+ <type>test-jar</type>
+ </dependency>
+ <dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
@@ -147,6 +168,10 @@
<artifactId>jackson-databind</artifactId>
<optional>true</optional>
</dependency>
+ <dependency>
+ <groupId>co.elastic.logging</groupId>
+ <artifactId>log4j2-ecs-layout</artifactId>
+ </dependency>
</dependencies>
<build>
@@ -165,7 +190,7 @@
<configuration>
<toolchains>
<jdk>
- <version>9</version>
+ <version>[11, )</version>
</jdk>
</toolchains>
</configuration>
@@ -196,6 +221,7 @@
<minimizeJar>false</minimizeJar>
<finalName>${uberjar.name}</finalName>
<transformers>
+ <transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>org.openjdk.jmh.Main</mainClass>
<manifestEntries>
diff --git a/log4j-perf/src/main/java/org/apache/logging/log4j/layout/json/template/JsonTemplateLayoutBenchmark.java b/log4j-perf/src/main/java/org/apache/logging/log4j/layout/json/template/JsonTemplateLayoutBenchmark.java
new file mode 100644
index 0000000..5af536b
--- /dev/null
+++ b/log4j-perf/src/main/java/org/apache/logging/log4j/layout/json/template/JsonTemplateLayoutBenchmark.java
@@ -0,0 +1,185 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.logging.log4j.layout.json.template;
+
+import org.apache.logging.log4j.core.Layout;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.layout.ByteBufferDestination;
+import org.openjdk.jmh.annotations.Benchmark;
+
+import java.nio.ByteBuffer;
+import java.util.List;
+
+/**
+ * Benchmark suite for various JSON layouts.
+ * <p>
+ * You can run this test as follows:
+ * <pre>
+ * java \
+ * -jar log4j-perf/target/benchmarks.jar \
+ * -f 2 \
+ * -wi 3 -w 20s \
+ * -i 5 -r 30s \
+ * -prof gc \
+ * -rf json -rff log4j-perf/target/JsonTemplateLayoutBenchmarkResult.json \
+ * ".*JsonTemplateLayoutBenchmark.*"
+ * </pre>
+ */
+public class JsonTemplateLayoutBenchmark {
+
+ @Benchmark
+ public static int fullJsonTemplateLayout4JsonLayout(
+ final JsonTemplateLayoutBenchmarkState state) {
+ return benchmark(
+ state.getJsonTemplateLayout4JsonLayout(),
+ state.getFullLogEvents(),
+ state.getByteBufferDestination());
+ }
+
+ @Benchmark
+ public static int liteJsonTemplateLayout4JsonLayout(
+ final JsonTemplateLayoutBenchmarkState state) {
+ return benchmark(
+ state.getJsonTemplateLayout4JsonLayout(),
+ state.getLiteLogEvents(),
+ state.getByteBufferDestination());
+ }
+
+ @Benchmark
+ public static int fullJsonTemplateLayout4EcsLayout(
+ final JsonTemplateLayoutBenchmarkState state) {
+ return benchmark(
+ state.getJsonTemplateLayout4EcsLayout(),
+ state.getFullLogEvents(),
+ state.getByteBufferDestination());
+ }
+
+ @Benchmark
+ public static int liteJsonTemplateLayout4EcsLayout(
+ final JsonTemplateLayoutBenchmarkState state) {
+ return benchmark(
+ state.getJsonTemplateLayout4EcsLayout(),
+ state.getLiteLogEvents(),
+ state.getByteBufferDestination());
+ }
+
+ @Benchmark
+ public static int fullJsonTemplateLayout4GelfLayout(
+ final JsonTemplateLayoutBenchmarkState state) {
+ return benchmark(
+ state.getJsonTemplateLayout4GelfLayout(),
+ state.getFullLogEvents(),
+ state.getByteBufferDestination());
+ }
+
+ @Benchmark
+ public static int liteJsonTemplateLayout4GelfLayout(
+ final JsonTemplateLayoutBenchmarkState state) {
+ return benchmark(
+ state.getJsonTemplateLayout4GelfLayout(),
+ state.getLiteLogEvents(),
+ state.getByteBufferDestination());
+ }
+
+ @Benchmark
+ public static int fullDefaultJsonLayout(
+ final JsonTemplateLayoutBenchmarkState state) {
+ return benchmark(
+ state.getDefaultJsonLayout(),
+ state.getFullLogEvents(),
+ state.getByteBufferDestination());
+ }
+
+ @Benchmark
+ public static int liteDefaultJsonLayout(
+ final JsonTemplateLayoutBenchmarkState state) {
+ return benchmark(
+ state.getDefaultJsonLayout(),
+ state.getLiteLogEvents(),
+ state.getByteBufferDestination());
+ }
+
+ @Benchmark
+ public static int fullCustomJsonLayout(
+ final JsonTemplateLayoutBenchmarkState state) {
+ return benchmark(
+ state.getCustomJsonLayout(),
+ state.getFullLogEvents(),
+ state.getByteBufferDestination());
+ }
+
+ @Benchmark
+ public static int liteCustomJsonLayout(
+ final JsonTemplateLayoutBenchmarkState state) {
+ return benchmark(
+ state.getCustomJsonLayout(),
+ state.getLiteLogEvents(),
+ state.getByteBufferDestination());
+ }
+
+ @Benchmark
+ public static int fullEcsLayout(
+ final JsonTemplateLayoutBenchmarkState state) {
+ return benchmark(
+ state.getEcsLayout(),
+ state.getFullLogEvents(),
+ state.getByteBufferDestination());
+ }
+
+ @Benchmark
+ public static int liteEcsLayout(
+ final JsonTemplateLayoutBenchmarkState state) {
+ return benchmark(
+ state.getEcsLayout(),
+ state.getLiteLogEvents(),
+ state.getByteBufferDestination());
+ }
+
+ @Benchmark
+ public static int fullGelfLayout(
+ final JsonTemplateLayoutBenchmarkState state) {
+ return benchmark(
+ state.getGelfLayout(),
+ state.getFullLogEvents(),
+ state.getByteBufferDestination());
+ }
+
+ @Benchmark
+ public static int liteGelfLayout(
+ final JsonTemplateLayoutBenchmarkState state) {
+ return benchmark(
+ state.getGelfLayout(),
+ state.getLiteLogEvents(),
+ state.getByteBufferDestination());
+ }
+
+ private static int benchmark(
+ final Layout<String> layout,
+ final List<LogEvent> logEvents,
+ final ByteBufferDestination destination) {
+ // noinspection ForLoopReplaceableByForEach (avoid iterator instantiation)
+ for (int logEventIndex = 0; logEventIndex < logEvents.size(); logEventIndex++) {
+ LogEvent logEvent = logEvents.get(logEventIndex);
+ layout.encode(logEvent, destination);
+ }
+ final ByteBuffer byteBuffer = destination.getByteBuffer();
+ final int position = byteBuffer.position();
+ byteBuffer.clear();
+ return position;
+ }
+
+}
diff --git a/log4j-perf/src/main/java/org/apache/logging/log4j/layout/json/template/JsonTemplateLayoutBenchmarkReport.java b/log4j-perf/src/main/java/org/apache/logging/log4j/layout/json/template/JsonTemplateLayoutBenchmarkReport.java
new file mode 100644
index 0000000..2c43f0a
--- /dev/null
+++ b/log4j-perf/src/main/java/org/apache/logging/log4j/layout/json/template/JsonTemplateLayoutBenchmarkReport.java
@@ -0,0 +1,359 @@
+package org.apache.logging.log4j.layout.json.template;
+
+import org.apache.logging.log4j.layout.json.template.util.JsonReader;
+import org.apache.logging.log4j.util.Strings;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.math.RoundingMode;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.attribute.FileTime;
+import java.time.Instant;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+/**
+ * Utility class to summarize {@link JsonTemplateLayoutBenchmark} results in Asciidoctor.
+ * <p>
+ * Usage:
+ * <pre>
+ * java \
+ * -cp log4j-perf/target/benchmarks.jar \
+ * org.apache.logging.log4j.layout.json.template.JsonTemplateLayoutBenchmarkReport \
+ * log4j-perf/target/JsonTemplateLayoutBenchmarkResult.json \
+ * log4j-perf/target/JsonTemplateLayoutBenchmarkReport.adoc
+ * </pre>
+ * @see JsonTemplateLayoutBenchmark on how to generate JMH result JSON file
+ */
+public enum JsonTemplateLayoutBenchmarkReport {;
+
+ private static final Charset CHARSET = StandardCharsets.UTF_8;
+
+ public static void main(final String[] args) throws Exception {
+ final CliArgs cliArgs = CliArgs.parseArgs(args);
+ final JmhSetup jmhSetup = JmhSetup.ofJmhResult(cliArgs.jmhResultJsonFile);
+ final List<JmhSummary> jmhSummaries = JmhSummary.ofJmhResult(cliArgs.jmhResultJsonFile);
+ dumpReport(cliArgs.outputAdocFile, jmhSetup, jmhSummaries);
+ }
+
+ private static final class CliArgs {
+
+ private final File jmhResultJsonFile;
+
+ private final File outputAdocFile;
+
+ private CliArgs(final File jmhResultJsonFile, final File outputAdocFile) {
+ this.jmhResultJsonFile = jmhResultJsonFile;
+ this.outputAdocFile = outputAdocFile;
+ }
+
+ private static CliArgs parseArgs(final String[] args) {
+
+ // Check number of arguments.
+ if (args.length != 2) {
+ throw new IllegalArgumentException(
+ "usage: <jmhResultJsonFile> <outputAdocFile>");
+ }
+
+ // Parse the JMH result JSON file.
+ final File jmhResultJsonFile = new File(args[0]);
+ if (!jmhResultJsonFile.isFile()) {
+ throw new IllegalArgumentException(
+ "jmhResultJsonFile doesn't point to a regular file: " +
+ jmhResultJsonFile);
+ }
+ if (!jmhResultJsonFile.canRead()) {
+ throw new IllegalArgumentException(
+ "jmhResultJsonFile is not readable: " +
+ jmhResultJsonFile);
+ }
+
+ // Parse the output AsciiDoc file.
+ final File outputAdocFile = new File(args[1]);
+ touch(outputAdocFile);
+
+ // Looks okay.
+ return new CliArgs(jmhResultJsonFile, outputAdocFile);
+
+ }
+
+ public static void touch(final File file) {
+ Objects.requireNonNull(file, "file");
+ final Path path = file.toPath();
+ try {
+ if (Files.exists(path)) {
+ Files.setLastModifiedTime(path, FileTime.from(Instant.now()));
+ } else {
+ Files.createFile(path);
+ }
+ } catch (IOException error) {
+ throw new RuntimeException("failed to touch file: " + file, error);
+ }
+ }
+
+ }
+
+ private static final class JmhSetup {
+
+ private final String vmName;
+
+ private final String vmVersion;
+
+ private final List<String> vmArgs;
+
+ private final int forkCount;
+
+ private final int warmupIterationCount;
+
+ private final String warmupTime;
+
+ private final int measurementIterationCount;
+
+ private final String measurementTime;
+
+ private JmhSetup(
+ final String vmName,
+ final String vmVersion,
+ final List<String> vmArgs,
+ final int forkCount,
+ final int warmupIterationCount,
+ final String warmupTime,
+ final int measurementIterationCount,
+ final String measurementTime) {
+ this.vmName = vmName;
+ this.vmVersion = vmVersion;
+ this.vmArgs = vmArgs;
+ this.forkCount = forkCount;
+ this.warmupIterationCount = warmupIterationCount;
+ this.warmupTime = warmupTime;
+ this.measurementIterationCount = measurementIterationCount;
+ this.measurementTime = measurementTime;
+ }
+
+ private static JmhSetup ofJmhResult(final File jmhResultFile) throws IOException {
+ final List<Object> jmhResult = readObject(jmhResultFile);
+ return ofJmhResult(jmhResult);
+ }
+
+ private static JmhSetup ofJmhResult(final List<Object> jmhResult) {
+ final Object jmhResultEntry = jmhResult.stream().findFirst().get();
+ final String vmName = readObjectAtPath(jmhResultEntry, "vmName");
+ final String vmVersion = readObjectAtPath(jmhResultEntry, "vmVersion");
+ final List<String> vmArgs = readObjectAtPath(jmhResultEntry, "jvmArgs");
+ final int forkCount = readObjectAtPath(jmhResultEntry, "forks");
+ final int warmupIterationCount = readObjectAtPath(jmhResultEntry, "warmupIterations");
+ final String warmupTime = readObjectAtPath(jmhResultEntry, "warmupTime");
+ final int measurementIterationCount = readObjectAtPath(jmhResultEntry, "measurementIterations");
+ final String measurementTime = readObjectAtPath(jmhResultEntry, "measurementTime");
+ return new JmhSetup(
+ vmName,
+ vmVersion,
+ vmArgs,
+ forkCount,
+ warmupIterationCount,
+ warmupTime,
+ measurementIterationCount,
+ measurementTime);
+ }
+
+ }
+
+ private static final class JmhSummary {
+
+ private final String benchmark;
+
+ private final BigDecimal opRate;
+
+ private final BigDecimal gcRate;
+
+ private JmhSummary(
+ final String benchmark,
+ final BigDecimal opRate,
+ final BigDecimal gcRate) {
+ this.benchmark = benchmark;
+ this.opRate = opRate;
+ this.gcRate = gcRate;
+ }
+
+ private static List<JmhSummary> ofJmhResult(final File jmhResultFile) throws IOException {
+ final List<Object> jmhResult = readObject(jmhResultFile);
+ return ofJmhResult(jmhResult);
+ }
+
+ private static List<JmhSummary> ofJmhResult(final List<Object> jmhResult) {
+ final BigDecimal maxOpRate = jmhResult
+ .stream()
+ .map(jmhResultEntry -> readBigDecimalAtPath(jmhResultEntry, "primaryMetric", "scorePercentiles", "99.0"))
+ .max(BigDecimal::compareTo)
+ .get();
+ return jmhResult
+ .stream()
+ .map(jmhResultEntry -> {
+ final String benchmark = readObjectAtPath(jmhResultEntry, "benchmark");
+ final BigDecimal opRate = readBigDecimalAtPath(jmhResultEntry, "primaryMetric", "scorePercentiles", "99.0");
+ final BigDecimal gcRate = readBigDecimalAtPath(jmhResultEntry, "secondaryMetrics", "·gc.alloc.rate.norm", "scorePercentiles", "99.0");
+ return new JmhSummary(benchmark, opRate, gcRate);
+ })
+ .collect(Collectors.toList());
+ }
+
+ }
+
+ private static <V> V readObject(final File file) throws IOException {
+ final byte[] jsonBytes = Files.readAllBytes(file.toPath());
+ final String json = new String(jsonBytes, CHARSET);
+ @SuppressWarnings("unchecked")
+ final V object = (V) JsonReader.read(json);
+ return object;
+ }
+
+ private static <V> V readObjectAtPath(final Object object, String... path) {
+ Object lastObject = object;
+ for (final String key : path) {
+ @SuppressWarnings("unchecked")
+ Map<String, Object> lastMap = (Map<String, Object>) lastObject;
+ lastObject = lastMap.get(key);
+ }
+ @SuppressWarnings("unchecked")
+ final V typedLastObject = (V) lastObject;
+ return typedLastObject;
+ }
+
+ private static BigDecimal readBigDecimalAtPath(final Object object, String... path) {
+ final Number number = readObjectAtPath(object, path);
+ if (number instanceof BigDecimal) {
+ return (BigDecimal) number;
+ } else if (number instanceof Integer) {
+ final int intNumber = (int) number;
+ return BigDecimal.valueOf(intNumber);
+ } else if (number instanceof Long) {
+ final long longNumber = (long) number;
+ return BigDecimal.valueOf(longNumber);
+ } else if (number instanceof BigInteger) {
+ final BigInteger bigInteger = (BigInteger) number;
+ return new BigDecimal(bigInteger);
+ } else {
+ final String message = String.format(
+ "failed to convert the value to BigDecimal at path %s: %s",
+ Arrays.asList(path), number);
+ throw new IllegalArgumentException(message);
+ }
+ }
+
+ private static void dumpReport(
+ final File outputAdocFile,
+ final JmhSetup jmhSetup,
+ final List<JmhSummary> jmhSummaries) throws IOException {
+ try (final OutputStream outputStream = new FileOutputStream(outputAdocFile);
+ final PrintStream printStream = new PrintStream(outputStream, false, CHARSET.name())) {
+ dumpJmhSetup(printStream, jmhSetup);
+ dumpJmhSummaries(printStream, jmhSummaries, "lite");
+ dumpJmhSummaries(printStream, jmhSummaries, "full");
+ }
+ }
+
+ private static void dumpJmhSetup(
+ final PrintStream printStream,
+ final JmhSetup jmhSetup) {
+ printStream.println("[cols=\"1,4\", options=\"header\"]");
+ printStream.println(".JMH setup");
+ printStream.println("|===");
+ printStream.println("|Setting|Value");
+ printStream.format("|JVM name|%s%n", jmhSetup.vmName);
+ printStream.format("|JVM version|%s%n", jmhSetup.vmVersion);
+ printStream.format("|JVM args|%s%n", jmhSetup.vmArgs != null ? String.join(" ", jmhSetup.vmArgs) : "");
+ printStream.format("|Forks|%s%n", jmhSetup.forkCount);
+ printStream.format("|Warmup iterations|%d × %s%n", jmhSetup.warmupIterationCount, jmhSetup.warmupTime);
+ printStream.format("|Measurement iterations|%d × %s%n", jmhSetup.measurementIterationCount, jmhSetup.measurementTime);
+ printStream.println("|===");
+ }
+
+ private static void dumpJmhSummaries(
+ final PrintStream printStream,
+ final List<JmhSummary> jmhSummaries,
+ final String prefix) {
+
+ // Print header.
+ printStream.println("[cols=\"4,>2,4,>2\", options=\"header\"]");
+ printStream.format(".JMH result (99^th^ percentile) summary for \"%s\" log events%n", prefix);
+ printStream.println("|===");
+ printStream.println("^|Benchmark");
+ printStream.println("2+^|ops/sec");
+ printStream.println("^|B/op");
+
+ // Filter JMH summaries by prefix.
+ final String filterRegex = String.format("^.*\\.%s[A-Za-z0-9]+$", prefix);
+ final List<JmhSummary> filteredJmhSummaries = jmhSummaries
+ .stream()
+ .filter(jmhSummary -> jmhSummary.benchmark.matches(filterRegex))
+ .collect(Collectors.toList());
+
+ // Determine the max. op rate.
+ final BigDecimal maxOpRate = filteredJmhSummaries
+ .stream()
+ .map(jmhSummary -> jmhSummary.opRate)
+ .max(BigDecimal::compareTo)
+ .get();
+
+ // Print each summary.
+ final Comparator<JmhSummary> jmhSummaryComparator =
+ Comparator
+ .comparing((final JmhSummary jmhSummary) -> jmhSummary.opRate)
+ .reversed();
+ filteredJmhSummaries
+ .stream()
+ .sorted(jmhSummaryComparator)
+ .forEach((final JmhSummary jmhSummary) -> {
+ dumpJmhSummary(printStream, maxOpRate, jmhSummary);
+ });
+
+ // Print footer.
+ printStream.println("|===");
+
+ }
+
+ private static void dumpJmhSummary(
+ final PrintStream printStream,
+ final BigDecimal maxOpRate,
+ final JmhSummary jmhSummary) {
+ printStream.println();
+ final String benchmark = jmhSummary
+ .benchmark
+ .replaceAll("^.*\\.([^.]+)$", "$1");
+ printStream.format("|%s%n", benchmark);
+ final long opRatePerSec = jmhSummary
+ .opRate
+ .multiply(BigDecimal.valueOf(1_000L))
+ .toBigInteger()
+ .longValueExact();
+ printStream.format("|%,d%n", opRatePerSec);
+ final BigDecimal normalizedOpRate = jmhSummary
+ .opRate
+ .divide(maxOpRate, RoundingMode.CEILING);
+ final int opRateBarLength = normalizedOpRate
+ .multiply(BigDecimal.valueOf(19))
+ .toBigInteger()
+ .add(BigInteger.ONE)
+ .intValueExact();
+ final String opRateBar = Strings.repeat("â–‰", opRateBarLength);
+ final int opRatePercent = normalizedOpRate
+ .multiply(BigDecimal.valueOf(100))
+ .toBigInteger()
+ .intValueExact();
+ printStream.format("|%s (%d%%)%n", opRateBar, opRatePercent);
+ printStream.format("|%,.1f%n", jmhSummary.gcRate.doubleValue());
+ }
+
+}
diff --git a/log4j-perf/src/main/java/org/apache/logging/log4j/layout/json/template/JsonTemplateLayoutBenchmarkState.java b/log4j-perf/src/main/java/org/apache/logging/log4j/layout/json/template/JsonTemplateLayoutBenchmarkState.java
new file mode 100644
index 0000000..dc8dcd1
--- /dev/null
+++ b/log4j-perf/src/main/java/org/apache/logging/log4j/layout/json/template/JsonTemplateLayoutBenchmarkState.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.logging.log4j.layout.json.template;
+
+import co.elastic.logging.log4j2.EcsLayout;
+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.layout.ByteBufferDestination;
+import org.apache.logging.log4j.core.layout.GelfLayout;
+import org.apache.logging.log4j.core.util.KeyValuePair;
+import org.apache.logging.log4j.core.util.NetUtils;
+import org.apache.logging.log4j.jackson.json.layout.JsonLayout;
+import org.apache.logging.log4j.layout.json.template.JsonTemplateLayout.EventTemplateAdditionalField;
+import org.apache.logging.log4j.layout.json.template.JsonTemplateLayout.EventTemplateAdditionalFields;
+import org.apache.logging.log4j.layout.json.template.util.ThreadLocalRecyclerFactory;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.State;
+
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+
+@State(Scope.Benchmark)
+public class JsonTemplateLayoutBenchmarkState {
+
+ private static final Configuration CONFIGURATION = new DefaultConfiguration();
+
+ private static final Charset CHARSET = StandardCharsets.UTF_8;
+
+ private final ByteBufferDestination byteBufferDestination;
+
+ private final JsonTemplateLayout jsonTemplateLayout4JsonLayout;
+
+ private final JsonTemplateLayout jsonTemplateLayout4EcsLayout;
+
+ private final JsonTemplateLayout jsonTemplateLayout4GelfLayout;
+
+ private final JsonLayout defaultJsonLayout;
+
+ private final JsonLayout customJsonLayout;
+
+ private final EcsLayout ecsLayout;
+
+ private final GelfLayout gelfLayout;
+
+ private final List<LogEvent> fullLogEvents;
+
+ private final List<LogEvent> liteLogEvents;
+
+ public JsonTemplateLayoutBenchmarkState() {
+ this.byteBufferDestination = new BlackHoleByteBufferDestination(1024 * 512);
+ this.jsonTemplateLayout4JsonLayout = createJsonTemplateLayout4JsonLayout();
+ this.jsonTemplateLayout4EcsLayout = createJsonTemplateLayout4EcsLayout();
+ this.jsonTemplateLayout4GelfLayout = createJsonTemplateLayout4GelfLayout();
+ this.defaultJsonLayout = createDefaultJsonLayout();
+ this.customJsonLayout = createCustomJsonLayout();
+ this.ecsLayout = createEcsLayout();
+ this.gelfLayout = createGelfLayout();
+ int logEventCount = 1_000;
+ this.fullLogEvents = LogEventFixture.createFullLogEvents(logEventCount);
+ this.liteLogEvents = LogEventFixture.createLiteLogEvents(logEventCount);
+ }
+
+ private static JsonTemplateLayout createJsonTemplateLayout4JsonLayout() {
+ return JsonTemplateLayout
+ .newBuilder()
+ .setConfiguration(CONFIGURATION)
+ .setCharset(CHARSET)
+ .setEventTemplateUri("classpath:JsonLayout.json")
+ .setRecyclerFactory(ThreadLocalRecyclerFactory.getInstance())
+ .build();
+ }
+
+ private static JsonTemplateLayout createJsonTemplateLayout4EcsLayout() {
+ final EventTemplateAdditionalFields additionalFields = EventTemplateAdditionalFields
+ .newBuilder()
+ .setAdditionalFields(
+ new EventTemplateAdditionalField[]{
+ EventTemplateAdditionalField
+ .newBuilder()
+ .setKey("service.name")
+ .setValue("benchmark")
+ .build()
+ })
+ .build();
+ return JsonTemplateLayout
+ .newBuilder()
+ .setConfiguration(CONFIGURATION)
+ .setCharset(CHARSET)
+ .setEventTemplateUri("classpath:EcsLayout.json")
+ .setRecyclerFactory(ThreadLocalRecyclerFactory.getInstance())
+ .setEventTemplateAdditionalFields(additionalFields)
+ .build();
+ }
+
+ private static JsonTemplateLayout createJsonTemplateLayout4GelfLayout() {
+ return JsonTemplateLayout
+ .newBuilder()
+ .setConfiguration(CONFIGURATION)
+ .setCharset(CHARSET)
+ .setEventTemplateUri("classpath:GelfLayout.json")
+ .setRecyclerFactory(ThreadLocalRecyclerFactory.getInstance())
+ .setEventTemplateAdditionalFields(EventTemplateAdditionalFields
+ .newBuilder()
+ .setAdditionalFields(
+ new EventTemplateAdditionalField[]{
+ // Adding "host" as a constant rather than using
+ // the "hostName" property lookup at runtime, which
+ // is what GelfLayout does as well.
+ EventTemplateAdditionalField
+ .newBuilder()
+ .setKey("host")
+ .setValue(NetUtils.getLocalHostname())
+ .build()
+ })
+ .build())
+ .build();
+ }
+
+ private static JsonLayout createDefaultJsonLayout() {
+ return JsonLayout
+ .newBuilder()
+ .setConfiguration(CONFIGURATION)
+ .setCharset(CHARSET)
+ .build();
+ }
+
+ private static JsonLayout createCustomJsonLayout() {
+ return JsonLayout
+ .newBuilder()
+ .setConfiguration(CONFIGURATION)
+ .setCharset(CHARSET)
+ .setAdditionalFields(new KeyValuePair[]{
+ new KeyValuePair("@version", "\"1\"")
+ })
+ .build();
+ }
+
+ private static EcsLayout createEcsLayout() {
+ return EcsLayout
+ .newBuilder()
+ .setConfiguration(CONFIGURATION)
+ .setCharset(CHARSET)
+ .setServiceName("benchmark")
+ .build();
+ }
+
+ private static GelfLayout createGelfLayout() {
+ return GelfLayout
+ .newBuilder()
+ .setConfiguration(CONFIGURATION)
+ .setCharset(CHARSET)
+ .setCompressionType(GelfLayout.CompressionType.OFF)
+ .build();
+ }
+
+ ByteBufferDestination getByteBufferDestination() {
+ return byteBufferDestination;
+ }
+
+ JsonTemplateLayout getJsonTemplateLayout4JsonLayout() {
+ return jsonTemplateLayout4JsonLayout;
+ }
+
+ JsonTemplateLayout getJsonTemplateLayout4EcsLayout() {
+ return jsonTemplateLayout4EcsLayout;
+ }
+
+ JsonTemplateLayout getJsonTemplateLayout4GelfLayout() {
+ return jsonTemplateLayout4GelfLayout;
+ }
+
+ JsonLayout getDefaultJsonLayout() {
+ return defaultJsonLayout;
+ }
+
+ JsonLayout getCustomJsonLayout() {
+ return customJsonLayout;
+ }
+
+ EcsLayout getEcsLayout() {
+ return ecsLayout;
+ }
+
+ GelfLayout getGelfLayout() {
+ return gelfLayout;
+ }
+
+ List<LogEvent> getFullLogEvents() {
+ return fullLogEvents;
+ }
+
+ List<LogEvent> getLiteLogEvents() {
+ return liteLogEvents;
+ }
+
+}
diff --git a/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/AbstractStringLayoutStringEncodingBenchmark.java b/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/AbstractStringLayoutStringEncodingBenchmark.java
index 21b3e6a..959f6b2 100644
--- a/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/AbstractStringLayoutStringEncodingBenchmark.java
+++ b/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/AbstractStringLayoutStringEncodingBenchmark.java
@@ -123,15 +123,15 @@
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@Benchmark
- public void baseline() {
- consume(bytes);
+ public long baseline() {
+ return consume(bytes);
}
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@Benchmark
- public void usAsciiGetBytes() {
- consume(usAsciiGetBytesLayout.toByteArray(logEvent));
+ public long usAsciiGetBytes() {
+ return consume(usAsciiGetBytesLayout.toByteArray(logEvent));
}
@BenchmarkMode(Mode.Throughput)
@@ -145,8 +145,8 @@
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@Benchmark
- public void iso8859_1GetBytes() {
- consume(iso8859_1GetBytesLayout.toByteArray(logEvent));
+ public long iso8859_1GetBytes() {
+ return consume(iso8859_1GetBytesLayout.toByteArray(logEvent));
}
@BenchmarkMode(Mode.Throughput)
@@ -160,8 +160,8 @@
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@Benchmark
- public void utf8GetBytes() {
- consume(utf8GetBytesLayout.toByteArray(logEvent));
+ public long utf8GetBytes() {
+ return consume(utf8GetBytesLayout.toByteArray(logEvent));
}
@BenchmarkMode(Mode.Throughput)
@@ -175,8 +175,8 @@
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
@Benchmark
- public void utf16GetBytes() {
- consume(utf16GetBytesLayout.toByteArray(logEvent));
+ public long utf16GetBytes() {
+ return consume(utf16GetBytesLayout.toByteArray(logEvent));
}
@BenchmarkMode(Mode.Throughput)
diff --git a/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/ClocksBenchmark.java b/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/ClocksBenchmark.java
index ff778d2..2d2ccb3 100644
--- a/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/ClocksBenchmark.java
+++ b/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/ClocksBenchmark.java
@@ -152,7 +152,7 @@
private static volatile OldCachedClock instance;
private static final Object INSTANCE_LOCK = new Object();
private volatile long millis = System.currentTimeMillis();
- private volatile short count = 0;
+ private volatile short count;
private OldCachedClock() {
final Thread updater = new Thread(new Runnable() {
diff --git a/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/ConcurrentAsyncLoggerToFileBenchmark.java b/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/ConcurrentAsyncLoggerToFileBenchmark.java
new file mode 100644
index 0000000..6eda1ee
--- /dev/null
+++ b/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/ConcurrentAsyncLoggerToFileBenchmark.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.perf.jmh;
+
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.core.LifeCycle;
+import org.apache.logging.log4j.core.async.AsyncQueueFullPolicy;
+import org.apache.logging.log4j.core.async.EventRoute;
+import org.apache.logging.log4j.perf.util.BenchmarkMessageParams;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.Fork;
+import org.openjdk.jmh.annotations.Measurement;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.OutputTimeUnit;
+import org.openjdk.jmh.annotations.Param;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.Setup;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.annotations.TearDown;
+import org.openjdk.jmh.annotations.Threads;
+import org.openjdk.jmh.annotations.Warmup;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Tests Log4j2 Async Loggers performance with many threads producing events quickly while the background
+ * thread persists events to disk.
+ * @see <a href="https://issues.apache.org/jira/browse/LOG4J2-2606">LOG4J2-2606</a>
+ */
+@Fork(1)
+@State(Scope.Benchmark)
+@Warmup(iterations = 1, time = 3)
+@Measurement(iterations = 3, time = 15)
+public class ConcurrentAsyncLoggerToFileBenchmark {
+
+ @Benchmark
+ @Threads(32)
+ @BenchmarkMode(Mode.Throughput)
+ @OutputTimeUnit(TimeUnit.SECONDS)
+ public void concurrentLoggingThreads(BenchmarkState state) {
+ state.logger.info(BenchmarkMessageParams.TEST);
+ }
+
+ @Benchmark
+ @Threads(1)
+ @BenchmarkMode(Mode.Throughput)
+ @OutputTimeUnit(TimeUnit.SECONDS)
+ public void singleLoggingThread(BenchmarkState state) {
+ state.logger.info(BenchmarkMessageParams.TEST);
+ }
+
+ @State(Scope.Benchmark)
+ public static class BenchmarkState {
+
+ @Param({"ENQUEUE", "ENQUEUE_UNSYNCHRONIZED", "SYNCHRONOUS"})
+ private QueueFullPolicy queueFullPolicy;
+
+ @Param({"ASYNC_CONTEXT", "ASYNC_CONFIG"})
+ private AsyncLoggerType asyncLoggerType;
+
+ private Logger logger;
+
+ @Setup
+ public final void before() {
+ new File("target/ConcurrentAsyncLoggerToFileBenchmark.log").delete();
+ System.setProperty("log4j2.is.webapp", "false");
+ asyncLoggerType.setProperties();
+ queueFullPolicy.setProperties();
+ logger = LogManager.getLogger(ConcurrentAsyncLoggerToFileBenchmark.class);
+ }
+
+ @TearDown
+ public final void after() {
+ ((LifeCycle) LogManager.getContext(false)).stop();
+ new File("target/ConcurrentAsyncLoggerToFileBenchmark.log").delete();
+ logger = null;
+ }
+
+ @SuppressWarnings("unused") // Used by JMH
+ public enum QueueFullPolicy {
+ ENQUEUE(Collections.singletonMap("log4j2.AsyncQueueFullPolicy", "Default")),
+ ENQUEUE_UNSYNCHRONIZED(new HashMap<>() {{
+ put("log4j2.AsyncQueueFullPolicy", "Default");
+ put("AsyncLogger.SynchronizeEnqueueWhenQueueFull", "false");
+ put("AsyncLoggerConfig.SynchronizeEnqueueWhenQueueFull", "false");
+ }
+ }),
+ SYNCHRONOUS(Collections.singletonMap("log4j2.AsyncQueueFullPolicy",
+ SynchronousAsyncQueueFullPolicy.class.getName()));
+
+ private final Map<String, String> properties;
+
+ QueueFullPolicy(Map<String, String> properties) {
+ this.properties = properties;
+ }
+
+ void setProperties() {
+ for (Map.Entry<String, String> entry : properties.entrySet()) {
+ System.setProperty(entry.getKey(), entry.getValue());
+ }
+ }
+ }
+
+ @SuppressWarnings("unused") // Used by JMH
+ public enum AsyncLoggerType {
+ ASYNC_CONTEXT,
+ ASYNC_CONFIG;
+ // TODO(ckozak): Consider adding ASYNC_APPENDER
+
+ void setProperties() {
+ switch (this) {
+ case ASYNC_CONTEXT:
+ System.setProperty("log4j.configurationFile", "ConcurrentAsyncLoggerToFileBenchmark.xml");
+ System.setProperty("Log4jContextSelector",
+ "org.apache.logging.log4j.core.async.AsyncLoggerContextSelector");
+ break;
+ case ASYNC_CONFIG:
+ System.setProperty("log4j.configurationFile",
+ "ConcurrentAsyncLoggerToFileBenchmark-asyncConfig.xml");
+ break;
+ default:
+ throw new IllegalStateException("Unknown type: " + this);
+ }
+ }
+ }
+ }
+
+ public static final class SynchronousAsyncQueueFullPolicy implements AsyncQueueFullPolicy {
+ @Override
+ public EventRoute getRoute(final long backgroundThreadId, final Level level) {
+ return EventRoute.SYNCHRONOUS;
+ }
+ }
+}
diff --git a/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/FileAppenderBenchmark.java b/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/FileAppenderBenchmark.java
index e851d5c..91ce426 100644
--- a/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/FileAppenderBenchmark.java
+++ b/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/FileAppenderBenchmark.java
@@ -107,21 +107,22 @@
final File julFile = new File("target/testJulLog.log");
julFile.delete();
}
-
+/*
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.SECONDS)
@Benchmark
public void log4j2RAF() {
log4j2RandomLogger.debug(MESSAGE);
- }
+ }*/
+ /* The MemoryMappedFileAppender gets exceptions in Java 11
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.SECONDS)
@Benchmark
public void log4j2MMF() {
log4j2MemoryLogger.debug(MESSAGE);
- }
-
+ } */
+/*
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.SECONDS)
@Benchmark
@@ -141,7 +142,7 @@
@Benchmark
public void log4j2AsyncLogger() {
log4j2AsyncLogger.debug(MESSAGE);
- }
+ }*/
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.SECONDS)
@@ -153,10 +154,17 @@
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.SECONDS)
@Benchmark
+ public void log4j2Builder() {
+ log4j2Logger.atDebug().withLocation().log(MESSAGE);
+ }
+
+ @BenchmarkMode(Mode.Throughput)
+ @OutputTimeUnit(TimeUnit.SECONDS)
+ @Benchmark
public void logbackFile() {
slf4jLogger.debug(MESSAGE);
}
-
+/*
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.SECONDS)
@Benchmark
@@ -177,5 +185,5 @@
public void julFile() {
// must specify sourceClass or JUL will look it up by walking the stack trace!
julLogger.logp(Level.INFO, getClass().getName(), "julFile", MESSAGE);
- }
+ }*/
}
diff --git a/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/FileAppenderThrowableBenchmark.java b/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/FileAppenderThrowableBenchmark.java
index 6f02c01..fde3b47 100644
--- a/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/FileAppenderThrowableBenchmark.java
+++ b/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/FileAppenderThrowableBenchmark.java
@@ -19,12 +19,16 @@
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.core.async.AsyncLoggerContext;
+import org.apache.logging.log4j.core.async.AsyncLoggerContextSelector;
+import org.apache.logging.log4j.core.util.Constants;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Fork;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.OutputTimeUnit;
+import org.openjdk.jmh.annotations.Param;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
@@ -34,6 +38,10 @@
import org.slf4j.LoggerFactory;
import java.io.File;
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
import java.util.concurrent.TimeUnit;
import java.util.logging.FileHandler;
import java.util.logging.Level;
@@ -44,52 +52,117 @@
*/
// HOW TO RUN THIS TEST
// java -jar target/benchmarks.jar ".*FileAppenderThrowableBenchmark.*" -f 1 -i 10 -wi 20 -bm sample -tu ns
-@State(Scope.Thread)
+@State(Scope.Benchmark)
@Threads(1)
@Fork(1)
@Warmup(iterations = 3, time = 3)
@Measurement(iterations = 3, time = 3)
public class FileAppenderThrowableBenchmark {
+ static {
+ // log4j2
+ System.setProperty("log4j2.is.webapp", "false");
+ System.setProperty("log4j.configurationFile", "log4j2-perf-file-throwable.xml");
+ // log4j 1.2
+ System.setProperty("log4j.configuration", "log4j12-perf-file-throwable.xml");
+ // logback
+ System.setProperty("logback.configurationFile", "logback-perf-file-throwable.xml");
+ }
- private static final Throwable THROWABLE = getThrowable();
+ private static final Throwable THROWABLE = getSimpleThrowable();
+ private static final Throwable COMPLEX_THROWABLE = getComplexThrowable();
- private static Throwable getThrowable() {
+ @SuppressWarnings("unused") // Set by JMH
+ @Param
+ private LoggingConfiguration loggingConfiguration;
+
+ private static Throwable getSimpleThrowable() {
return new IllegalStateException("Test Throwable");
}
- private FileHandler julFileHandler;
- Logger log4j2ExtendedThrowable;
- Logger log4j2SimpleThrowable;
- org.slf4j.Logger slf4jLogger;
- org.apache.log4j.Logger log4j1Logger;
- java.util.logging.Logger julLogger;
+ interface ThrowableHelper {
+ void action();
+ }
+
+ // Used to create a deeper stack with many different classes
+ // This makes the ThrowableProxy Map<String, CacheEntry> cache
+ // perform more closely to real applications.
+ interface TestIface0 extends ThrowableHelper {}
+ interface TestIface1 extends ThrowableHelper {}
+ interface TestIface2 extends ThrowableHelper {}
+ interface TestIface3 extends ThrowableHelper {}
+ interface TestIface4 extends ThrowableHelper {}
+ interface TestIface5 extends ThrowableHelper {}
+ interface TestIface6 extends ThrowableHelper {}
+ interface TestIface7 extends ThrowableHelper {}
+ interface TestIface8 extends ThrowableHelper {}
+ interface TestIface9 extends ThrowableHelper {}
+ interface TestIface10 extends ThrowableHelper {}
+ interface TestIface11 extends ThrowableHelper {}
+ interface TestIface12 extends ThrowableHelper {}
+ interface TestIface13 extends ThrowableHelper {}
+ interface TestIface14 extends ThrowableHelper {}
+ interface TestIface15 extends ThrowableHelper {}
+ interface TestIface16 extends ThrowableHelper {}
+ interface TestIface17 extends ThrowableHelper {}
+ interface TestIface18 extends ThrowableHelper {}
+ interface TestIface19 extends ThrowableHelper {}
+ interface TestIface20 extends ThrowableHelper {}
+ interface TestIface21 extends ThrowableHelper {}
+ interface TestIface22 extends ThrowableHelper {}
+ interface TestIface23 extends ThrowableHelper {}
+ interface TestIface24 extends ThrowableHelper {}
+ interface TestIface25 extends ThrowableHelper {}
+ interface TestIface26 extends ThrowableHelper {}
+ interface TestIface27 extends ThrowableHelper {}
+ interface TestIface28 extends ThrowableHelper {}
+ interface TestIface29 extends ThrowableHelper {}
+ interface TestIface30 extends ThrowableHelper {}
+
+ private static Throwable getComplexThrowable() {
+ ThrowableHelper helper = new ThrowableHelper() {
+ @Override
+ public void action() {
+ throw new IllegalStateException("Test Throwable");
+ }
+ };
+ try {
+ for (int i = 0; i < 31; i++) {
+ final ThrowableHelper delegate = helper;
+ helper = (ThrowableHelper) Proxy.newProxyInstance(
+ FileAppenderThrowableBenchmark.class.getClassLoader(),
+ new Class<?>[]{Class.forName(FileAppenderThrowableBenchmark.class.getName() + "$TestIface" + (i % 31))},
+ new InvocationHandler() {
+ @Override
+ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+ try {
+ return method.invoke(delegate, args);
+ } catch (InvocationTargetException e) {
+ throw e.getCause();
+ }
+ }
+ });
+ }
+ } catch (ClassNotFoundException e) {
+ throw new IllegalStateException("Failed to create stack", e);
+ }
+ try {
+ helper.action();
+ } catch (IllegalStateException e) {
+ return e;
+ }
+ throw new IllegalStateException("Failed to create throwable");
+ }
+
@Setup
public void setUp() throws Exception {
deleteLogFiles();
- System.setProperty("log4j2.is.webapp", "false");
- System.setProperty("log4j.configurationFile", "log4j2-perf-file-throwable.xml");
- System.setProperty("log4j.configuration", "log4j12-perf-file-throwable.xml");
- System.setProperty("logback.configurationFile", "logback-perf-file-throwable.xml");
-
- log4j2ExtendedThrowable = LogManager.getLogger("RAFExtendedException");
- log4j2SimpleThrowable = LogManager.getLogger("RAFSimpleException");
- slf4jLogger = LoggerFactory.getLogger(getClass());
- log4j1Logger = org.apache.log4j.Logger.getLogger(getClass());
-
- julFileHandler = new FileHandler("target/testJulLog.log");
- julLogger = java.util.logging.Logger.getLogger(getClass().getName());
- julLogger.setUseParentHandlers(false);
- julLogger.addHandler(julFileHandler);
- julLogger.setLevel(Level.ALL);
+ loggingConfiguration.setUp();
}
@TearDown
- public void tearDown() {
- System.clearProperty("log4j2.is.webapp");
- System.clearProperty("log4j.configurationFile");
- System.clearProperty("log4j.configuration");
- System.clearProperty("logback.configurationFile");
+ public void tearDown() throws Exception{
+ loggingConfiguration.tearDown();
deleteLogFiles();
}
@@ -106,39 +179,198 @@
julFile.delete();
}
- @BenchmarkMode(Mode.Throughput)
- @OutputTimeUnit(TimeUnit.SECONDS)
- @Benchmark
- public void log4j1() {
- log4j1Logger.error("Caught an exception", THROWABLE);
+ @SuppressWarnings("unused") // Used by JMH
+ public enum LoggingConfiguration {
+ LOG4J2_EXTENDED_THROWABLE() {
+ Logger logger;
+ @Override
+ void setUp() throws Exception {
+ logger = LogManager.getLogger("RAFExtendedException");
+ }
+
+ @Override
+ void tearDown() throws Exception {
+
+ }
+
+ @Override
+ void log(String message, Throwable throwable) {
+ logger.error(message, throwable);
+ }
+ },
+ LOG4J2_EXTENDED_THROWABLE_ASYNC() {
+ Logger logger;
+ @Override
+ void setUp() throws Exception {
+ System.setProperty(Constants.LOG4J_CONTEXT_SELECTOR,
+ AsyncLoggerContextSelector.class.getName());
+ logger = LogManager.getLogger("RAFExtendedException");
+ if (!AsyncLoggerContext.class.equals(LogManager.getContext(false).getClass())) {
+ throw new IllegalStateException("Expected an AsyncLoggerContext");
+ }
+ }
+
+ @Override
+ void tearDown() throws Exception {
+
+ }
+
+ @Override
+ void log(String message, Throwable throwable) {
+ logger.error(message, throwable);
+ }
+ },
+ LOG4J2_EXTENDED_THROWABLE_ASYNC_CONFIG() {
+ Logger logger;
+ @Override
+ void setUp() throws Exception {
+ logger = LogManager.getLogger("async.RAFExtendedException");
+ }
+
+ @Override
+ void tearDown() throws Exception {
+
+ }
+
+ @Override
+ void log(String message, Throwable throwable) {
+ logger.error(message, throwable);
+ }
+ },
+ LOG4J2_THROWABLE() {
+ Logger logger;
+ @Override
+ void setUp() throws Exception {
+ logger = LogManager.getLogger("RAFSimpleException");
+ }
+
+ @Override
+ void tearDown() throws Exception {
+
+ }
+
+ @Override
+ void log(String message, Throwable throwable) {
+ logger.error(message, throwable);
+ }
+ },
+ LOG4J2_THROWABLE_ASYNC() {
+ Logger logger;
+ @Override
+ void setUp() throws Exception {
+ System.setProperty(Constants.LOG4J_CONTEXT_SELECTOR,
+ AsyncLoggerContextSelector.class.getName());
+ logger = LogManager.getLogger("RAFSimpleException");
+ if (!AsyncLoggerContext.class.equals(LogManager.getContext(false).getClass())) {
+ throw new IllegalStateException("Expected an AsyncLoggerContext");
+ }
+ }
+
+ @Override
+ void tearDown() throws Exception {
+
+ }
+
+ @Override
+ void log(String message, Throwable throwable) {
+ logger.error(message, throwable);
+ }
+ },
+ LOG4J2_THROWABLE_ASYNC_CONFIG() {
+ Logger logger;
+ @Override
+ void setUp() throws Exception {
+ logger = LogManager.getLogger("async.RAFSimpleException");
+ }
+
+ @Override
+ void tearDown() throws Exception {
+
+ }
+
+ @Override
+ void log(String message, Throwable throwable) {
+ logger.error(message, throwable);
+ }
+ },
+ LOG4J1() {
+ org.apache.log4j.Logger logger;
+ @Override
+ void setUp() throws Exception {
+ logger = org.apache.log4j.Logger.getLogger(FileAppenderThrowableBenchmark.class);
+ }
+
+ @Override
+ void tearDown() throws Exception {
+
+ }
+
+ @Override
+ void log(String message, Throwable throwable) {
+ logger.error(message, throwable);
+ }
+ },
+ LOGBACK() {
+ org.slf4j.Logger logger;
+
+ @Override
+ void setUp() throws Exception {
+ logger = LoggerFactory.getLogger(FileAppenderThrowableBenchmark.class);
+ }
+
+ @Override
+ void tearDown() throws Exception {
+
+ }
+
+ @Override
+ void log(String message, Throwable throwable) {
+ logger.error(message, throwable);
+ }
+ },
+ JUL() {
+ private FileHandler julFileHandler;
+ private java.util.logging.Logger logger;
+
+ @Override
+ void setUp() throws Exception {
+ julFileHandler = new FileHandler("target/testJulLog.log");
+ logger = java.util.logging.Logger.getLogger(getClass().getName());
+ logger.setUseParentHandlers(false);
+ logger.addHandler(julFileHandler);
+ logger.setLevel(Level.ALL);
+ }
+
+ @Override
+ void tearDown() throws Exception {
+ julFileHandler.close();
+ }
+
+ @Override
+ void log(String message, Throwable throwable) {
+ // must specify sourceClass or JUL will look it up by walking the stack trace!
+ logger.logp(Level.SEVERE, FileAppenderThrowableBenchmark.class.getName(), "param1JulFile", message, throwable);
+ }
+ };
+
+ abstract void setUp() throws Exception;
+
+ abstract void tearDown() throws Exception;
+
+ abstract void log(String message, Throwable throwable);
}
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.SECONDS)
@Benchmark
- public void log4j2SimpleThrowable() {
- log4j2SimpleThrowable.error("Caught an exception", THROWABLE);
+ public void simpleThrowable() {
+ loggingConfiguration.log("Caught an exception", THROWABLE);
}
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.SECONDS)
@Benchmark
- public void log4j2ExtendedThrowable() {
- log4j2ExtendedThrowable.error("Caught an exception", THROWABLE);
- }
-
- @BenchmarkMode(Mode.Throughput)
- @OutputTimeUnit(TimeUnit.SECONDS)
- @Benchmark
- public void logbackFile() {
- slf4jLogger.error("Caught an exception", THROWABLE);
- }
-
- @BenchmarkMode(Mode.Throughput)
- @OutputTimeUnit(TimeUnit.SECONDS)
- @Benchmark
- public void julFile() {
- // must specify sourceClass or JUL will look it up by walking the stack trace!
- julLogger.logp(Level.SEVERE, getClass().getName(), "param1JulFile", "Caught an exception", THROWABLE);
+ public void complexThrowable() {
+ loggingConfiguration.log("Caught an exception", COMPLEX_THROWABLE);
}
}
diff --git a/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/FileAppenderWithLocationBenchmark.java b/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/FileAppenderWithLocationBenchmark.java
index f0c2dd9..da173a3 100644
--- a/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/FileAppenderWithLocationBenchmark.java
+++ b/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/FileAppenderWithLocationBenchmark.java
@@ -102,6 +102,13 @@
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.SECONDS)
@Benchmark
+ public void log4j2FluentFile() {
+ log4j2Logger.atDebug().withLocation().log(MESSAGE);
+ }
+
+ @BenchmarkMode(Mode.Throughput)
+ @OutputTimeUnit(TimeUnit.SECONDS)
+ @Benchmark
public void logbackFile() {
slf4jLogger.debug(MESSAGE);
}
diff --git a/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/LoggerConfigBenchmark.java b/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/LoggerConfigBenchmark.java
index bf64b19..41ef9c8 100644
--- a/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/LoggerConfigBenchmark.java
+++ b/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/LoggerConfigBenchmark.java
@@ -29,6 +29,7 @@
import org.apache.logging.log4j.core.appender.AbstractAppender;
import org.apache.logging.log4j.core.config.AppenderControl;
import org.apache.logging.log4j.core.config.LoggerConfig;
+import org.apache.logging.log4j.core.config.Property;
import org.apache.logging.log4j.core.impl.Log4jLogEvent;
import org.apache.logging.log4j.message.SimpleMessage;
import org.openjdk.jmh.annotations.Benchmark;
@@ -52,7 +53,7 @@
public class LoggerConfigBenchmark {
private final CopyOnWriteArraySet<AppenderControl> appenderSet = new CopyOnWriteArraySet<>();
- private volatile Filter filter = null;
+ private volatile Filter filter;
private final boolean additive = true;
private final boolean includeLocation = true;
private LoggerConfig parent;
@@ -68,7 +69,7 @@
private final AtomicInteger count = new AtomicInteger();
protected SimpleListAppender() {
- super("list", null, null);
+ super("list", null, null, true, Property.EMPTY_ARRAY);
}
@Override
diff --git a/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/LoggingDisabledBenchmark.java b/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/LoggingDisabledBenchmark.java
new file mode 100644
index 0000000..0f8805c
--- /dev/null
+++ b/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/LoggingDisabledBenchmark.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.perf.jmh;
+
+import java.io.File;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.OutputTimeUnit;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.Setup;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.annotations.TearDown;
+import org.slf4j.LoggerFactory;
+
+//import com.newrelic.api.agent.Trace;
+
+/**
+ * Benchmark logging with logging disabled.
+ * // ============================== HOW TO RUN THIS TEST: ====================================
+ * //
+ * // single thread:
+ * // java -jar log4j-perf/target/benchmarks.jar ".*LoggingDisabledBenchmark.*" -f 1 -wi 5 -i 10
+ * //
+ * // multiple threads (for example, 4 threads):
+ * // java -jar log4j-perf/target/benchmarks.jar ".*LoggingDisabledBenchmark.*" -f 1 -wi 5 -i 10 -t 4 -si true
+ * //
+ * // Usage help:
+ * // java -jar log4j-perf/target/benchmarks.jar -help
+ * //
+ */
+@State(Scope.Thread)
+public class LoggingDisabledBenchmark {
+
+ Logger log4j2Logger;
+ org.slf4j.Logger slf4jLogger;
+ org.apache.log4j.Logger log4j1Logger;
+
+ @Setup
+ public void setUp() throws Exception {
+ System.setProperty("log4j.configurationFile", "log4j2-perf2.xml");
+ System.setProperty("log4j.configuration", "log4j12-perf2.xml");
+ System.setProperty("logback.configurationFile", "logback-perf2.xml");
+
+ deleteLogFiles();
+
+ log4j2Logger = LogManager.getLogger(FileAppenderWithLocationBenchmark.class);
+ slf4jLogger = LoggerFactory.getLogger(FileAppenderWithLocationBenchmark.class);
+ log4j1Logger = org.apache.log4j.Logger.getLogger(FileAppenderWithLocationBenchmark.class);
+ }
+
+ @TearDown
+ public void tearDown() {
+ System.clearProperty("log4j.configurationFile");
+ System.clearProperty("log4j.configuration");
+ System.clearProperty("logback.configurationFile");
+
+ deleteLogFiles();
+ }
+
+ private void deleteLogFiles() {
+ final File logbackFile = new File("target/testlogback.log");
+ logbackFile.delete();
+ final File log4jFile = new File ("target/testlog4j.log");
+ log4jFile.delete();
+ final File log4j2File = new File ("target/testlog4j2.log");
+ log4j2File.delete();
+ }
+
+ @Benchmark
+ @BenchmarkMode(Mode.AverageTime) @OutputTimeUnit(TimeUnit.NANOSECONDS)
+ public void baseline() {
+ }
+
+ /*
+ This benchmark tests the overhead of NewRelic on method calls. It is commented out so
+ that we don't have to include the dependency during a "normal" build. Uncomment and add
+ the New Relic Agent client dependency if you would like to test this.
+ @Benchmark
+ @BenchmarkMode(Mode.AverageTime) @OutputTimeUnit(TimeUnit.NANOSECONDS)
+ @Trace(dispatcher = true)
+ public void log4j2NewRelic() {
+ log4j2Logger.debug("This won't be logged");
+ } */
+
+ @Benchmark
+ @BenchmarkMode(Mode.AverageTime) @OutputTimeUnit(TimeUnit.NANOSECONDS)
+ public void log4j2() {
+ log4j2Logger.debug("This won't be logged");
+ }
+
+ @Benchmark
+ @BenchmarkMode(Mode.AverageTime) @OutputTimeUnit(TimeUnit.NANOSECONDS)
+ public void slf4j() {
+ slf4jLogger.debug("This won't be logged");
+ }
+
+ @Benchmark
+ @BenchmarkMode(Mode.AverageTime) @OutputTimeUnit(TimeUnit.NANOSECONDS)
+ public void log4j2IsDebugEnabled() {
+ if (log4j2Logger.isDebugEnabled()) {
+ log4j2Logger.debug("This won't be logged");
+ }
+ }
+
+ @Benchmark
+ @BenchmarkMode(Mode.AverageTime) @OutputTimeUnit(TimeUnit.NANOSECONDS)
+ public void slf4jIsDebugEnabled() {
+ if (slf4jLogger.isDebugEnabled()) {
+ slf4jLogger.debug("This won't be logged");
+ }
+ }
+
+ @Benchmark
+ @BenchmarkMode(Mode.AverageTime) @OutputTimeUnit(TimeUnit.NANOSECONDS)
+ public void log4j1IsDebugEnabled() {
+ if (log4j1Logger.isDebugEnabled()) {
+ log4j1Logger.debug("This won't be logged");
+ }
+ }
+
+}
diff --git a/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/OutputBenchmark.java b/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/OutputBenchmark.java
new file mode 100644
index 0000000..3aa8a97
--- /dev/null
+++ b/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/OutputBenchmark.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.perf.jmh;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.PrintStream;
+import java.util.concurrent.TimeUnit;
+import java.util.logging.FileHandler;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.Group;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.OutputTimeUnit;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.Setup;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.annotations.TearDown;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Benchmarks Log4j 2, Log4j 1, Logback and JUL using the DEBUG level which is enabled for this test. The configuration
+ * for each uses a FileAppender
+ */
+// HOW TO RUN THIS TEST
+// java -jar log4j-perf/target/benchmarks.jar ".*OutputBenchmark.*" -f 1 -wi 10 -i 20
+//
+// RUNNING THIS TEST WITH 4 THREADS:
+// java -jar log4j-perf/target/benchmarks.jar ".*OutputBenchmark.*" -f 1 -wi 10 -i 20 -t 4
+@State(Scope.Thread)
+public class OutputBenchmark {
+ public static final String MESSAGE = "This is a debug message";
+
+ Logger log4j2Logger;
+
+
+ @State(Scope.Group)
+ public static class Redirect {
+ PrintStream defaultStream = System.out;
+
+ @Setup
+ public void setUp() throws Exception {
+ PrintStream ps = new PrintStream(new FileOutputStream("target/stdout.log"));
+ System.setOut(ps);
+ }
+
+ @TearDown
+ public void tearDown() {
+ PrintStream ps = System.out;
+ System.setOut(defaultStream);
+ ps.close();
+ }
+ }
+
+ @Setup
+ public void setUp() throws Exception {
+ System.setProperty("log4j.configurationFile", "log4j2-perf3.xml");
+
+ deleteLogFiles();
+
+ log4j2Logger = LogManager.getLogger(OutputBenchmark.class);
+ }
+
+ @TearDown
+ public void tearDown() {
+ System.clearProperty("log4j.configurationFile");
+
+ deleteLogFiles();
+ }
+
+ private void deleteLogFiles() {
+ final File outFile = new File("target/stdout.log");
+ final File log4j2File = new File ("target/testlog4j2.log");
+ log4j2File.delete();
+ outFile.delete();
+ }
+
+ @BenchmarkMode(Mode.Throughput)
+ @OutputTimeUnit(TimeUnit.SECONDS)
+ @Group("console")
+ @Benchmark
+ public void console() {
+ System.out.println(MESSAGE);
+ }
+
+ @BenchmarkMode(Mode.Throughput)
+ @OutputTimeUnit(TimeUnit.SECONDS)
+ @Group("file")
+ @Benchmark
+ public void log4j2File() {
+ log4j2Logger.debug(MESSAGE);
+ }
+
+ @BenchmarkMode(Mode.Throughput)
+ @OutputTimeUnit(TimeUnit.SECONDS)
+ @Group("redirect")
+ @Benchmark
+ public void redirect(Redirect redirect) {
+ System.out.println(MESSAGE);
+ }
+}
diff --git a/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/PatternLayoutBenchmark.java b/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/PatternLayoutBenchmark.java
index 85e3956..35ded03 100644
--- a/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/PatternLayoutBenchmark.java
+++ b/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/PatternLayoutBenchmark.java
@@ -60,18 +60,150 @@
private static final String DEFAULT_ENCODING = CHARSET_DEFAULT.name();
private static final String STRING_SHIFT_JIS = "SHIFT_JIS";
private static final Charset CHARSET_SHIFT_JIS = Charset.forName(STRING_SHIFT_JIS);
- private final PatternLayout PATTERN_M = PatternLayout.createLayout("%m%n", null, null, null, CHARSET_DEFAULT, false, true, null, null);
- private final PatternLayout PATTERN_SPACE = PatternLayout.createLayout(" ", null, null, null, CHARSET_DEFAULT, false, true, null, null);
- private final PatternLayout PATTERN_M_C = PatternLayout.createLayout("%c %m%n", null, null, null, CHARSET_DEFAULT, false, true, null, null);
- private final PatternLayout PATTERN_M_C_D = PatternLayout.createLayout("%d %c %m%n", null, null, null, CHARSET_DEFAULT, false, true, null, null);
- private final PatternLayout PATTERN_M_D = PatternLayout.createLayout("%d %m%n", null, null, null, CHARSET_DEFAULT, false, true, null, null);
- private final PatternLayout PATTERN_C = PatternLayout.createLayout("%c%n", null, null, null, CHARSET_DEFAULT, false, true, null, null);
- private final PatternLayout PATTERN_D = PatternLayout.createLayout("%d%n", null, null, null, CHARSET_DEFAULT, false, true, null, null);
- private final PatternLayout PATTERN_M_D_NOSPACE = PatternLayout.createLayout("%d%m%n", null, null, null, CHARSET_DEFAULT, false, true, null, null);
- private final PatternLayout PATTERN_M_C_NOSPACE = PatternLayout.createLayout("%c%m%n", null, null, null, CHARSET_DEFAULT, false, true, null, null);
- private final PatternLayout PATTERN_M_EX = PatternLayout.createLayout("%m %ex%n", null, null, null, CHARSET_DEFAULT, false, true, null, null);
- private final PatternLayout PATTERN_M_D_EX = PatternLayout.createLayout("%d %m%ex%n", null, null, null, CHARSET_DEFAULT, false, true, null, null);
- private final PatternLayout PATTERN_M_C_D_EX = PatternLayout.createLayout("%d %c %m%ex%n", null, null, null, CHARSET_DEFAULT, false, true, null, null);
+
+ private final PatternLayout PATTERN_M = PatternLayout.newBuilder()
+ .setPattern((String) "%m%n")
+ .setPatternSelector(null)
+ .setConfiguration(null)
+ .setRegexReplacement(null)
+ .setCharset((Charset) CHARSET_DEFAULT)
+ .setAlwaysWriteExceptions(false)
+ .setNoConsoleNoAnsi(true)
+ .setHeader(null)
+ .setFooter(null)
+ .build();
+
+ private final PatternLayout PATTERN_SPACE = PatternLayout.newBuilder()
+ .setPattern((String) " ")
+ .setPatternSelector(null)
+ .setConfiguration(null)
+ .setRegexReplacement(null)
+ .setCharset((Charset) CHARSET_DEFAULT)
+ .setAlwaysWriteExceptions(false)
+ .setNoConsoleNoAnsi(true)
+ .setHeader(null)
+ .setFooter(null)
+ .build();
+
+ private final PatternLayout PATTERN_M_C = PatternLayout.newBuilder()
+ .setPattern((String) "%c %m%n")
+ .setPatternSelector(null)
+ .setConfiguration(null)
+ .setRegexReplacement(null)
+ .setCharset((Charset) CHARSET_DEFAULT)
+ .setAlwaysWriteExceptions(false)
+ .setNoConsoleNoAnsi(true)
+ .setHeader(null)
+ .setFooter(null)
+ .build();
+
+ private final PatternLayout PATTERN_M_C_D = PatternLayout.newBuilder()
+ .setPattern((String) "%d %c %m%n")
+ .setPatternSelector(null)
+ .setConfiguration(null)
+ .setRegexReplacement(null)
+ .setCharset((Charset) CHARSET_DEFAULT)
+ .setAlwaysWriteExceptions(false)
+ .setNoConsoleNoAnsi(true)
+ .setHeader(null)
+ .setFooter(null)
+ .build();
+
+ private final PatternLayout PATTERN_M_D = PatternLayout.newBuilder()
+ .setPattern((String) "%d %m%n")
+ .setPatternSelector(null)
+ .setConfiguration(null)
+ .setRegexReplacement(null)
+ .setCharset((Charset) CHARSET_DEFAULT)
+ .setAlwaysWriteExceptions(false)
+ .setNoConsoleNoAnsi(true)
+ .setHeader(null)
+ .setFooter(null)
+ .build();
+
+ private final PatternLayout PATTERN_C = PatternLayout.newBuilder()
+ .setPattern((String) "%c%n")
+ .setPatternSelector(null)
+ .setConfiguration(null)
+ .setRegexReplacement(null)
+ .setCharset((Charset) CHARSET_DEFAULT)
+ .setAlwaysWriteExceptions(false)
+ .setNoConsoleNoAnsi(true)
+ .setHeader(null)
+ .setFooter(null)
+ .build();
+
+ private final PatternLayout PATTERN_D = PatternLayout.newBuilder()
+ .setPattern((String) "%d%n")
+ .setPatternSelector(null)
+ .setConfiguration(null)
+ .setRegexReplacement(null)
+ .setCharset((Charset) CHARSET_DEFAULT)
+ .setAlwaysWriteExceptions(false)
+ .setNoConsoleNoAnsi(true)
+ .setHeader(null)
+ .setFooter(null)
+ .build();
+
+ private final PatternLayout PATTERN_M_D_NOSPACE = PatternLayout.newBuilder()
+ .setPattern((String) "%d%m%n")
+ .setPatternSelector(null)
+ .setConfiguration(null)
+ .setRegexReplacement(null)
+ .setCharset((Charset) CHARSET_DEFAULT)
+ .setAlwaysWriteExceptions(false)
+ .setNoConsoleNoAnsi(true)
+ .setHeader(null)
+ .setFooter(null)
+ .build();
+
+ private final PatternLayout PATTERN_M_C_NOSPACE = PatternLayout.newBuilder()
+ .setPattern((String) "%c%m%n")
+ .setPatternSelector(null)
+ .setConfiguration(null)
+ .setRegexReplacement(null)
+ .setCharset((Charset) CHARSET_DEFAULT)
+ .setAlwaysWriteExceptions(false)
+ .setNoConsoleNoAnsi(true)
+ .setHeader(null)
+ .setFooter(null)
+ .build();
+
+ private final PatternLayout PATTERN_M_EX = PatternLayout.newBuilder()
+ .setPattern((String) "%m %ex%n")
+ .setPatternSelector(null)
+ .setConfiguration(null)
+ .setRegexReplacement(null)
+ .setCharset((Charset) CHARSET_DEFAULT)
+ .setAlwaysWriteExceptions(false)
+ .setNoConsoleNoAnsi(true)
+ .setHeader(null)
+ .setFooter(null)
+ .build();
+
+ private final PatternLayout PATTERN_M_D_EX = PatternLayout.newBuilder()
+ .setPattern((String) "%d %m%ex%n")
+ .setPatternSelector(null)
+ .setConfiguration(null)
+ .setRegexReplacement(null)
+ .setCharset((Charset) CHARSET_DEFAULT)
+ .setAlwaysWriteExceptions(false)
+ .setNoConsoleNoAnsi(true)
+ .setHeader(null)
+ .setFooter(null)
+ .build();
+
+ private final PatternLayout PATTERN_M_C_D_EX = PatternLayout.newBuilder()
+ .setPattern((String) "%d %c %m%ex%n")
+ .setPatternSelector(null)
+ .setConfiguration(null)
+ .setRegexReplacement(null)
+ .setCharset((Charset) CHARSET_DEFAULT)
+ .setAlwaysWriteExceptions(false)
+ .setNoConsoleNoAnsi(true)
+ .setHeader(null)
+ .setFooter(null)
+ .build();
private static LogEvent createLogEvent() {
final Marker marker = null;
diff --git a/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/PatternLayoutComparisonBenchmark.java b/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/PatternLayoutComparisonBenchmark.java
index 6402a6c..e863aa3 100644
--- a/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/PatternLayoutComparisonBenchmark.java
+++ b/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/PatternLayoutComparisonBenchmark.java
@@ -59,8 +59,18 @@
final static LogEvent LOG4J2EVENT = createLog4j2Event();
private static final Charset CHARSET_DEFAULT = Charset.defaultCharset();
private static final String LOG4JPATTERN = "%d %5p [%t] %c{1} %X{transactionId} - %m%n";
- private final PatternLayout LOG4J2_PATTERN_LAYOUT = PatternLayout.createLayout(LOG4JPATTERN, null,
- null, null, CHARSET_DEFAULT, false, true, null, null);
+
+ private final PatternLayout LOG4J2_PATTERN_LAYOUT = PatternLayout.newBuilder()
+ .setPattern((String) LOG4JPATTERN)
+ .setPatternSelector(null)
+ .setConfiguration(null)
+ .setRegexReplacement(null)
+ .setCharset((Charset) CHARSET_DEFAULT)
+ .setAlwaysWriteExceptions(false)
+ .setNoConsoleNoAnsi(true)
+ .setHeader(null)
+ .setFooter(null)
+ .build();
private static LogEvent createLog4j2Event() {
final Marker marker = null;
diff --git a/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/SortedArrayVsHashMapBenchmark.java b/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/SortedArrayVsHashMapBenchmark.java
index d951617..81baeda 100644
--- a/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/SortedArrayVsHashMapBenchmark.java
+++ b/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/SortedArrayVsHashMapBenchmark.java
@@ -236,4 +236,4 @@
map.put("someKey", "someValue");
return map.size();
}
-}
\ No newline at end of file
+}
diff --git a/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/StackWalkBenchmark.java b/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/StackWalkBenchmark.java
new file mode 100644
index 0000000..15dcfb6
--- /dev/null
+++ b/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/StackWalkBenchmark.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.perf.jmh;
+
+import org.apache.logging.log4j.perf.util.StackDriver;
+import org.openjdk.jmh.annotations.Benchmark;
+import org.openjdk.jmh.annotations.BenchmarkMode;
+import org.openjdk.jmh.annotations.Mode;
+import org.openjdk.jmh.annotations.OutputTimeUnit;
+import org.openjdk.jmh.annotations.Param;
+import org.openjdk.jmh.annotations.Scope;
+import org.openjdk.jmh.annotations.State;
+import org.openjdk.jmh.infra.Blackhole;
+
+import java.lang.StackWalker;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Function;
+import java.util.stream.Stream;
+
+/**
+ * Benchmark logging with logging disabled.
+ * // ============================== HOW TO RUN THIS TEST: ====================================
+ * //
+ * // single thread:
+ * // java -jar log4j-perf/target/benchmarks.jar ".*StackWalkBenchmark.*" -f 1 -wi 5 -i 10
+ * //
+ * // multiple threads (for example, 4 threads):
+ * // java -jar log4j-perf/target/benchmarks.jar ".*StackWalkBenchmark.*" -f 1 -wi 5 -i 10 -t 4 -si true
+ * //
+ * // Usage help:
+ * // java -jar log4j-perf/target/benchmarks.jar -help
+ * //
+ */
+@State(Scope.Benchmark)
+@BenchmarkMode(Mode.AverageTime)
+@OutputTimeUnit(TimeUnit.MICROSECONDS)
+public class StackWalkBenchmark {
+
+ private static final StackDriver stackDriver = new StackDriver();
+ private final static ThreadLocal<String> FQCN = new ThreadLocal<>();
+ private final static FqcnCallerLocator LOCATOR = new FqcnCallerLocator();
+ private final static StackWalker walker = StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE);
+
+ @Param({"10", "20", "50"})
+ private int initialDepth;
+
+ @Param({"5", "10", "20"})
+ private int callDepth;
+
+ @Benchmark
+ public void throwableSearch(Blackhole bh) {
+
+ stackDriver.deepCall(initialDepth, callDepth, (fqcn) -> {
+ final StackTraceElement[] stackTrace = new Throwable().getStackTrace();
+ boolean found = false;
+ for (int i = 0; i < stackTrace.length; i++) {
+ final String className = stackTrace[i].getClassName();
+ if (fqcn.equals(className)) {
+ found = true;
+ continue;
+ }
+ if (found && !fqcn.equals(className)) {
+ return stackTrace[i];
+ }
+ }
+ return null;
+ });
+ }
+
+ @Benchmark
+ public void stackWalkerWalk(Blackhole bh) {
+ stackDriver.deepCall(initialDepth, callDepth, (fqcn) -> {
+ return walker.walk(
+ s -> s.dropWhile(f -> !f.getClassName().equals(fqcn)) // drop the top frames until we reach the logger
+ .dropWhile(f -> f.getClassName().equals(fqcn)) // drop the logger frames
+ .findFirst())
+ .get()
+ .toStackTraceElement();
+ });
+ }
+
+ @Benchmark
+ public void baseline(Blackhole bh) {
+
+ stackDriver.deepCall(initialDepth, callDepth, (fqcn) -> {
+ return null;
+ });
+ }
+
+ @Benchmark
+ public void stackWalkerArray(Blackhole bh) {
+
+ stackDriver.deepCall(initialDepth, callDepth, (fqcn) -> {
+ FQCN.set(fqcn);
+ final StackWalker.StackFrame walk = walker.walk(LOCATOR);
+ final StackTraceElement element = walk == null ? null : walk.toStackTraceElement();
+ FQCN.set(null);
+ return element;
+ });
+ }
+
+ static final class FqcnCallerLocator implements Function<Stream<StackWalker.StackFrame>, StackWalker.StackFrame> {
+
+ @Override
+ public StackWalker.StackFrame apply(Stream<StackWalker.StackFrame> stackFrameStream) {
+ String fqcn = FQCN.get();
+ boolean foundFqcn = false;
+ Object[] frames = stackFrameStream.toArray();
+ for (int i = 0; i < frames.length ; ++i) {
+ final String className = ((StackWalker.StackFrame) frames[i]).getClassName();
+ if (!foundFqcn) {
+ // Skip frames until we find the FQCN
+ foundFqcn = className.equals(fqcn);
+ } else if (!className.equals(fqcn)) {
+ // The frame is no longer equal to the FQCN so it is the one we want.
+ return (StackWalker.StackFrame) frames[i];
+ } // Otherwise it is equal to the FQCN so we need to skip it.
+ }
+ // Should never happen
+ return null;
+ }
+ }
+
+}
diff --git a/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/TextEncoderHelperBenchmark.java b/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/TextEncoderHelperBenchmark.java
index 25854ec..ec4a26c 100644
--- a/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/TextEncoderHelperBenchmark.java
+++ b/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/TextEncoderHelperBenchmark.java
@@ -62,7 +62,19 @@
final static LogEvent EVENT = createLogEvent();
private static final Charset CHARSET_DEFAULT = Charset.defaultCharset();
- private final PatternLayout PATTERN_M_C_D = PatternLayout.createLayout("%d %c %m%n", null, null, null, CHARSET_DEFAULT, false, true, null, null);
+
+ private final PatternLayout PATTERN_M_C_D = PatternLayout.newBuilder()
+ .setPattern((String) "%d %c %m%n")
+ .setPatternSelector(null)
+ .setConfiguration(null)
+ .setRegexReplacement(null)
+ .setCharset((Charset) CHARSET_DEFAULT)
+ .setAlwaysWriteExceptions(false)
+ .setNoConsoleNoAnsi(true)
+ .setHeader(null)
+ .setFooter(null)
+ .build();
+
private final Destination destination = new Destination();
class Destination implements ByteBufferDestination {
diff --git a/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/ThreadContextBenchmark.java b/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/ThreadContextBenchmark.java
index ed30f58..ff22a47 100644
--- a/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/ThreadContextBenchmark.java
+++ b/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/ThreadContextBenchmark.java
@@ -198,4 +198,4 @@
}
return Collections.unmodifiableMap(map);
}
-}
\ No newline at end of file
+}
diff --git a/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/ThreadLocalVsConcurrentHashMapBenchmark.java b/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/ThreadLocalVsConcurrentHashMapBenchmark.java
index 9755f5a..0941e72 100644
--- a/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/ThreadLocalVsConcurrentHashMapBenchmark.java
+++ b/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/ThreadLocalVsConcurrentHashMapBenchmark.java
@@ -91,4 +91,4 @@
buf.setLength(0);
return buf;
}
-}
\ No newline at end of file
+}
diff --git a/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/ThreadLocalVsPoolBenchmark.java b/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/ThreadLocalVsPoolBenchmark.java
index beb1456..2996dec 100644
--- a/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/ThreadLocalVsPoolBenchmark.java
+++ b/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/ThreadLocalVsPoolBenchmark.java
@@ -17,14 +17,7 @@
package org.apache.logging.log4j.perf.jmh;
-import java.nio.charset.Charset;
-import java.util.Deque;
-import java.util.List;
-import java.util.concurrent.ConcurrentLinkedDeque;
-
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.config.Configuration;
import org.apache.logging.log4j.core.config.DefaultConfiguration;
@@ -34,14 +27,16 @@
import org.apache.logging.log4j.core.pattern.PatternParser;
import org.apache.logging.log4j.message.Message;
import org.apache.logging.log4j.message.SimpleMessage;
-import org.apache.logging.log4j.util.StringMap;
+import org.jctools.queues.MpmcArrayQueue;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.State;
+import java.util.List;
+
/**
- * Checks PatternLayout performance when reusing the StringBuilder in a ThreadLocal, an ObjectPool or when creating a
- * new instance for each log event.
+ * Checks {@link PatternFormatter} performance with various StringBuilder
+ * caching strategies: no-op, ThreadLocal, and JCTools MPMC queue.
*/
// ============================== HOW TO RUN THIS TEST: ====================================
// (Quick build: mvn -DskipTests=true clean package -pl log4j-perf -am )
@@ -58,160 +53,159 @@
@State(Scope.Benchmark)
public class ThreadLocalVsPoolBenchmark {
- static final Charset CHARSET_DEFAULT = Charset.defaultCharset();
- static final String LOG4JPATTERN = "%d %5p [%t] %c{1} %X{transactionId} - %m%n";
- static final int DEFAULT_STRING_BUILDER_SIZE = 1024;
+ private static final LogEvent LOG_EVENT = createLogEvent();
- /**
- * The LogEvent to serialize.
- */
- private final static LogEvent LOG4J2EVENT = createLog4j2Event();
+ private static final List<PatternFormatter> FORMATTERS = createFormatters();
- /**
- * Initial converter for pattern.
- */
- private final static PatternFormatter[] formatters = createFormatters();
-
- private final StringBuilderPool pool = new StringBuilderPool(DEFAULT_STRING_BUILDER_SIZE);
- private static ThreadLocal<StringBuilder> threadLocal = new ThreadLocal<>();
-
- /**
- */
- private static PatternFormatter[] createFormatters() {
- final Configuration config = new DefaultConfiguration();
- final PatternParser parser = new PatternParser(config, "Converter", LogEventPatternConverter.class);
- final List<PatternFormatter> result = parser.parse(LOG4JPATTERN, false, true);
- return result.toArray(new PatternFormatter[result.size()]);
- }
-
- @Benchmark
- public byte[] newInstance() {
- return serializeWithNewInstance(LOG4J2EVENT).getBytes(CHARSET_DEFAULT);
- }
-
- @Benchmark
- public byte[] threadLocal() {
- return serializeWithThreadLocal(LOG4J2EVENT).getBytes(CHARSET_DEFAULT);
- }
-
- @Benchmark
- public byte[] objectPool() {
- return serializeWithPool(LOG4J2EVENT).getBytes(CHARSET_DEFAULT);
- }
-
- @Benchmark
- public String _stringNewInstance() {
- return serializeWithNewInstance(LOG4J2EVENT);
- }
-
- @Benchmark
- public String _stringThreadLocal() {
- return serializeWithThreadLocal(LOG4J2EVENT);
- }
-
- @Benchmark
- public String _stringObjectPool() {
- return serializeWithPool(LOG4J2EVENT);
- }
-
- private String serializeWithNewInstance(final LogEvent event) {
- final StringBuilder buf = new StringBuilder(DEFAULT_STRING_BUILDER_SIZE);
- return serialize(event, buf);
- }
-
- private String serializeWithThreadLocal(final LogEvent event) {
- StringBuilder buf = threadLocal.get();
- if (buf == null) {
- buf = new StringBuilder(DEFAULT_STRING_BUILDER_SIZE);
- threadLocal.set(buf);
- }
- buf.setLength(0);
- return serialize(event, buf);
- }
-
- private String serializeWithPool(final LogEvent event) {
- final StringBuilder buf = pool.borrowObject();
- try {
- buf.setLength(0);
- return serialize(event, buf);
- } finally {
- pool.returnObject(buf);
- }
- }
-
- private String serialize(final LogEvent event, final StringBuilder buf) {
- final int len = formatters.length;
- for (int i = 0; i < len; i++) {
- formatters[i].format(event, buf);
- }
- return buf.toString();
- }
-
- private static LogEvent createLog4j2Event() {
- final Marker marker = null;
- final String fqcn = "com.mycom.myproject.mypackage.MyClass";
+ private static LogEvent createLogEvent() {
+ final String loggerName = "name(ignored)";
+ final String loggerFqcn = "com.mycom.myproject.mypackage.MyClass";
final Level level = Level.DEBUG;
- final String STR = "AB!(%087936DZYXQWEIOP$#^~-=/><nb"; // length=32
- final Message message = new SimpleMessage(STR);
- final Throwable t = null;
- final StringMap mdc = null;
- final ContextStack ndc = null;
- final String threadName = null;
- final StackTraceElement location = null;
+ final String messageString = "AB!(%087936DZYXQWEIOP$#^~-=/><nb"; // length=32
+ final Message message = new SimpleMessage(messageString);
final long timestamp = 12345678;
-
- return Log4jLogEvent.newBuilder() //
- .setLoggerName("name(ignored)") //
- .setMarker(marker) //
- .setLoggerFqcn(fqcn) //
- .setLevel(level) //
- .setMessage(message) //
- .setThrown(t) //
- .setContextData(mdc) //
- .setContextStack(ndc) //
- .setThreadName(threadName) //
- .setSource(location) //
- .setTimeMillis(timestamp) //
+ return Log4jLogEvent
+ .newBuilder()
+ .setLoggerName(loggerName)
+ .setLoggerFqcn(loggerFqcn)
+ .setLevel(level)
+ .setMessage(message)
+ .setTimeMillis(timestamp)
.build();
}
-}
-/**
- *
- */
-abstract class ObjectPool<T> {
- private final Deque<T> pool = new ConcurrentLinkedDeque<>();
-
- public T borrowObject() {
- final T object = pool.poll();
- return object == null ? createObject() : object;
+ private static List<PatternFormatter> createFormatters() {
+ final Configuration config = new DefaultConfiguration();
+ final PatternParser parser = new PatternParser(config, "Converter", LogEventPatternConverter.class);
+ return parser.parse("%d %5p [%t] %c{1} %X{transactionId} - %m%n", false, true);
}
- public void returnObject(final T object) {
- pool.add(object);
+ private static abstract class StringBuilderPool {
+
+ abstract StringBuilder acquire();
+
+ abstract void release(StringBuilder stringBuilder);
+
+ StringBuilder createStringBuilder() {
+ return new StringBuilder(1024 * 32);
+ }
+
}
- protected abstract T createObject();
-}
+ private static final class AllocatePool extends StringBuilderPool {
-/**
- *
- */
-class StringBuilderPool extends ObjectPool<StringBuilder> {
- private final int initialSize;
+ private static final AllocatePool INSTANCE = new AllocatePool();
- public StringBuilderPool(final int stringBuilderSize) {
- this.initialSize = stringBuilderSize;
+ @Override
+ public StringBuilder acquire() {
+ return createStringBuilder();
+ }
+
+ @Override
+ public void release(final StringBuilder stringBuilder) {}
+
}
- @Override
- public void returnObject(final StringBuilder stringBuilder) {
- stringBuilder.setLength(0);
- super.returnObject(stringBuilder);
+ private static final class ThreadLocalPool extends StringBuilderPool {
+
+ private static final ThreadLocalPool INSTANCE = new ThreadLocalPool();
+
+ private final ThreadLocal<StringBuilder> stringBuilderRef =
+ ThreadLocal.withInitial(this::createStringBuilder);
+
+ @Override
+ public StringBuilder acquire() {
+ return stringBuilderRef.get();
+ }
+
+ @Override
+ public void release(final StringBuilder stringBuilder) {
+ stringBuilder.setLength(0);
+ }
+
}
- @Override
- protected StringBuilder createObject() {
- return new StringBuilder(initialSize);
+ private static final class JcPool extends StringBuilderPool {
+
+ private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
+
+ private static final int MPMC_REQUIRED_MIN_CAPACITY = 2;
+
+ // Putting the under-provisioned instance to a wrapper class to prevent
+ // the initialization of JcPool itself when there are insufficient CPU
+ // cores.
+ private enum UnderProvisionedInstanceHolder {;
+
+ private static final JcPool INSTANCE = createInstance();
+
+ private static JcPool createInstance() {
+ if (CPU_COUNT <= MPMC_REQUIRED_MIN_CAPACITY) {
+ throw new IllegalArgumentException("insufficient CPU cores");
+ }
+ return new JcPool(MPMC_REQUIRED_MIN_CAPACITY);
+ }
+
+ }
+
+ private static final JcPool RIGHT_PROVISIONED_INSTANCE =
+ new JcPool(Math.max(MPMC_REQUIRED_MIN_CAPACITY, CPU_COUNT));
+
+ private final MpmcArrayQueue<StringBuilder> stringBuilders;
+
+ private JcPool(final int capacity) {
+ this.stringBuilders = new MpmcArrayQueue<>(capacity);
+ }
+
+ @Override
+ public StringBuilder acquire() {
+ final StringBuilder stringBuilder = stringBuilders.poll();
+ return stringBuilder != null
+ ? stringBuilder
+ : createStringBuilder();
+ }
+
+ @Override
+ public void release(final StringBuilder stringBuilder) {
+ stringBuilder.setLength(0);
+ stringBuilders.offer(stringBuilder);
+ }
+
}
+
+ @Benchmark
+ public int allocate() {
+ return findSerializedLength(AllocatePool.INSTANCE);
+ }
+
+ @Benchmark
+ public int threadLocal() {
+ return findSerializedLength(ThreadLocalPool.INSTANCE);
+ }
+
+ @Benchmark
+ public int rightProvedJc() {
+ return findSerializedLength(JcPool.RIGHT_PROVISIONED_INSTANCE);
+ }
+
+ @Benchmark
+ public int underProvedJc() {
+ return findSerializedLength(JcPool.UnderProvisionedInstanceHolder.INSTANCE);
+ }
+
+ private int findSerializedLength(final StringBuilderPool pool) {
+ final StringBuilder stringBuilder = pool.acquire();
+ serialize(stringBuilder);
+ final int length = stringBuilder.length();
+ pool.release(stringBuilder);
+ return length;
+ }
+
+ private void serialize(final StringBuilder stringBuilder) {
+ // noinspection ForLoopReplaceableByForEach (avoid iterator instantiation)
+ for (int formatterIndex = 0; formatterIndex < FORMATTERS.size(); formatterIndex++) {
+ PatternFormatter formatter = FORMATTERS.get(formatterIndex);
+ formatter.format(LOG_EVENT, stringBuilder);
+ }
+ }
+
}
diff --git a/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/ThreadsafeDateFormatBenchmark.java b/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/ThreadsafeDateFormatBenchmark.java
index 59ac56f..98af16b 100644
--- a/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/ThreadsafeDateFormatBenchmark.java
+++ b/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/ThreadsafeDateFormatBenchmark.java
@@ -18,6 +18,8 @@
package org.apache.logging.log4j.perf.jmh;
import java.text.SimpleDateFormat;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
import java.util.Date;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
@@ -49,6 +51,7 @@
public class ThreadsafeDateFormatBenchmark {
private final Date date = new Date();
+ private final DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("HH:mm:ss.SSS");
private final SimpleDateFormat simpleDateFormat = new SimpleDateFormat("HH:mm:ss.SSS");
private final ThreadLocal<SimpleDateFormat> threadLocalSDFormat = new ThreadLocal<SimpleDateFormat>() {
@Override
@@ -163,6 +166,14 @@
@Benchmark
@BenchmarkMode(Mode.SampleTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
+ public String dateTimeFormatter() {
+ final LocalDateTime now = LocalDateTime.now();
+ return dateTimeFormatter.format(now);
+ }
+
+ @Benchmark
+ @BenchmarkMode(Mode.SampleTime)
+ @OutputTimeUnit(TimeUnit.NANOSECONDS)
public String threadLocalSimpleDateFmt() {
final long timestamp = System.currentTimeMillis();
return threadLocalSDFormat.get().format(timestamp);
diff --git a/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/TimeFormatBenchmark.java b/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/TimeFormatBenchmark.java
index fd462c2..b82547c 100644
--- a/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/TimeFormatBenchmark.java
+++ b/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/TimeFormatBenchmark.java
@@ -57,8 +57,8 @@
};
FastDateFormat fastDateFormat = FastDateFormat.getInstance("HH:mm:ss.SSS");
FixedDateFormat fixedDateFormat = FixedDateFormat.createIfSupported(new String[]{"ABSOLUTE"});
- volatile long midnightToday = 0;
- volatile long midnightTomorrow = 0;
+ volatile long midnightToday;
+ volatile long midnightTomorrow;
@State(Scope.Thread)
public static class BufferState {
diff --git a/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/jpa/TestBasicEntity.java b/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/jpa/TestBasicEntity.java
index eb55925..ee41f60 100644
--- a/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/jpa/TestBasicEntity.java
+++ b/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/jpa/TestBasicEntity.java
@@ -57,10 +57,4 @@
this.id = id;
}
- @Override
- @Convert(converter = ContextMapJsonAttributeConverter.class)
- @Column(name = "contextMapJson")
- public Map<String, String> getContextMap() {
- return super.getContextMap();
- }
}
diff --git a/log4j-perf/src/main/java/org/apache/logging/log4j/perf/nogc/ClassicLogger.java b/log4j-perf/src/main/java/org/apache/logging/log4j/perf/nogc/ClassicLogger.java
index 97fd24e..a05cfdd 100644
--- a/log4j-perf/src/main/java/org/apache/logging/log4j/perf/nogc/ClassicLogger.java
+++ b/log4j-perf/src/main/java/org/apache/logging/log4j/perf/nogc/ClassicLogger.java
@@ -29,7 +29,7 @@
@Override
protected StringLayout createLayout() {
- return PatternLayout.newBuilder().withCharset(StandardCharsets.UTF_8).withPattern("%m").build();
+ return PatternLayout.newBuilder().setCharset(StandardCharsets.UTF_8).setPattern("%m").build();
}
public void log(final String message, final Object... params) {
diff --git a/log4j-perf/src/main/java/org/apache/logging/log4j/perf/nogc/DemoAppender.java b/log4j-perf/src/main/java/org/apache/logging/log4j/perf/nogc/DemoAppender.java
index a4ff97f..f59889d 100644
--- a/log4j-perf/src/main/java/org/apache/logging/log4j/perf/nogc/DemoAppender.java
+++ b/log4j-perf/src/main/java/org/apache/logging/log4j/perf/nogc/DemoAppender.java
@@ -21,6 +21,7 @@
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.layout.ByteBufferDestination;
import org.apache.logging.log4j.core.layout.ByteBufferDestinationHelper;
@@ -33,7 +34,7 @@
public long checksum;
public DemoAppender(final Layout<?> layout) {
- super("demo", null, layout);
+ super("demo", null, layout, true, Property.EMPTY_ARRAY);
}
@Override
diff --git a/log4j-perf/src/main/java/org/apache/logging/log4j/perf/nogc/NoGcLayout.java b/log4j-perf/src/main/java/org/apache/logging/log4j/perf/nogc/NoGcLayout.java
index 81fa861..693b8df 100644
--- a/log4j-perf/src/main/java/org/apache/logging/log4j/perf/nogc/NoGcLayout.java
+++ b/log4j-perf/src/main/java/org/apache/logging/log4j/perf/nogc/NoGcLayout.java
@@ -131,4 +131,4 @@
public Map<String, String> getContentFormat() {
return null;
}
-}
\ No newline at end of file
+}
diff --git a/log4j-perf/src/main/java/org/apache/logging/log4j/perf/util/DemoAppender.java b/log4j-perf/src/main/java/org/apache/logging/log4j/perf/util/DemoAppender.java
index 3a6da07..aa2df05 100644
--- a/log4j-perf/src/main/java/org/apache/logging/log4j/perf/util/DemoAppender.java
+++ b/log4j-perf/src/main/java/org/apache/logging/log4j/perf/util/DemoAppender.java
@@ -19,6 +19,7 @@
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.layout.ByteBufferDestination;
import org.apache.logging.log4j.core.layout.ByteBufferDestinationHelper;
import org.apache.logging.log4j.core.util.Constants;
@@ -34,7 +35,7 @@
public long checksum;
public DemoAppender(final Layout<?> layout) {
- super("demo", null, layout);
+ super("demo", null, layout, true, Property.EMPTY_ARRAY);
}
@Override
diff --git a/log4j-perf/src/main/java/org/apache/logging/log4j/perf/util/StackDriver.java b/log4j-perf/src/main/java/org/apache/logging/log4j/perf/util/StackDriver.java
new file mode 100644
index 0000000..9f239e4
--- /dev/null
+++ b/log4j-perf/src/main/java/org/apache/logging/log4j/perf/util/StackDriver.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.perf.util;
+
+import java.util.function.BiFunction;
+import java.util.function.Function;
+
+/**
+ * Facilitates creating a Call Stack for testing the performance of walkign it.
+ */
+public class StackDriver {
+ public StackTraceElement deepCall(int initialDepth, Integer targetDepth, Function<String, StackTraceElement> supplier) {
+ if (--initialDepth == 0) {
+ Processor processor = new Processor();
+ return processor.apply(targetDepth, supplier);
+ } else {
+ return deepCall(initialDepth, targetDepth, supplier);
+ }
+ }
+
+ public static class Processor implements BiFunction<Integer, Function<String, StackTraceElement>, StackTraceElement> {
+ private static final String FQCN = Processor.class.getName();
+
+ @Override
+ public StackTraceElement apply(Integer depth, Function<String, StackTraceElement> function) {
+ if (--depth == 0) {
+ return function.apply(FQCN);
+ } else {
+ return apply(depth, function);
+ }
+ }
+ }
+}
diff --git a/log4j-perf/src/main/resources/ConcurrentAsyncLoggerToFileBenchmark-asyncConfig.xml b/log4j-perf/src/main/resources/ConcurrentAsyncLoggerToFileBenchmark-asyncConfig.xml
new file mode 100644
index 0000000..36e309e
--- /dev/null
+++ b/log4j-perf/src/main/resources/ConcurrentAsyncLoggerToFileBenchmark-asyncConfig.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-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 "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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">
+ <Appenders>
+ <RandomAccessFile name="RandomAccessFile" fileName="target/ConcurrentAsyncLoggerToFileBenchmark.log" immediateFlush="false">
+ <PatternLayout pattern="%d %p [%t] %c{1} %X{transactionId} - %m%n"/>
+ </RandomAccessFile>
+ </Appenders>
+ <Loggers>
+ <AsyncRoot level="info" includeLocation="false">
+ <appender-ref ref="RandomAccessFile"/>
+ </AsyncRoot>
+ </Loggers>
+</Configuration>
diff --git a/log4j-perf/src/main/resources/ConcurrentAsyncLoggerToFileBenchmark.xml b/log4j-perf/src/main/resources/ConcurrentAsyncLoggerToFileBenchmark.xml
new file mode 100644
index 0000000..3b0e043
--- /dev/null
+++ b/log4j-perf/src/main/resources/ConcurrentAsyncLoggerToFileBenchmark.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-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 "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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">
+ <Appenders>
+ <RandomAccessFile name="RandomAccessFile" fileName="target/ConcurrentAsyncLoggerToFileBenchmark.log" immediateFlush="false">
+ <PatternLayout pattern="%d %p [%t] %c{1} %X{transactionId} - %m%n"/>
+ </RandomAccessFile>
+ </Appenders>
+ <Loggers>
+ <Root level="info" includeLocation="false">
+ <appender-ref ref="RandomAccessFile"/>
+ </Root>
+ </Loggers>
+</Configuration>
diff --git a/log4j-perf/src/main/resources/log4j2-perf-file-throwable.xml b/log4j-perf/src/main/resources/log4j2-perf-file-throwable.xml
index 3a4d9a4..c55b2ae 100644
--- a/log4j-perf/src/main/resources/log4j2-perf-file-throwable.xml
+++ b/log4j-perf/src/main/resources/log4j2-perf-file-throwable.xml
@@ -33,8 +33,14 @@
<Logger name="RAFExtendedException" level="debug" additivity="false">
<AppenderRef ref="RAFExtendedException"/>
</Logger>
+ <AsyncLogger name="async.RAFExtendedException" level="debug" additivity="false">
+ <AppenderRef ref="RAFExtendedException"/>
+ </AsyncLogger>
<Logger name="RAFSimpleException" level="debug" additivity="false">
<AppenderRef ref="RAFSimpleException"/>
</Logger>
+ <AsyncLogger name="async.RAFSimpleException" level="debug" additivity="false">
+ <AppenderRef ref="RAFSimpleException"/>
+ </AsyncLogger>
</Loggers>
</Configuration>
\ No newline at end of file
diff --git a/log4j-perf/src/main/resources/log4j2-perf3.xml b/log4j-perf/src/main/resources/log4j2-perf3.xml
new file mode 100644
index 0000000..1b977e7
--- /dev/null
+++ b/log4j-perf/src/main/resources/log4j2-perf3.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-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 "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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="XMLPerfTest" status="OFF">
+ <Appenders>
+ <File name="TestLogfile" fileName="target/testlog4j2.log" immediateFlush="false">
+ <PatternLayout>
+ <Pattern>%d %5p [%t] %c{1} %X{transactionId} - %m%n</Pattern>
+ </PatternLayout>
+ </File>
+ </Appenders>
+ <Loggers>
+ <Root level="debug">
+ <AppenderRef ref="TestLogfile"/>
+ </Root>
+ </Loggers>
+</Configuration>
diff --git a/log4j-plugins-java9/pom.xml b/log4j-plugins-java9/pom.xml
new file mode 100644
index 0000000..f27ec94
--- /dev/null
+++ b/log4j-plugins-java9/pom.xml
@@ -0,0 +1,157 @@
+<?xml version="1.0" encoding="UTF-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 "License"); you may not use this file except in compliance with
+ ~ the License. You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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 xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j</artifactId>
+ <version>3.0.0-SNAPSHOT</version>
+ <relativePath>../</relativePath>
+ </parent>
+ <artifactId>log4j-plugins-java9</artifactId>
+ <packaging>pom</packaging>
+ <name>Apache Log4j Plugins Module support</name>
+ <description>Apache Log4j Plugin Modules Support</description>
+ <properties>
+ <log4jParentDir>${basedir}/..</log4jParentDir>
+ <docLabel>Log4j Plugins Documentation</docLabel>
+ <projectDir>/plugins</projectDir>
+ </properties>
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.maven</groupId>
+ <artifactId>maven-core</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-toolchains-plugin</artifactId>
+ <version>1.1</version>
+ <executions>
+ <execution>
+ <goals>
+ <goal>toolchain</goal>
+ </goals>
+ </execution>
+ </executions>
+ <configuration>
+ <toolchains>
+ <jdk>
+ <version>[11, )</version>
+ </jdk>
+ </toolchains>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>default-compile</id>
+ <phase>compile</phase>
+ <goals>
+ <goal>compile</goal>
+ </goals>
+ </execution>
+ <execution>
+ <id>default-test-compile</id>
+ <phase>test-compile</phase>
+ <goals>
+ <goal>testCompile</goal>
+ </goals>
+ </execution>
+ </executions>
+ <configuration>
+ <source>9</source>
+ <target>9</target>
+ <release>9</release>
+ <proc>none</proc>
+ <!-- disable errorprone -->
+ <compilerId>javac</compilerId>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <!-- Do not upgrade until https://issues.apache.org/jira/browse/SUREFIRE-720 is fixed -->
+ <version>2.13</version>
+ <executions>
+ <execution>
+ <id>test</id>
+ <phase>test</phase>
+ <goals>
+ <goal>test</goal>
+ </goals>
+ </execution>
+ </executions>
+ <configuration>
+ <systemPropertyVariables>
+ <java.awt.headless>true</java.awt.headless>
+ </systemPropertyVariables>
+ <includes>
+ <include>**/Test*.java</include>
+ <include>**/*Test.java</include>
+ </includes>
+ <excludes>
+ <exclude>**/*FuncTest.java</exclude>
+ </excludes>
+ </configuration>
+ </plugin>
+ <plugin>
+ <artifactId>maven-assembly-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>zip</id>
+ <phase>package</phase>
+ <goals>
+ <goal>single</goal>
+ </goals>
+ <configuration>
+ <finalName>log4j-plugins-java9-${project.version}</finalName>
+ <appendAssemblyId>false</appendAssemblyId>
+ <descriptors>
+ <descriptor>src/assembly/java9.xml</descriptor>
+ </descriptors>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-deploy-plugin</artifactId>
+ <version>${deploy.plugin.version}</version>
+ <configuration>
+ <skip>true</skip>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+</project>
+
diff --git a/log4j-plugins-java9/src/assembly/java9.xml b/log4j-plugins-java9/src/assembly/java9.xml
new file mode 100644
index 0000000..249fa86
--- /dev/null
+++ b/log4j-plugins-java9/src/assembly/java9.xml
@@ -0,0 +1,41 @@
+<?xml version='1.0' encoding='UTF-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
+ "License"); you may not use this file except in compliance
+ with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing,
+ software distributed under the License is distributed on an
+ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ KIND, either express or implied. See the License for the
+ specific language governing permissions and limitations
+ under the License.
+-->
+
+<assembly>
+ <id>src</id>
+ <formats>
+ <format>zip</format>
+ </formats>
+ <baseDirectory>/</baseDirectory>
+ <fileSets>
+ <fileSet>
+ <directory>${project.build.outputDirectory}</directory>
+ <outputDirectory>/classes/META-INF/versions/9</outputDirectory>
+ <includes>
+ <include>module-info.class</include>
+ </includes>
+ <excludes>
+ <exclude>**/Dummy.class</exclude>
+ <exclude>**/PluginService.class</exclude>
+ <exclude>**/Log4jPlugins.class</exclude>
+ </excludes>
+ </fileSet>
+ </fileSets>
+</assembly>
diff --git a/log4j-plugins-java9/src/main/java/module-info.java b/log4j-plugins-java9/src/main/java/module-info.java
new file mode 100644
index 0000000..896d3e1
--- /dev/null
+++ b/log4j-plugins-java9/src/main/java/module-info.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.
+ */
+module org.apache.logging.log4j.plugins {
+ exports org.apache.logging.log4j.plugins;
+ exports org.apache.logging.log4j.plugins.convert;
+ exports org.apache.logging.log4j.plugins.processor;
+ exports org.apache.logging.log4j.plugins.util;
+ exports org.apache.logging.log4j.plugins.validation;
+ exports org.apache.logging.log4j.plugins.validation.constraints;
+ exports org.apache.logging.log4j.plugins.validation.validators;
+ exports org.apache.logging.log4j.plugins.bind;
+ exports org.apache.logging.log4j.plugins.inject;
+ exports org.apache.logging.log4j.plugins.name;
+
+ requires org.apache.logging.log4j;
+
+ provides org.apache.logging.log4j.plugins.processor.PluginService with org.apache.logging.log4j.plugins.convert.plugins.Log4jPlugins;
+
+ uses org.apache.logging.log4j.plugins.processor.PluginService;
+}
diff --git a/log4j-plugins-java9/src/main/java/org/apache/logging/log4j/plugins/Dummy.java b/log4j-plugins-java9/src/main/java/org/apache/logging/log4j/plugins/Dummy.java
new file mode 100644
index 0000000..14a90ed
--- /dev/null
+++ b/log4j-plugins-java9/src/main/java/org/apache/logging/log4j/plugins/Dummy.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.plugins;
+
+/**
+ * This is a dummy class and is only here to allow module-info.java to compile. It will not
+ * be copied into the log4j-api module.
+ */
+public class Dummy {
+}
diff --git a/log4j-plugins-java9/src/main/java/org/apache/logging/log4j/plugins/bind/Dummy.java b/log4j-plugins-java9/src/main/java/org/apache/logging/log4j/plugins/bind/Dummy.java
new file mode 100644
index 0000000..98f00c7
--- /dev/null
+++ b/log4j-plugins-java9/src/main/java/org/apache/logging/log4j/plugins/bind/Dummy.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.plugins.bind;
+
+/**
+ * This is a dummy class and is only here to allow module-info.java to compile. It will not
+ * be copied into the log4j-plugins module.
+ */
+public class Dummy {
+}
diff --git a/log4j-plugins-java9/src/main/java/org/apache/logging/log4j/plugins/convert/Dummy.java b/log4j-plugins-java9/src/main/java/org/apache/logging/log4j/plugins/convert/Dummy.java
new file mode 100644
index 0000000..10923e8
--- /dev/null
+++ b/log4j-plugins-java9/src/main/java/org/apache/logging/log4j/plugins/convert/Dummy.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.plugins.convert;
+
+/**
+ * This is a dummy class and is only here to allow module-info.java to compile. It will not
+ * be copied into the log4j-api module.
+ */
+public class Dummy {
+}
diff --git a/log4j-plugins-java9/src/main/java/org/apache/logging/log4j/plugins/convert/plugins/Log4jPlugins.java b/log4j-plugins-java9/src/main/java/org/apache/logging/log4j/plugins/convert/plugins/Log4jPlugins.java
new file mode 100644
index 0000000..6cdd225
--- /dev/null
+++ b/log4j-plugins-java9/src/main/java/org/apache/logging/log4j/plugins/convert/plugins/Log4jPlugins.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.plugins.convert.plugins;
+
+import org.apache.logging.log4j.plugins.processor.PluginService;
+
+/**
+ * Class Description goes here.
+ */
+public class Log4jPlugins extends PluginService {
+}
diff --git a/log4j-plugins-java9/src/main/java/org/apache/logging/log4j/plugins/inject/Dummy.java b/log4j-plugins-java9/src/main/java/org/apache/logging/log4j/plugins/inject/Dummy.java
new file mode 100644
index 0000000..68f4aa2
--- /dev/null
+++ b/log4j-plugins-java9/src/main/java/org/apache/logging/log4j/plugins/inject/Dummy.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.plugins.inject;
+
+/**
+ * This is a dummy class and is only here to allow module-info.java to compile. It will not
+ * be copied into the log4j-plugins module.
+ */
+public class Dummy {
+}
diff --git a/log4j-plugins-java9/src/main/java/org/apache/logging/log4j/plugins/name/Dummy.java b/log4j-plugins-java9/src/main/java/org/apache/logging/log4j/plugins/name/Dummy.java
new file mode 100644
index 0000000..853dc00
--- /dev/null
+++ b/log4j-plugins-java9/src/main/java/org/apache/logging/log4j/plugins/name/Dummy.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.plugins.name;
+
+/**
+ * This is a dummy class and is only here to allow module-info.java to compile. It will not
+ * be copied into the log4j-plugins module.
+ */
+public class Dummy {
+}
diff --git a/log4j-plugins-java9/src/main/java/org/apache/logging/log4j/plugins/processor/PluginService.java b/log4j-plugins-java9/src/main/java/org/apache/logging/log4j/plugins/processor/PluginService.java
new file mode 100644
index 0000000..b93ef59
--- /dev/null
+++ b/log4j-plugins-java9/src/main/java/org/apache/logging/log4j/plugins/processor/PluginService.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.plugins.processor;
+
+/**
+ * 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 PluginService {
+}
diff --git a/log4j-plugins-java9/src/main/java/org/apache/logging/log4j/plugins/util/Dummy.java b/log4j-plugins-java9/src/main/java/org/apache/logging/log4j/plugins/util/Dummy.java
new file mode 100644
index 0000000..5940b03
--- /dev/null
+++ b/log4j-plugins-java9/src/main/java/org/apache/logging/log4j/plugins/util/Dummy.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.plugins.util;
+
+/**
+ * This is a dummy class and is only here to allow module-info.java to compile. It will not
+ * be copied into the log4j-api module.
+ */
+public class Dummy {
+}
diff --git a/log4j-plugins-java9/src/main/java/org/apache/logging/log4j/plugins/validation/Dummy.java b/log4j-plugins-java9/src/main/java/org/apache/logging/log4j/plugins/validation/Dummy.java
new file mode 100644
index 0000000..14882b5
--- /dev/null
+++ b/log4j-plugins-java9/src/main/java/org/apache/logging/log4j/plugins/validation/Dummy.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.plugins.validation;
+
+/**
+ * This is a dummy class and is only here to allow module-info.java to compile. It will not
+ * be copied into the log4j-api module.
+ */
+public class Dummy {
+}
diff --git a/log4j-plugins-java9/src/main/java/org/apache/logging/log4j/plugins/validation/constraints/Dummy.java b/log4j-plugins-java9/src/main/java/org/apache/logging/log4j/plugins/validation/constraints/Dummy.java
new file mode 100644
index 0000000..9810d5e
--- /dev/null
+++ b/log4j-plugins-java9/src/main/java/org/apache/logging/log4j/plugins/validation/constraints/Dummy.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.plugins.validation.constraints;
+
+/**
+ * This is a dummy class and is only here to allow module-info.java to compile. It will not
+ * be copied into the log4j-api module.
+ */
+public class Dummy {
+}
diff --git a/log4j-plugins-java9/src/main/java/org/apache/logging/log4j/plugins/validation/validators/Dummy.java b/log4j-plugins-java9/src/main/java/org/apache/logging/log4j/plugins/validation/validators/Dummy.java
new file mode 100644
index 0000000..da73052
--- /dev/null
+++ b/log4j-plugins-java9/src/main/java/org/apache/logging/log4j/plugins/validation/validators/Dummy.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.plugins.validation.validators;
+
+/**
+ * This is a dummy class and is only here to allow module-info.java to compile. It will not
+ * be copied into the log4j-api module.
+ */
+public class Dummy {
+}
diff --git a/log4j-plugins/pom.xml b/log4j-plugins/pom.xml
new file mode 100644
index 0000000..d349a5a
--- /dev/null
+++ b/log4j-plugins/pom.xml
@@ -0,0 +1,376 @@
+<?xml version="1.0" encoding="UTF-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 "License"); you may not use this file except in compliance with
+ ~ the License. You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT 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 xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j</artifactId>
+ <version>3.0.0-SNAPSHOT</version>
+ <relativePath>../</relativePath>
+ </parent>
+ <artifactId>log4j-plugins</artifactId>
+ <packaging>jar</packaging>
+ <name>Apache Log4j Plugins</name>
+ <description>Log4j Plugin Support</description>
+ <properties>
+ <log4jParentDir>${basedir}/..</log4jParentDir>
+ <docLabel>Plugin Documentation</docLabel>
+ <projectDir>/plugins</projectDir>
+ </properties>
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-api</artifactId>
+ </dependency>
+ <!-- Classes and resources to be shaded into the core jar -->
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-plugins-java9</artifactId>
+ <scope>provided</scope>
+ <type>zip</type>
+ </dependency>
+ <!-- Used for OSGi bundle support -->
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.core</artifactId>
+ <scope>provided</scope>
+ </dependency>
+
+ <!-- TEST DEPENDENCIES -->
+
+ <!-- Pull in useful test classes from API -->
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-api</artifactId>
+ <type>test-jar</type>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.hamcrest</groupId>
+ <artifactId>hamcrest-all</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-dependency-plugin</artifactId>
+ <version>3.0.2</version>
+ <executions>
+ <execution>
+ <id>unpack-classes</id>
+ <phase>prepare-package</phase>
+ <goals>
+ <goal>unpack</goal>
+ </goals>
+ <configuration>
+ <artifactItems>
+ <artifactItem>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-plugins-java9</artifactId>
+ <version>${project.version}</version>
+ <type>zip</type>
+ <overWrite>false</overWrite>
+ </artifactItem>
+ </artifactItems>
+ <includes>**/*.class</includes>
+ <excludes>**/*.java</excludes>
+ <outputDirectory>${project.build.directory}</outputDirectory>
+ <overWriteReleases>false</overWriteReleases>
+ <overWriteSnapshots>true</overWriteSnapshots>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <executions>
+ <execution>
+ <!-- disable annotation processing for first pass -->
+ <id>generate-plugins</id>
+ <phase>process-classes</phase>
+ <goals>
+ <goal>compile</goal>
+ </goals>
+ <configuration>
+ <excludes>
+ <exclude>module-info.java</exclude>
+ </excludes>
+ </configuration>
+ </execution>
+ <execution>
+ <!-- then do a processing-only pass to generate plugins -->
+ <id>process-plugins</id>
+ <goals>
+ <goal>compile</goal>
+ </goals>
+ <phase>process-classes</phase>
+ <configuration>
+ <excludes>
+ <exclude>module-info.java</exclude>
+ </excludes>
+ <proc>only</proc>
+ </configuration>
+ </execution>
+ <execution>
+ <!-- disable annotation processing for first pass -->
+ <id>generate-test-plugins</id>
+ <phase>generate-test-sources</phase>
+ <goals>
+ <goal>compile</goal>
+ </goals>
+ <configuration>
+ <excludes>
+ <exclude>module-info.java</exclude>
+ </excludes>
+ <proc>only</proc>
+ </configuration>
+ </execution>
+ <execution>
+ <!-- disable annotation processing for first pass -->
+ <id>default-compile</id>
+ <configuration>
+ <excludes>
+ <exclude>module-info.java</exclude>
+ </excludes>
+ <proc>none</proc>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <excludedGroups>
+ org.apache.logging.log4j.categories.PerformanceTests
+ </excludedGroups>
+ <systemPropertyVariables>
+ <org.apache.activemq.SERIALIZABLE_PACKAGES>*</org.apache.activemq.SERIALIZABLE_PACKAGES>
+ </systemPropertyVariables>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-failsafe-plugin</artifactId>
+ <configuration>
+ <skipTests>true</skipTests>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jar-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>default-jar</id>
+ <goals>
+ <goal>jar</goal>
+ </goals>
+ <configuration combine.self="override">
+ <archive>
+ <manifestFile>${manifestfile}</manifestFile>
+ <manifestEntries>
+ <Specification-Title>${project.name}</Specification-Title>
+ <Specification-Version>${project.version}</Specification-Version>
+ <Specification-Vendor>${project.organization.name}</Specification-Vendor>
+ <Implementation-Title>${project.name}</Implementation-Title>
+ <Implementation-Version>${project.version}</Implementation-Version>
+ <Implementation-Vendor>${project.organization.name}</Implementation-Vendor>
+ <Implementation-Vendor-Id>org.apache</Implementation-Vendor-Id>
+ <X-Compile-Source-JDK>${maven.compiler.source}</X-Compile-Source-JDK>
+ <X-Compile-Target-JDK>${maven.compiler.target}</X-Compile-Target-JDK>
+ <Automatic-Module-Name>org.apache.logging.log4j.plugins</Automatic-Module-Name>
+ <Multi-Release>true</Multi-Release>
+ </manifestEntries>
+ </archive>
+ </configuration>
+ </execution>
+ <execution>
+ <id>default</id>
+ <goals>
+ <goal>test-jar</goal>
+ </goals>
+ <configuration>
+ <archive>
+ <manifestFile>${manifestfile}</manifestFile>
+ <manifestEntries>
+ <Specification-Title>${project.name}</Specification-Title>
+ <Specification-Version>${project.version}</Specification-Version>
+ <Specification-Vendor>${project.organization.name}</Specification-Vendor>
+ <Implementation-Title>${project.name}</Implementation-Title>
+ <Implementation-Version>${project.version}</Implementation-Version>
+ <Implementation-Vendor>${project.organization.name}</Implementation-Vendor>
+ <Implementation-Vendor-Id>org.apache</Implementation-Vendor-Id>
+ <X-Compile-Source-JDK>${maven.compiler.source}</X-Compile-Source-JDK>
+ <X-Compile-Target-JDK>${maven.compiler.target}</X-Compile-Target-JDK>
+ </manifestEntries>
+ </archive>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <configuration>
+ <instructions>
+ <Bundle-SymbolicName>org.apache.logging.log4j.plugins</Bundle-SymbolicName>
+ <!-- TODO: exclude internal classes from export -->
+ <Export-Package>org.apache.logging.log4j.plugins.*</Export-Package>
+ <Import-Package>
+ org.apache.logging.log4j,
+ org.apache.logging.log4j.status,
+ org.apache.logging.log4j.util,
+ org.osgi.framework.*
+ </Import-Package>
+ <Bundle-Activator>org.apache.logging.log4j.plugins.osgi.Activator</Bundle-Activator>
+ </instructions>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ <reporting>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-changes-plugin</artifactId>
+ <version>${changes.plugin.version}</version>
+ <reportSets>
+ <reportSet>
+ <reports>
+ <report>changes-report</report>
+ </reports>
+ </reportSet>
+ </reportSets>
+ <configuration>
+ <issueLinkTemplate>%URL%/show_bug.cgi?id=%ISSUE%</issueLinkTemplate>
+ <useJql>true</useJql>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-checkstyle-plugin</artifactId>
+ <version>${checkstyle.plugin.version}</version>
+ <configuration>
+ <!--<propertiesLocation>${vfs.parent.dir}/checkstyle.properties</propertiesLocation> -->
+ <configLocation>${log4jParentDir}/checkstyle.xml</configLocation>
+ <suppressionsLocation>${log4jParentDir}/checkstyle-suppressions.xml</suppressionsLocation>
+ <enableRulesSummary>false</enableRulesSummary>
+ <propertyExpansion>basedir=${basedir}</propertyExpansion>
+ <propertyExpansion>licensedir=${log4jParentDir}/checkstyle-header.txt</propertyExpansion>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-javadoc-plugin</artifactId>
+ <version>${javadoc.plugin.version}</version>
+ <configuration>
+ <failOnError>false</failOnError>
+ <source>8</source>
+ <bottom><![CDATA[<p align="center">Copyright © {inceptionYear}-{currentYear} {organizationName}. All Rights Reserved.<br />
+ Apache Logging, Apache Log4j, Log4j, Apache, the Apache feather logo, the Apache Logging project logo,
+ and the Apache Log4j logo are trademarks of The Apache Software Foundation.</p>]]></bottom>
+ <!-- module link generation is completely broken in the javadoc plugin for a multi-module non-aggregating
+ project -->
+ <additionalparam>${javadoc.opts}</additionalparam>
+ <detectOfflineLinks>false</detectOfflineLinks>
+ <linksource>true</linksource>
+ <links>
+ <link>http://docs.oracle.com/javaee/6/api/</link>
+ <link>http://www.osgi.org/javadoc/r4v43/core/</link>
+ <link>https://commons.apache.org/proper/commons-lang/javadocs/api-release/</link>
+ </links>
+ <groups>
+ <group>
+ <title>Core API</title>
+ <packages>org.apache.logging.log4j.core</packages>
+ </group>
+ <group>
+ <title>Configuration</title>
+ <packages>org.apache.logging.log4j.core.config*:org.apache.logging.log4j.core.selector</packages>
+ </group>
+ <group>
+ <title>Core Plugins</title>
+ <packages>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</packages>
+ </group>
+ <group>
+ <title>Tools</title>
+ <packages>org.apache.logging.log4j.core.net*:org.apache.logging.log4j.core.tools</packages>
+ </group>
+ <group>
+ <title>Internals</title>
+ <packages>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</packages>
+ </group>
+ </groups>
+ </configuration>
+ <reportSets>
+ <reportSet>
+ <id>non-aggregate</id>
+ <reports>
+ <report>javadoc</report>
+ </reports>
+ </reportSet>
+ </reportSets>
+ </plugin>
+ <plugin>
+ <groupId>com.github.spotbugs</groupId>
+ <artifactId>spotbugs-maven-plugin</artifactId>
+ <configuration>
+ <fork>true</fork>
+ <jvmArgs>-Duser.language=en</jvmArgs>
+ <threshold>Normal</threshold>
+ <effort>Default</effort>
+ <excludeFilterFile>${log4jParentDir}/spotbugs-exclude-filter.xml</excludeFilterFile>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jxr-plugin</artifactId>
+ <version>${jxr.plugin.version}</version>
+ <reportSets>
+ <reportSet>
+ <id>non-aggregate</id>
+ <reports>
+ <report>jxr</report>
+ </reports>
+ </reportSet>
+ <reportSet>
+ <id>aggregate</id>
+ <reports>
+ <report>aggregate</report>
+ </reports>
+ </reportSet>
+ </reportSets>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-pmd-plugin</artifactId>
+ <version>${pmd.plugin.version}</version>
+ <configuration>
+ <targetJdk>${maven.compiler.target}</targetJdk>
+ </configuration>
+ </plugin>
+ </plugins>
+ </reporting>
+</project>
+
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/Node.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/Node.java
new file mode 100644
index 0000000..22fd9bf
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/Node.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.plugins;
+
+import org.apache.logging.log4j.plugins.util.PluginType;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A Configuration node.
+ */
+public class Node {
+
+ /**
+ * Main plugin category for plugins which are represented as a configuration node. Such plugins tend to be
+ * available as XML elements in a configuration file.
+ *
+ * @since 2.1
+ */
+ public static final String CATEGORY = "Core";
+
+ private final Node parent;
+ private final String name;
+ private String value;
+ private final PluginType<?> type;
+ private final Map<String, String> attributes = new HashMap<>();
+ private final List<Node> children = new ArrayList<>();
+ private Object object;
+
+
+ /**
+ * Creates a new instance of {@code Node} and initializes it
+ * with a name and the corresponding XML element.
+ *
+ * @param parent the node's parent.
+ * @param name the node's name.
+ * @param type The Plugin Type associated with the node.
+ */
+ public Node(final Node parent, final String name, final PluginType<?> type) {
+ this.parent = parent;
+ this.name = name;
+ this.type = type;
+ }
+
+ public Node() {
+ this.parent = null;
+ this.name = null;
+ this.type = null;
+ }
+
+ public Node(final Node node) {
+ this.parent = node.parent;
+ this.name = node.name;
+ this.type = node.type;
+ this.attributes.putAll(node.getAttributes());
+ this.value = node.getValue();
+ for (final Node child : node.getChildren()) {
+ this.children.add(new Node(child));
+ }
+ this.object = node.object;
+ }
+
+ public Map<String, String> getAttributes() {
+ return attributes;
+ }
+
+ public List<Node> getChildren() {
+ return children;
+ }
+
+ public boolean hasChildren() {
+ return !children.isEmpty();
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ public void setValue(final String value) {
+ this.value = value;
+ }
+
+ public Node getParent() {
+ return parent;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public boolean isRoot() {
+ return parent == null;
+ }
+
+ public void setObject(final Object obj) {
+ object = obj;
+ }
+
+ @SuppressWarnings("unchecked")
+ public <T> T getObject() {
+ return (T) object;
+ }
+
+ /**
+ * Returns this node's object cast to the given class.
+ *
+ * @param clazz the class to cast this node's object to.
+ * @param <T> the type to cast to.
+ * @return this node's object.
+ * @since 2.1
+ */
+ public <T> T getObject(final Class<T> clazz) {
+ return clazz.cast(object);
+ }
+
+ /**
+ * Determines if this node's object is an instance of the given class.
+ *
+ * @param clazz the class to check.
+ * @return {@code true} if this node's object is an instance of the given class.
+ * @since 2.1
+ */
+ public boolean isInstanceOf(final Class<?> clazz) {
+ return clazz.isInstance(object);
+ }
+
+ public PluginType<?> getType() {
+ return type;
+ }
+
+ @Override
+ public String toString() {
+ if (object == null) {
+ return "null";
+ }
+ return type.isObjectPrintable() ? object.toString() :
+ type.getPluginClass().getName() + " with name " + name;
+ }
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/Plugin.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/Plugin.java
new file mode 100644
index 0000000..18f04ee
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/Plugin.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.plugins;
+
+import org.apache.logging.log4j.util.Strings;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation that identifies a Class as a Plugin.
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+public @interface Plugin {
+
+ /**
+ * Value of the elementType when none is specified.
+ */
+ String EMPTY = Strings.EMPTY;
+
+ /**
+ * Name of the plugin. Note that this name is case-insensitive.
+ * @return the name of the plugin.
+ */
+ String name();
+
+ /**
+ * Category to place the plugin under. Category names are case-sensitive.
+ * @return the category
+ */
+ String category();
+
+ /**
+ * Name of the corresponding category of elements this plugin belongs under. For example, {@code appender} would
+ * indicate an Appender plugin which would be in the
+ * {@code <Appenders/>} element of a Configuration.
+ * @return the element's type.
+ */
+ String elementType() default EMPTY;
+
+ /**
+ * Indicates if the plugin class implements a useful {@link Object#toString()} method for use in log messages.
+ * @return true if the object should print nicely.
+ */
+ boolean printObject() default false;
+
+ /**
+ * Indicates if construction and injection of child configuration nodes should be deferred until first use.
+ * @return true if child elements should defer instantiation until they are accessed.
+ */
+ boolean deferChildren() default false;
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/PluginAliases.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/PluginAliases.java
new file mode 100644
index 0000000..a194798
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/PluginAliases.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.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;
+
+/**
+ * Identifies a list of aliases for an annotated plugin element. This is supported by plugin classes and other element
+ * types supported by the annotations in this package.
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.PARAMETER, ElementType.TYPE, ElementType.FIELD, ElementType.METHOD})
+public @interface PluginAliases {
+
+ /**
+ * Aliases the annotated element can also be referred to. These aliases are case-insensitive.
+ * @return the aliases associated with the plugin.
+ */
+ String[] value();
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/PluginAttribute.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/PluginAttribute.java
new file mode 100644
index 0000000..d844d02
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/PluginAttribute.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.plugins;
+
+import org.apache.logging.log4j.plugins.inject.InjectorStrategy;
+import org.apache.logging.log4j.plugins.inject.PluginAttributeInjector;
+import org.apache.logging.log4j.plugins.name.NameProvider;
+import org.apache.logging.log4j.plugins.name.PluginAttributeNameProvider;
+import org.apache.logging.log4j.util.Strings;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Identifies a Plugin Attribute along with optional metadata. Plugin attributes can be injected as parameters to
+ * a static {@linkplain PluginFactory factory method}, or as fields and single-parameter methods in a plugin
+ * {@linkplain org.apache.logging.log4j.plugins.util.Builder builder class}.
+ *
+ * <p>Default values may be specified via one of the <code>default<var>Type</var></code> attributes depending on the
+ * annotated type. Unlisted types that are supported by a corresponding
+ * {@link org.apache.logging.log4j.plugins.convert.TypeConverter} may use the {@link #defaultString()} attribute.
+ * When annotating a field, a default value can be specified by the field's initial value instead of using one of the
+ * annotation attributes.</p>
+ *
+ * <p>Plugin attributes with sensitive data such as passwords should specify {@link #sensitive()} to avoid having
+ * their values logged in debug logs.</p>
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.PARAMETER, ElementType.FIELD, ElementType.METHOD})
+@InjectorStrategy(PluginAttributeInjector.class)
+@NameProvider(PluginAttributeNameProvider.class)
+public @interface PluginAttribute {
+
+ /**
+ * Specifies the default boolean value to use.
+ * @return the default boolean value.
+ */
+ boolean defaultBoolean() default false;
+
+ /**
+ * Specifies the default byte value to use.
+ * @return the default byte value;
+ */
+ byte defaultByte() default 0;
+
+ /**
+ * Specifies the default byte value to use.
+ * @return the default char value.
+ */
+ char defaultChar() default 0;
+
+ /**
+ * Specifies the default {@link Class} value to use.
+ * @return the default class value.
+ */
+ Class<?> defaultClass() default Object.class;
+
+ /**
+ * Specifies the default double floating point value to use.
+ * @return the default double value;
+ */
+ double defaultDouble() default 0.0d;
+
+ /**
+ * Specifies the default floating point value to use.
+ * @return the default float value.
+ */
+ float defaultFloat() default 0.0f;
+
+ /**
+ * Specifies the default integer value to use.
+ * @return the default integer value.
+ */
+ int defaultInt() default 0;
+
+ /**
+ * Specifies the default long value to use.
+ * @return the default long value;
+ */
+ long defaultLong() default 0L;
+
+ /**
+ * Specifies the default long value to use.
+ * @return the default short value.
+ */
+ short defaultShort() default 0;
+
+ /**
+ * Specifies the default value this attribute should use if none is provided or if the provided value is invalid.
+ * @return the default String value.
+ */
+ String defaultString() default Strings.EMPTY;
+
+ /**
+ * Specifies the name of the attribute (case-insensitive) this annotation corresponds to.
+ * If blank, defaults to using reflection on the annotated element as such:
+ *
+ * <ul>
+ * <li>Field: uses the field name.</li>
+ * <li>Method: when named <code>set<var>XYZ</var></code> or <code>with<var>XYZ</var></code>, uses the rest
+ * (<var>XYZ</var>) of the method name. Otherwise, uses the name of the first parameter.</li>
+ * <li>Parameter: uses the parameter name.</li>
+ * </ul>
+ * @return the value;
+ */
+ String value() default Strings.EMPTY;
+
+ /**
+ * Indicates that this attribute is a sensitive one that shouldn't be logged directly. Such attributes will instead
+ * be output as a hashed value.
+ * @return true if the attribute should be considered sensitive.
+ */
+ boolean sensitive() default false;
+
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/PluginBuilderAttribute.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/PluginBuilderAttribute.java
new file mode 100644
index 0000000..a0a39b5
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/PluginBuilderAttribute.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.plugins;
+
+import org.apache.logging.log4j.plugins.inject.InjectorStrategy;
+import org.apache.logging.log4j.plugins.inject.PluginBuilderAttributeInjector;
+import org.apache.logging.log4j.plugins.name.NameProvider;
+import org.apache.logging.log4j.plugins.name.PluginBuilderAttributeNameProvider;
+import org.apache.logging.log4j.util.Strings;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Marks a field as a Plugin Attribute.
+ *
+ * @deprecated use {@link PluginAttribute}
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.PARAMETER, ElementType.FIELD, ElementType.TYPE})
+@InjectorStrategy(PluginBuilderAttributeInjector.class)
+@NameProvider(PluginBuilderAttributeNameProvider.class)
+@Deprecated
+public @interface PluginBuilderAttribute {
+
+ /**
+ * Specifies the attribute name this corresponds to. If no attribute is set (i.e., a blank string), then the name
+ * of the field (or member) this annotation is attached to will be used.
+ * @return the name of the attribute.
+ */
+ String value() default Strings.EMPTY;
+
+ /**
+ * Indicates that this attribute is a sensitive one that shouldn't be logged directly. Such attributes will instead
+ * be output as a hashed value.
+ * @return true if this attribute should be considered sensitive.
+ */
+ boolean sensitive() default false;
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/PluginElement.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/PluginElement.java
new file mode 100644
index 0000000..fa296ca
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/PluginElement.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.plugins;
+
+import org.apache.logging.log4j.plugins.inject.InjectorStrategy;
+import org.apache.logging.log4j.plugins.inject.PluginElementInjector;
+import org.apache.logging.log4j.plugins.name.NameProvider;
+import org.apache.logging.log4j.plugins.name.PluginElementNameProvider;
+import org.apache.logging.log4j.util.Strings;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Identifies a Plugin Element which allows for plugins to be configured and injected into another plugin.
+ * Plugin elements can be injected as parameters to a static {@linkplain PluginFactory factory method}, or as fields and
+ * single-parameter methods in a plugin {@linkplain org.apache.logging.log4j.plugins.util.Builder builder class}.
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.PARAMETER, ElementType.FIELD, ElementType.METHOD})
+@InjectorStrategy(PluginElementInjector.class)
+@NameProvider(PluginElementNameProvider.class)
+public @interface PluginElement {
+
+ /**
+ * Identifies the case-insensitive element name (or attribute name) this corresponds with in a configuration file.
+ * If blank, defaults to using reflection on the annotated element as such:
+ *
+ * <ul>
+ * <li>Field: uses the field name.</li>
+ * <li>Method: when named <code>set<var>XYZ</var></code> or <code>with<var>XYZ</var></code>, uses the rest
+ * (<var>XYZ</var>) of the method name. Otherwise, uses the name of the first parameter.</li>
+ * <li>Parameter: uses the parameter name.</li>
+ * </ul>
+ * @return the element name.
+ */
+ String value() default Strings.EMPTY;
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/PluginFactory.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/PluginFactory.java
new file mode 100644
index 0000000..f1d89c6
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/PluginFactory.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.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;
+
+/**
+ * Identifies a static method as a factory to create a plugin or a
+ * {@linkplain org.apache.logging.log4j.plugins.util.Builder builder class} for constructing a plugin.
+ * Factory methods should annotate their parameters with {@link PluginAttribute}, {@link PluginElement},
+ * {@link PluginValue}, or other plugin annotations annotated with
+ * {@link org.apache.logging.log4j.plugins.inject.InjectorStrategy}.
+ * If a factory method returns a builder class, this method should have no arguments; instead, the builder class should
+ * annotate its fields or single-parameter methods to inject plugin configuration data.
+ * <p>
+ * There can only be one factory method per class.
+ * </p>
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface PluginFactory {
+ // empty
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/PluginNode.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/PluginNode.java
new file mode 100644
index 0000000..dcc5b0c
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/PluginNode.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.plugins;
+
+import org.apache.logging.log4j.plugins.inject.InjectorStrategy;
+import org.apache.logging.log4j.plugins.inject.PluginNodeInjector;
+
+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;
+
+/**
+ * Identifies the configuration {@link Node} currently being configured. This can be injected as a parameter to a static
+ * {@linkplain PluginFactory factory method}, or as a field or single-parameter method in a plugin
+ * {@linkplain org.apache.logging.log4j.plugins.util.Builder builder class}.
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.PARAMETER, ElementType.FIELD, ElementType.METHOD})
+@InjectorStrategy(PluginNodeInjector.class)
+public @interface PluginNode {
+ // empty
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/PluginValue.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/PluginValue.java
new file mode 100644
index 0000000..5e7a3fe
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/PluginValue.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.plugins;
+
+import org.apache.logging.log4j.plugins.inject.InjectorStrategy;
+import org.apache.logging.log4j.plugins.inject.PluginValueInjector;
+import org.apache.logging.log4j.plugins.name.NameProvider;
+import org.apache.logging.log4j.plugins.name.PluginValueNameProvider;
+
+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;
+
+/**
+ * Identifies a Plugin Value and its corresponding attribute alias for configuration formats that don't distinguish
+ * between values and attributes. A value is typically used differently from an attribute in that it is either the
+ * main configuration value required or it is the only value needed to create a plugin. A plugin value can be injected
+ * as a parameter to a static {@linkplain PluginFactory factory method}, or as a field or single-parameter method in a
+ * plugin {@linkplain org.apache.logging.log4j.plugins.util.Builder builder class}.
+ *
+ * <p>For example, a Property plugin corresponds to a property entry in a configuration file. The property name is
+ * specified as an attribute, and the property value is specified as a value.</p>
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.PARAMETER, ElementType.FIELD, ElementType.METHOD})
+@InjectorStrategy(PluginValueInjector.class)
+@NameProvider(PluginValueNameProvider.class)
+public @interface PluginValue {
+
+ /**
+ * Specifies the case-insensitive attribute name to use in configuration formats that don't distinguish between
+ * attributes and values. By default, this uses the attribute name {@code value}.
+ * @return the value of the attribute.
+ */
+ String value() default "value";
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/bind/AbstractConfigurationBinder.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/bind/AbstractConfigurationBinder.java
new file mode 100644
index 0000000..e2a9cfc
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/bind/AbstractConfigurationBinder.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.plugins.bind;
+
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.plugins.convert.TypeConverter;
+import org.apache.logging.log4j.plugins.convert.TypeConverterRegistry;
+import org.apache.logging.log4j.plugins.name.AnnotatedElementNameProvider;
+import org.apache.logging.log4j.plugins.validation.ConstraintValidator;
+import org.apache.logging.log4j.plugins.validation.ConstraintValidators;
+import org.apache.logging.log4j.status.StatusLogger;
+
+import java.lang.reflect.AnnotatedElement;
+import java.lang.reflect.Type;
+import java.util.Collection;
+import java.util.Objects;
+import java.util.function.Function;
+
+/**
+ * Generic configuration binder for an {@link AnnotatedElement}. This provides automatic
+ * {@linkplain TypeConverter string conversion} and {@linkplain ConstraintValidator constraint validation} support.
+ *
+ * @param <E> element type being bound
+ */
+public abstract class AbstractConfigurationBinder<E extends AnnotatedElement> implements ConfigurationBinder {
+ protected static final Logger LOGGER = StatusLogger.getLogger();
+
+ final E element;
+ final String name;
+ private final Type injectionType;
+ private final Collection<ConstraintValidator<?>> validators;
+
+ AbstractConfigurationBinder(final E element, final Function<E, Type> injectionTypeExtractor) {
+ this.element = Objects.requireNonNull(element);
+ this.name = AnnotatedElementNameProvider.getName(element);
+ Objects.requireNonNull(injectionTypeExtractor);
+ this.injectionType = Objects.requireNonNull(injectionTypeExtractor.apply(element));
+ this.validators = ConstraintValidators.findValidators(element.getAnnotations());
+ }
+
+ @Override
+ public void bindString(final Object factory, final String value) {
+ Object convertedValue = null;
+ if (value != null) {
+ final TypeConverter<?> converter = TypeConverterRegistry.getInstance().findCompatibleConverter(injectionType);
+ try {
+ convertedValue = converter.convert(value);
+ } catch (final Exception e) {
+ throw new ConfigurationBindingException(name, value, e);
+ }
+ }
+ bindObject(factory, convertedValue);
+ }
+
+ void validate(final Object value) {
+ boolean valid = true;
+ for (ConstraintValidator<?> validator : validators) {
+ valid &= validator.isValid(name, value);
+ }
+ // FIXME: this doesn't seem to work properly with primitive types
+// if (valid && value != null && !TypeUtil.isAssignable(injectionType, value.getClass())) {
+// LOGGER.error("Cannot bind value of type {} to option {} with type {}", value.getClass(), name, injectionType);
+// valid = false;
+// }
+ if (!valid) {
+ throw new ConfigurationBindingException(name, value);
+ }
+ }
+
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/bind/ConfigurationBinder.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/bind/ConfigurationBinder.java
new file mode 100644
index 0000000..d97075c
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/bind/ConfigurationBinder.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.plugins.bind;
+
+/**
+ * Strategy to bind and validate an {@linkplain org.apache.logging.log4j.plugins.inject.ConfigurationInjector injected
+ * configuration value} to a {@linkplain org.apache.logging.log4j.plugins.PluginFactory plugin factory}.
+ */
+public interface ConfigurationBinder {
+ /**
+ * Binds an unparsed string value to the given factory.
+ *
+ * @param factory injection factory to bind value to
+ * @param value string representation of configuration value
+ * @throws ConfigurationBindingException if the given value is invalid
+ */
+ void bindString(final Object factory, final String value);
+
+ /**
+ * Binds an object to the given factory.
+ *
+ * @param factory injection factory to bind value to
+ * @param value configuration value to bind
+ * @throws ConfigurationBindingException if the given value is invalid
+ */
+ void bindObject(final Object factory, final Object value);
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/bind/ConfigurationBindingException.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/bind/ConfigurationBindingException.java
new file mode 100644
index 0000000..69e9b94
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/bind/ConfigurationBindingException.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.plugins.bind;
+
+public class ConfigurationBindingException extends IllegalArgumentException {
+
+ ConfigurationBindingException(final String name, final Object value) {
+ super("Invalid value '" + value + "' for option '" + name + "'");
+ }
+
+ ConfigurationBindingException(final String name, final Object value, final Throwable cause) {
+ super("Unable to set option '" + name + "' to value '" + value + "'", cause);
+ }
+
+ ConfigurationBindingException(final String message, final Throwable cause) {
+ super(message, cause);
+ }
+
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/bind/FactoryMethodBinder.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/bind/FactoryMethodBinder.java
new file mode 100644
index 0000000..7548871
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/bind/FactoryMethodBinder.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.plugins.bind;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Parameter;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.BiConsumer;
+
+// TODO: can support constructor factory following same pattern
+public class FactoryMethodBinder {
+
+ private final Method factoryMethod;
+ private final Map<Parameter, ConfigurationBinder> binders = new ConcurrentHashMap<>();
+ private final Map<Parameter, Object> boundParameters = new ConcurrentHashMap<>();
+
+ public FactoryMethodBinder(final Method factoryMethod) {
+ this.factoryMethod = Objects.requireNonNull(factoryMethod);
+ for (final Parameter parameter : factoryMethod.getParameters()) {
+ binders.put(parameter, new ParameterConfigurationBinder(parameter));
+ }
+ }
+
+ public void forEachParameter(final BiConsumer<Parameter, ConfigurationBinder> consumer) {
+ binders.forEach(consumer);
+ }
+
+ public Object invoke() throws Throwable {
+ final Parameter[] parameters = factoryMethod.getParameters();
+ final Object[] args = new Object[parameters.length];
+ for (int i = 0; i < parameters.length; i++) {
+ args[i] = boundParameters.get(parameters[i]);
+ }
+ try {
+ return factoryMethod.invoke(null, args);
+ } catch (final IllegalAccessException e) {
+ throw new ConfigurationBindingException("Cannot access factory method " + factoryMethod, e);
+ } catch (final InvocationTargetException e) {
+ throw e.getCause();
+ }
+ }
+
+ private class ParameterConfigurationBinder extends AbstractConfigurationBinder<Parameter> {
+ private ParameterConfigurationBinder(final Parameter parameter) {
+ super(parameter, Parameter::getParameterizedType);
+ }
+
+ @Override
+ public void bindObject(final Object factory, final Object value) {
+ validate(value);
+ if (value != null) {
+ boundParameters.put(element, value);
+ }
+ }
+ }
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/bind/FieldConfigurationBinder.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/bind/FieldConfigurationBinder.java
new file mode 100644
index 0000000..112d759
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/bind/FieldConfigurationBinder.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.plugins.bind;
+
+import java.lang.reflect.Field;
+import java.util.Objects;
+
+public class FieldConfigurationBinder extends AbstractConfigurationBinder<Field> {
+
+ public FieldConfigurationBinder(final Field field) {
+ super(field, Field::getGenericType);
+ }
+
+ @Override
+ public void bindObject(final Object factory, final Object value) {
+ Objects.requireNonNull(factory);
+ // FIXME: if we specify a default field value, @PluginAttribute's defaultType will override that
+ if (value == null) {
+ try {
+ Object defaultValue = element.get(factory);
+ validate(defaultValue);
+ LOGGER.trace("Using default value {} for option {}", defaultValue, name);
+ } catch (final IllegalAccessException e) {
+ throw new ConfigurationBindingException("Unable to validate option " + name, e);
+ }
+ } else {
+ validate(value);
+ try {
+ element.set(factory, value);
+ LOGGER.trace("Using value {} for option {}", value, name);
+ } catch (final IllegalAccessException e) {
+ throw new ConfigurationBindingException(name, value, e);
+ }
+ }
+ }
+
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/bind/MethodConfigurationBinder.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/bind/MethodConfigurationBinder.java
new file mode 100644
index 0000000..0ef7f0f
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/bind/MethodConfigurationBinder.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.plugins.bind;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Objects;
+
+public class MethodConfigurationBinder extends AbstractConfigurationBinder<Method> {
+
+ public MethodConfigurationBinder(final Method method) {
+ super(method, m -> m.getGenericParameterTypes()[0]);
+ }
+
+ @Override
+ public void bindObject(final Object factory, final Object value) {
+ Objects.requireNonNull(factory);
+ validate(value);
+ try {
+ element.invoke(factory, value);
+ } catch (final IllegalAccessException e) {
+ throw new ConfigurationBindingException(name, value, e);
+ } catch (final InvocationTargetException e) {
+ throw new ConfigurationBindingException(name, value, e.getCause());
+ }
+ }
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/convert/EnumConverter.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/convert/EnumConverter.java
new file mode 100644
index 0000000..fd8012a
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/convert/EnumConverter.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.plugins.convert;
+
+import org.apache.logging.log4j.util.EnglishEnums;
+
+/**
+ * Converts a {@link String} into a {@link Enum}. Returns {@code null} for invalid enum names.
+ *
+ * @param <E> the enum class to parse.
+ * @since 2.1 moved from TypeConverters
+ */
+public class EnumConverter<E extends Enum<E>> implements TypeConverter<E> {
+ private final Class<E> clazz;
+
+ public EnumConverter(final Class<E> clazz) {
+ this.clazz = clazz;
+ }
+
+ @Override
+ public E convert(final String s) {
+ return EnglishEnums.valueOf(clazz, s);
+ }
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/convert/HexConverter.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/convert/HexConverter.java
new file mode 100644
index 0000000..f70131d
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/convert/HexConverter.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.plugins.convert;
+
+/**
+ * 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
+ */
+public class HexConverter {
+
+ 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));
+ }
+ return data;
+ }
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/convert/TypeConverter.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/convert/TypeConverter.java
new file mode 100644
index 0000000..aaa5b4d
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/convert/TypeConverter.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.plugins.convert;
+
+/**
+ * Interface for doing automatic String conversion to a specific type.
+ *
+ * @param <T> Converts Strings into the given type {@code T}.
+ * @since 2.1 Moved to the {@code convert} package.
+ */
+public interface TypeConverter<T> {
+
+ /**
+ * Converts a String to a given type.
+ *
+ * @param s the String to convert. Cannot be {@code null}.
+ * @return the converted object.
+ * @throws Exception thrown when a conversion error occurs
+ */
+ T convert(String s) throws Exception;
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/convert/TypeConverterRegistry.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/convert/TypeConverterRegistry.java
new file mode 100644
index 0000000..fb5b27e
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/convert/TypeConverterRegistry.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.plugins.convert;
+
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.plugins.util.PluginManager;
+import org.apache.logging.log4j.plugins.util.PluginType;
+import org.apache.logging.log4j.plugins.util.TypeUtil;
+import org.apache.logging.log4j.util.ReflectionUtil;
+import org.apache.logging.log4j.status.StatusLogger;
+
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.Collection;
+import java.util.Map;
+import java.util.Objects;
+import java.util.UnknownFormatConversionException;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+/**
+ * Registry for {@link TypeConverter} plugins.
+ *
+ * @since 2.1
+ */
+public class TypeConverterRegistry {
+
+ private static final Logger LOGGER = StatusLogger.getLogger();
+ private static volatile TypeConverterRegistry INSTANCE;
+ private static final Object INSTANCE_LOCK = new Object();
+
+ private final ConcurrentMap<Type, TypeConverter<?>> registry = new ConcurrentHashMap<>();
+
+ /**
+ * Gets the singleton instance of the TypeConverterRegistry.
+ *
+ * @return the singleton instance.
+ */
+ public static TypeConverterRegistry getInstance() {
+ TypeConverterRegistry result = INSTANCE;
+ if (result == null) {
+ synchronized (INSTANCE_LOCK) {
+ result = INSTANCE;
+ if (result == null) {
+ INSTANCE = result = new TypeConverterRegistry();
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Finds a {@link TypeConverter} for the given {@link Type}, falling back to an assignment-compatible TypeConverter
+ * if none exist for the given type. That is, if the given Type does not have a TypeConverter, but another Type
+ * which can be assigned to the given Type <em>does</em> have a TypeConverter, then that TypeConverter will be
+ * used and registered.
+ *
+ * @param type the Type to find a TypeConverter for (must not be {@code null}).
+ * @return a TypeConverter for the given Type.
+ * @throws UnknownFormatConversionException if no TypeConverter can be found for the given type.
+ */
+ public TypeConverter<?> findCompatibleConverter(final Type type) {
+ Objects.requireNonNull(type, "No type was provided");
+ final TypeConverter<?> primary = registry.get(type);
+ // cached type converters
+ if (primary != null) {
+ return primary;
+ }
+ // dynamic enum support
+ if (type instanceof Class<?>) {
+ final Class<?> clazz = (Class<?>) type;
+ if (clazz.isEnum()) {
+ @SuppressWarnings({"unchecked","rawtypes"})
+ final EnumConverter<? extends Enum> converter = new EnumConverter(clazz.asSubclass(Enum.class));
+ registry.putIfAbsent(type, converter);
+ return converter;
+ }
+ }
+ // look for compatible converters
+ for (final Map.Entry<Type, TypeConverter<?>> entry : registry.entrySet()) {
+ final Type key = entry.getKey();
+ if (TypeUtil.isAssignable(type, key)) {
+ LOGGER.debug("Found compatible TypeConverter<{}> for type [{}].", key, type);
+ final TypeConverter<?> value = entry.getValue();
+ registry.putIfAbsent(type, value);
+ return value;
+ }
+ }
+ throw new UnknownFormatConversionException(type.toString());
+ }
+
+ private TypeConverterRegistry() {
+ LOGGER.trace("TypeConverterRegistry initializing.");
+ final PluginManager manager = new PluginManager(TypeConverters.CATEGORY);
+ manager.collectPlugins();
+ loadKnownTypeConverters(manager.getPlugins().values());
+ registerPrimitiveTypes();
+ }
+
+ private void loadKnownTypeConverters(final Collection<PluginType<?>> knownTypes) {
+ for (final PluginType<?> knownType : knownTypes) {
+ final Class<?> clazz = knownType.getPluginClass();
+ if (TypeConverter.class.isAssignableFrom(clazz)) {
+ @SuppressWarnings("rawtypes")
+ final Class<? extends TypeConverter> pluginClass = clazz.asSubclass(TypeConverter.class);
+ final Type conversionType = getTypeConverterSupportedType(pluginClass);
+ final TypeConverter<?> converter = ReflectionUtil.instantiate(pluginClass);
+ if (registry.putIfAbsent(conversionType, converter) != null) {
+ LOGGER.warn("Found a TypeConverter [{}] for type [{}] that already exists.", converter,
+ conversionType);
+ }
+ }
+ }
+ }
+
+ private static Type getTypeConverterSupportedType(@SuppressWarnings("rawtypes") final Class<? extends TypeConverter> typeConverterClass) {
+ for (final Type type : typeConverterClass.getGenericInterfaces()) {
+ if (type instanceof ParameterizedType) {
+ final ParameterizedType pType = (ParameterizedType) type;
+ if (TypeConverter.class.equals(pType.getRawType())) {
+ // TypeConverter<T> has only one type argument (T), so return that
+ return pType.getActualTypeArguments()[0];
+ }
+ }
+ }
+ return Void.TYPE;
+ }
+
+ private void registerPrimitiveTypes() {
+ registerTypeAlias(Boolean.class, Boolean.TYPE);
+ registerTypeAlias(Byte.class, Byte.TYPE);
+ registerTypeAlias(Character.class, Character.TYPE);
+ registerTypeAlias(Double.class, Double.TYPE);
+ registerTypeAlias(Float.class, Float.TYPE);
+ registerTypeAlias(Integer.class, Integer.TYPE);
+ registerTypeAlias(Long.class, Long.TYPE);
+ registerTypeAlias(Short.class, Short.TYPE);
+ }
+
+ private void registerTypeAlias(final Type knownType, final Type aliasType) {
+ TypeConverter<?> converter = registry.get(knownType);
+ if (converter != null) {
+ registry.putIfAbsent(aliasType, converter);
+ } else {
+ LOGGER.error("Cannot locate converter for {}", knownType);
+ }
+ }
+
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/convert/TypeConverters.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/convert/TypeConverters.java
new file mode 100644
index 0000000..ed387d7
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/convert/TypeConverters.java
@@ -0,0 +1,423 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+
+package org.apache.logging.log4j.plugins.convert;
+
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.status.StatusLogger;
+import org.apache.logging.log4j.util.LoaderUtil;
+
+import java.io.File;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.net.*;
+import java.nio.charset.Charset;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.security.Provider;
+import java.security.Security;
+import java.util.Base64;
+import java.util.UUID;
+import java.util.regex.Pattern;
+
+/**
+ * Collection of basic TypeConverter implementations. May be used to register additional TypeConverters or find
+ * registered TypeConverters.
+ *
+ * @since 2.1 Moved to the {@code convert} package.
+ */
+public final class TypeConverters {
+
+ /**
+ * The {@link Plugin#category() Plugin Category} to use for {@link TypeConverter} plugins.
+ *
+ * @since 2.1
+ */
+ public static final String CATEGORY = "TypeConverter";
+
+ private static final Base64.Decoder decoder = Base64.getDecoder();
+
+ /**
+ * Parses a {@link String} into a {@link BigDecimal}.
+ */
+ @Plugin(name = "BigDecimal", category = CATEGORY)
+ public static class BigDecimalConverter implements TypeConverter<BigDecimal> {
+ @Override
+ public BigDecimal convert(final String s) {
+ return new BigDecimal(s);
+ }
+ }
+
+ /**
+ * Parses a {@link String} into a {@link BigInteger}.
+ */
+ @Plugin(name = "BigInteger", category = CATEGORY)
+ public static class BigIntegerConverter implements TypeConverter<BigInteger> {
+ @Override
+ public BigInteger convert(final String s) {
+ return new BigInteger(s);
+ }
+ }
+
+ /**
+ * Converts a {@link String} into a {@link Boolean}.
+ */
+ @Plugin(name = "Boolean", category = CATEGORY)
+ public static class BooleanConverter implements TypeConverter<Boolean> {
+ @Override
+ public Boolean convert(final String s) {
+ return Boolean.valueOf(s);
+ }
+ }
+
+ /**
+ * Converts a {@link String} into a {@code byte[]}.
+ *
+ * The supported formats are:
+ * <ul>
+ * <li>0x0123456789ABCDEF</li>
+ * <li>Base64:ABase64String</li>
+ * <li>String using {@link Charset#defaultCharset()} [TODO Should this be UTF-8 instead?]</li>
+ * </ul>
+ */
+ @Plugin(name = "ByteArray", category = CATEGORY)
+ public static class ByteArrayConverter implements TypeConverter<byte[]> {
+
+ private static final String PREFIX_0x = "0x";
+ private static final String PREFIX_BASE64 = "Base64:";
+
+ @Override
+ public byte[] convert(final String value) {
+ byte[] bytes;
+ if (value == null || value.isEmpty()) {
+ bytes = new byte[0];
+ } else if (value.startsWith(PREFIX_BASE64)) {
+ final String lexicalXSDBase64Binary = value.substring(PREFIX_BASE64.length());
+ bytes = decoder.decode(lexicalXSDBase64Binary);
+ } else if (value.startsWith(PREFIX_0x)) {
+ final String lexicalXSDHexBinary = value.substring(PREFIX_0x.length());
+ bytes = HexConverter.parseHexBinary(lexicalXSDHexBinary);
+ } else {
+ bytes = value.getBytes(Charset.defaultCharset());
+ }
+ return bytes;
+ }
+ }
+
+ /**
+ * Converts a {@link String} into a {@link Byte}.
+ */
+ @Plugin(name = "Byte", category = CATEGORY)
+ public static class ByteConverter implements TypeConverter<Byte> {
+ @Override
+ public Byte convert(final String s) {
+ return Byte.valueOf(s);
+ }
+ }
+
+ /**
+ * Converts a {@link String} into a {@link Character}.
+ */
+ @Plugin(name = "Character", category = CATEGORY)
+ public static class CharacterConverter implements TypeConverter<Character> {
+ @Override
+ public Character convert(final String s) {
+ if (s.length() != 1) {
+ throw new IllegalArgumentException("Character string must be of length 1: " + s);
+ }
+ return Character.valueOf(s.toCharArray()[0]);
+ }
+ }
+
+ /**
+ * Converts a {@link String} into a {@code char[]}.
+ */
+ @Plugin(name = "CharacterArray", category = CATEGORY)
+ public static class CharArrayConverter implements TypeConverter<char[]> {
+ @Override
+ public char[] convert(final String s) {
+ return s.toCharArray();
+ }
+ }
+
+ /**
+ * Converts a {@link String} into a {@link Charset}.
+ */
+ @Plugin(name = "Charset", category = CATEGORY)
+ public static class CharsetConverter implements TypeConverter<Charset> {
+ @Override
+ public Charset convert(final String s) {
+ return Charset.forName(s);
+ }
+ }
+
+ /**
+ * Converts a {@link String} into a {@link Class}.
+ */
+ @Plugin(name = "Class", category = CATEGORY)
+ public static class ClassConverter implements TypeConverter<Class<?>> {
+ @Override
+ public Class<?> convert(final String s) throws ClassNotFoundException {
+ switch (s.toLowerCase()) {
+ case "boolean":
+ return boolean.class;
+ case "byte":
+ return byte.class;
+ case "char":
+ return char.class;
+ case "double":
+ return double.class;
+ case "float":
+ return float.class;
+ case "int":
+ return int.class;
+ case "long":
+ return long.class;
+ case "short":
+ return short.class;
+ case "void":
+ return void.class;
+ default:
+ return LoaderUtil.loadClass(s);
+ }
+
+ }
+ }
+
+ /**
+ * Converts a {@link String} into a {@link Double}.
+ */
+ @Plugin(name = "Double", category = CATEGORY)
+ public static class DoubleConverter implements TypeConverter<Double> {
+ @Override
+ public Double convert(final String s) {
+ return Double.valueOf(s);
+ }
+ }
+
+ /**
+ * Converts a {@link String} into a {@link File}.
+ */
+ @Plugin(name = "File", category = CATEGORY)
+ public static class FileConverter implements TypeConverter<File> {
+ @Override
+ public File convert(final String s) {
+ return new File(s);
+ }
+ }
+
+ /**
+ * Converts a {@link String} into a {@link Float}.
+ */
+ @Plugin(name = "Float", category = CATEGORY)
+ public static class FloatConverter implements TypeConverter<Float> {
+ @Override
+ public Float convert(final String s) {
+ return Float.valueOf(s);
+ }
+ }
+
+ /**
+ * Converts a {@link String} into an {@link InetAddress}.
+ */
+ @Plugin(name = "InetAddress", category = CATEGORY)
+ public static class InetAddressConverter implements TypeConverter<InetAddress> {
+ @Override
+ public InetAddress convert(final String s) throws Exception {
+ return InetAddress.getByName(s);
+ }
+ }
+
+ /**
+ * Converts a {@link String} into a {@link Integer}.
+ */
+ @Plugin(name = "Integer", category = CATEGORY)
+ public static class IntegerConverter implements TypeConverter<Integer> {
+ @Override
+ public Integer convert(final String s) {
+ return Integer.valueOf(s);
+ }
+ }
+
+ /**
+ * Converts a {@link String} into a Log4j {@link Level}. Returns {@code null} for invalid level names.
+ */
+ @Plugin(name = "Level", category = CATEGORY)
+ public static class LevelConverter implements TypeConverter<Level> {
+ @Override
+ public Level convert(final String s) {
+ return Level.valueOf(s);
+ }
+ }
+
+ /**
+ * Converts a {@link String} into a {@link Long}.
+ */
+ @Plugin(name = "Long", category = CATEGORY)
+ public static class LongConverter implements TypeConverter<Long> {
+ @Override
+ public Long convert(final String s) {
+ return Long.valueOf(s);
+ }
+ }
+
+ /**
+ * Converts a {@link String} into a {@link Path}.
+ * @since 2.8
+ */
+ @Plugin(name = "Path", category = CATEGORY)
+ public static class PathConverter implements TypeConverter<Path> {
+ @Override
+ public Path convert(final String s) throws Exception {
+ return Paths.get(s);
+ }
+ }
+
+ /**
+ * Converts a {@link String} into a {@link Pattern}.
+ */
+ @Plugin(name = "Pattern", category = CATEGORY)
+ public static class PatternConverter implements TypeConverter<Pattern> {
+ @Override
+ public Pattern convert(final String s) {
+ return Pattern.compile(s);
+ }
+ }
+
+ /**
+ * Converts a {@link String} into a {@link Provider}.
+ */
+ @Plugin(name = "SecurityProvider", category = CATEGORY)
+ public static class SecurityProviderConverter implements TypeConverter<Provider> {
+ @Override
+ public Provider convert(final String s) {
+ return Security.getProvider(s);
+ }
+ }
+
+ /**
+ * Converts a {@link String} into a {@link Short}.
+ */
+ @Plugin(name = "Short", category = CATEGORY)
+ public static class ShortConverter implements TypeConverter<Short> {
+ @Override
+ public Short convert(final String s) {
+ return Short.valueOf(s);
+ }
+ }
+
+ /**
+ * Returns the given {@link String}, no conversion takes place.
+ */
+ @Plugin(name = "String", category = CATEGORY)
+ public static class StringConverter implements TypeConverter<String> {
+ @Override
+ public String convert(final String s) {
+ return s;
+ }
+ }
+
+ /**
+ * Converts a {@link String} into a {@link URI}.
+ */
+ @Plugin(name = "URI", category = CATEGORY)
+ public static class UriConverter implements TypeConverter<URI> {
+ @Override
+ public URI convert(final String s) throws URISyntaxException {
+ return new URI(s);
+ }
+ }
+
+ /**
+ * Converts a {@link String} into a {@link URL}.
+ */
+ @Plugin(name = "URL", category = CATEGORY)
+ public static class UrlConverter implements TypeConverter<URL> {
+ @Override
+ public URL convert(final String s) throws MalformedURLException {
+ return new URL(s);
+ }
+ }
+
+ /**
+ * Converts a {@link String} into a {@link UUID}.
+ * @since 2.8
+ */
+ @Plugin(name = "UUID", category = CATEGORY)
+ public static class UuidConverter implements TypeConverter<UUID> {
+ @Override
+ public UUID convert(final String s) throws Exception {
+ return UUID.fromString(s);
+ }
+ }
+
+ /**
+ * Converts a String to a given class if a TypeConverter is available for that class. Falls back to the provided
+ * default value if the conversion is unsuccessful. However, if the default value is <em>also</em> invalid, then
+ * {@code null} is returned (along with a nasty status log message).
+ *
+ * @param s
+ * the string to convert
+ * @param clazz
+ * the class to try to convert the string to
+ * @param defaultValue
+ * the fallback object to use if the conversion is unsuccessful
+ * @param <T> The type of the clazz parameter.
+ * @return the converted object which may be {@code null} if the string is invalid for the given type
+ * @throws NullPointerException
+ * if {@code clazz} is {@code null}
+ * @throws IllegalArgumentException
+ * if no TypeConverter exists for the given class
+ */
+ public static <T> T convert(final String s, final Class<? extends T> clazz, final Object defaultValue) {
+ @SuppressWarnings("unchecked")
+ final TypeConverter<T> converter = (TypeConverter<T>) TypeConverterRegistry.getInstance().findCompatibleConverter(clazz);
+ if (s == null) {
+ // don't debug print here, resulting output is hard to understand
+ // LOGGER.debug("Null string given to convert. Using default [{}].", defaultValue);
+ return parseDefaultValue(converter, defaultValue);
+ }
+ try {
+ return converter.convert(s);
+ } catch (final Exception e) {
+ LOGGER.warn("Error while converting string [{}] to type [{}]. Using default value [{}].", s, clazz,
+ defaultValue, e);
+ return parseDefaultValue(converter, defaultValue);
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ private static <T> T parseDefaultValue(final TypeConverter<T> converter, final Object defaultValue) {
+ if (defaultValue == null) {
+ return null;
+ }
+ if (!(defaultValue instanceof String)) {
+ return (T) defaultValue;
+ }
+ try {
+ return converter.convert((String) defaultValue);
+ } catch (final Exception e) {
+ LOGGER.debug("Can't parse default value [{}] for type [{}].", defaultValue, converter.getClass(), e);
+ return null;
+ }
+ }
+
+ private static final Logger LOGGER = StatusLogger.getLogger();
+
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/convert/package-info.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/convert/package-info.java
new file mode 100644
index 0000000..958beb4
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/convert/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.
+ */
+
+/**
+ * TypeConverter plugins for converter strings into various types. These plugins are used for parsing plugin
+ * attributes in plugin factory methods.
+ */
+package org.apache.logging.log4j.plugins.convert;
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/AbstractConfigurationInjector.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/AbstractConfigurationInjector.java
new file mode 100644
index 0000000..86df282
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/AbstractConfigurationInjector.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.plugins.inject;
+
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.plugins.Node;
+import org.apache.logging.log4j.plugins.PluginAliases;
+import org.apache.logging.log4j.plugins.bind.ConfigurationBinder;
+import org.apache.logging.log4j.plugins.name.AnnotatedElementNameProvider;
+import org.apache.logging.log4j.status.StatusLogger;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.AnnotatedElement;
+import java.lang.reflect.Type;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.function.Function;
+
+public abstract class AbstractConfigurationInjector<Ann extends Annotation, Cfg> implements ConfigurationInjector<Ann, Cfg> {
+
+ protected static final Logger LOGGER = StatusLogger.getLogger();
+
+ protected Ann annotation;
+ protected AnnotatedElement annotatedElement;
+ protected Type conversionType;
+ protected String name;
+ protected Collection<String> aliases = Collections.emptyList();
+ protected ConfigurationBinder configurationBinder;
+ protected StringBuilder debugLog;
+ protected Function<String, String> stringSubstitutionStrategy = Function.identity();
+ protected Cfg configuration;
+ protected Node node;
+
+ @Override
+ public ConfigurationInjector<Ann, Cfg> withAnnotation(final Ann annotation) {
+ this.annotation = Objects.requireNonNull(annotation);
+ return this;
+ }
+
+ @Override
+ public ConfigurationInjector<Ann, Cfg> withAnnotatedElement(final AnnotatedElement element) {
+ this.annotatedElement = Objects.requireNonNull(element);
+ withName(AnnotatedElementNameProvider.getName(element));
+ final PluginAliases aliases = element.getAnnotation(PluginAliases.class);
+ if (aliases != null) {
+ withAliases(aliases.value());
+ }
+ return this;
+ }
+
+ @Override
+ public ConfigurationInjector<Ann, Cfg> withConversionType(final Type type) {
+ this.conversionType = Objects.requireNonNull(type);
+ return this;
+ }
+
+ @Override
+ public ConfigurationInjector<Ann, Cfg> withName(final String name) {
+ this.name = Objects.requireNonNull(name);
+ return this;
+ }
+
+ @Override
+ public ConfigurationInjector<Ann, Cfg> withAliases(final String... aliases) {
+ this.aliases = Arrays.asList(aliases);
+ return this;
+ }
+
+ @Override
+ public ConfigurationInjector<Ann, Cfg> withConfigurationBinder(final ConfigurationBinder binder) {
+ this.configurationBinder = binder;
+ return this;
+ }
+
+ @Override
+ public ConfigurationInjector<Ann, Cfg> withDebugLog(final StringBuilder debugLog) {
+ this.debugLog = Objects.requireNonNull(debugLog);
+ return this;
+ }
+
+ @Override
+ public ConfigurationInjector<Ann, Cfg> withStringSubstitutionStrategy(final Function<String, String> strategy) {
+ this.stringSubstitutionStrategy = Objects.requireNonNull(strategy);
+ return this;
+ }
+
+ @Override
+ public ConfigurationInjector<Ann, Cfg> withConfiguration(final Cfg configuration) {
+ this.configuration = Objects.requireNonNull(configuration);
+ return this;
+ }
+
+ @Override
+ public ConfigurationInjector<Ann, Cfg> withNode(final Node node) {
+ this.node = Objects.requireNonNull(node);
+ return this;
+ }
+
+ protected Optional<String> findAndRemoveNodeAttribute() {
+ Objects.requireNonNull(node);
+ Objects.requireNonNull(name);
+ final Map<String, String> attributes = node.getAttributes();
+ for (final String key : attributes.keySet()) {
+ if (key.equalsIgnoreCase(name)) {
+ return Optional.ofNullable(attributes.remove(key));
+ }
+ for (final String alias : aliases) {
+ if (key.equalsIgnoreCase(alias)) {
+ return Optional.ofNullable(attributes.remove(key));
+ }
+ }
+ }
+ return Optional.empty();
+ }
+
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/ConfigurationInjector.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/ConfigurationInjector.java
new file mode 100644
index 0000000..f0db24d
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/ConfigurationInjector.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.plugins.inject;
+
+import org.apache.logging.log4j.plugins.Node;
+import org.apache.logging.log4j.plugins.bind.ConfigurationBinder;
+import org.apache.logging.log4j.util.ReflectionUtil;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.AnnotatedElement;
+import java.lang.reflect.Type;
+import java.util.Optional;
+import java.util.function.Function;
+
+/**
+ * Strategy builder for injecting configuration data into an {@link AnnotatedElement}. Configuration injection consists
+ * of {@linkplain ConfigurationBinder binding} a {@link Node} and configuration to an annotated element of a
+ * {@linkplain org.apache.logging.log4j.plugins.PluginFactory plugin factory}.
+ *
+ * @param <Ann> plugin annotation this injector uses
+ * @param <Cfg> configuration class
+ */
+public interface ConfigurationInjector<Ann extends Annotation, Cfg> {
+
+ static <Cfg> Optional<ConfigurationInjector<Annotation, Cfg>> forAnnotatedElement(final AnnotatedElement element) {
+ for (final Annotation annotation : element.getAnnotations()) {
+ final InjectorStrategy strategy = annotation.annotationType().getAnnotation(InjectorStrategy.class);
+ if (strategy != null) {
+ @SuppressWarnings("unchecked") final ConfigurationInjector<Annotation, Cfg> injector =
+ (ConfigurationInjector<Annotation, Cfg>) ReflectionUtil.instantiate(strategy.value());
+ return Optional.of(injector.withAnnotatedElement(element).withAnnotation(annotation));
+ }
+ }
+ return Optional.empty();
+ }
+
+ ConfigurationInjector<Ann, Cfg> withAnnotation(final Ann annotation);
+
+ ConfigurationInjector<Ann, Cfg> withAnnotatedElement(final AnnotatedElement element);
+
+ ConfigurationInjector<Ann, Cfg> withConversionType(final Type type);
+
+ ConfigurationInjector<Ann, Cfg> withName(final String name);
+
+ ConfigurationInjector<Ann, Cfg> withAliases(final String... aliases);
+
+ ConfigurationInjector<Ann, Cfg> withConfigurationBinder(final ConfigurationBinder binder);
+
+ ConfigurationInjector<Ann, Cfg> withDebugLog(final StringBuilder debugLog);
+
+ ConfigurationInjector<Ann, Cfg> withStringSubstitutionStrategy(final Function<String, String> strategy);
+
+ ConfigurationInjector<Ann, Cfg> withConfiguration(final Cfg configuration);
+
+ ConfigurationInjector<Ann, Cfg> withNode(final Node node);
+
+ void inject(final Object factory);
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/InjectorStrategy.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/InjectorStrategy.java
new file mode 100644
index 0000000..66d9507
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/InjectorStrategy.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.plugins.inject;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.ANNOTATION_TYPE)
+// TODO: annotation processor to validate type matches (help avoid runtime errors)
+public @interface InjectorStrategy {
+ Class<? extends ConfigurationInjector<? extends Annotation, ?>> value();
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/PluginAttributeInjector.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/PluginAttributeInjector.java
new file mode 100644
index 0000000..af1144f
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/PluginAttributeInjector.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.plugins.inject;
+
+import org.apache.logging.log4j.plugins.PluginAttribute;
+import org.apache.logging.log4j.util.NameUtil;
+import org.apache.logging.log4j.util.StringBuilders;
+import org.apache.logging.log4j.util.Strings;
+
+import java.lang.reflect.Type;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Function;
+
+public class PluginAttributeInjector extends AbstractConfigurationInjector<PluginAttribute, Object> {
+
+ private static final Map<Type, Function<PluginAttribute, Object>> DEFAULT_VALUE_EXTRACTORS;
+
+ static {
+ final Map<Class<?>, Function<PluginAttribute, Object>> extractors = new ConcurrentHashMap<>();
+ extractors.put(int.class, PluginAttribute::defaultInt);
+ extractors.put(Integer.class, PluginAttribute::defaultInt);
+ extractors.put(long.class, PluginAttribute::defaultLong);
+ extractors.put(Long.class, PluginAttribute::defaultLong);
+ extractors.put(boolean.class, PluginAttribute::defaultBoolean);
+ extractors.put(Boolean.class, PluginAttribute::defaultBoolean);
+ extractors.put(float.class, PluginAttribute::defaultFloat);
+ extractors.put(Float.class, PluginAttribute::defaultFloat);
+ extractors.put(double.class, PluginAttribute::defaultDouble);
+ extractors.put(Double.class, PluginAttribute::defaultDouble);
+ extractors.put(byte.class, PluginAttribute::defaultByte);
+ extractors.put(Byte.class, PluginAttribute::defaultByte);
+ extractors.put(char.class, PluginAttribute::defaultChar);
+ extractors.put(Character.class, PluginAttribute::defaultChar);
+ extractors.put(short.class, PluginAttribute::defaultShort);
+ extractors.put(Short.class, PluginAttribute::defaultShort);
+ extractors.put(Class.class, PluginAttribute::defaultClass);
+ DEFAULT_VALUE_EXTRACTORS = Collections.unmodifiableMap(extractors);
+ }
+
+ @Override
+ public void inject(final Object factory) {
+ final Optional<String> value = findAndRemoveNodeAttribute().map(stringSubstitutionStrategy);
+ if (value.isPresent()) {
+ configurationBinder.bindString(factory, value.get());
+ } else {
+ injectDefaultValue(factory);
+ }
+ }
+
+ private void injectDefaultValue(final Object factory) {
+ final Function<PluginAttribute, Object> extractor = DEFAULT_VALUE_EXTRACTORS.get(conversionType);
+ if (extractor != null) {
+ final Object value = extractor.apply(annotation);
+ debugLog(value);
+ configurationBinder.bindObject(factory, value);
+ } else {
+ final String value = stringSubstitutionStrategy.apply(annotation.defaultString());
+ if (Strings.isNotBlank(value)) {
+ debugLog(value);
+ configurationBinder.bindString(factory, value);
+ }
+ }
+ }
+
+ private void debugLog(final Object value) {
+ final Object debugValue = annotation.sensitive() ? NameUtil.md5(value + getClass().getName()) : value;
+ StringBuilders.appendKeyDqValue(debugLog, name, debugValue);
+ }
+
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/PluginBuilderAttributeInjector.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/PluginBuilderAttributeInjector.java
new file mode 100644
index 0000000..f1d734d
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/PluginBuilderAttributeInjector.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.plugins.inject;
+
+import org.apache.logging.log4j.plugins.PluginBuilderAttribute;
+import org.apache.logging.log4j.util.NameUtil;
+import org.apache.logging.log4j.util.StringBuilders;
+
+import java.util.Optional;
+
+public class PluginBuilderAttributeInjector extends AbstractConfigurationInjector<PluginBuilderAttribute, Object> {
+ @Override
+ public void inject(final Object factory) {
+ final Optional<String> value = findAndRemoveNodeAttribute().map(stringSubstitutionStrategy);
+ if (value.isPresent()) {
+ final String str = value.get();
+ debugLog(str);
+ configurationBinder.bindString(factory, str);
+ } else {
+ debugLog.append(name).append("=null");
+ configurationBinder.bindObject(factory, null);
+ }
+ }
+
+ private void debugLog(final Object value) {
+ final Object debugValue = annotation.sensitive() ? NameUtil.md5(value + getClass().getName()) : value;
+ StringBuilders.appendKeyDqValue(debugLog, name, debugValue);
+ }
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/PluginElementInjector.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/PluginElementInjector.java
new file mode 100644
index 0000000..21c74be
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/PluginElementInjector.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.plugins.inject;
+
+import org.apache.logging.log4j.plugins.Node;
+import org.apache.logging.log4j.plugins.PluginElement;
+import org.apache.logging.log4j.plugins.util.PluginType;
+import org.apache.logging.log4j.plugins.util.TypeUtil;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.Optional;
+
+public class PluginElementInjector extends AbstractConfigurationInjector<PluginElement, Object> {
+ @Override
+ public void inject(final Object factory) {
+ final Optional<Class<?>> componentType = getComponentType(conversionType);
+ if (componentType.isPresent()) {
+ final Class<?> compType = componentType.get();
+ final List<Object> values = new ArrayList<>();
+ final Collection<Node> used = new ArrayList<>();
+ debugLog.append("={");
+ boolean first = true;
+ for (final Node child : node.getChildren()) {
+ final PluginType<?> type = child.getType();
+ if (name.equalsIgnoreCase(type.getElementName()) || compType.isAssignableFrom(type.getPluginClass())) {
+ if (!first) {
+ debugLog.append(", ");
+ }
+ first = false;
+ used.add(child);
+ final Object childObject = child.getObject();
+ if (childObject == null) {
+ LOGGER.warn("Skipping null object returned for element {} in node {}", child.getName(), node.getName());
+ } else if (childObject.getClass().isArray()) {
+ Object[] children = (Object[]) childObject;
+ debugLog.append(Arrays.toString(children)).append('}');
+ node.getChildren().removeAll(used);
+ configurationBinder.bindObject(factory, children);
+ return;
+ } else {
+ debugLog.append(child.toString());
+ values.add(childObject);
+ }
+ }
+ }
+ debugLog.append('}');
+ if (!values.isEmpty() && !TypeUtil.isAssignable(compType, values.get(0).getClass())) {
+ LOGGER.error("Cannot assign element {} a list of {} as it is incompatible with {}", name, values.get(0).getClass(), compType);
+ return;
+ }
+ node.getChildren().removeAll(used);
+ // using List::toArray here would cause type mismatch later on
+ final Object[] vals = (Object[]) Array.newInstance(compType, values.size());
+ for (int i = 0; i < vals.length; i++) {
+ vals[i] = values.get(i);
+ }
+ configurationBinder.bindObject(factory, vals);
+ } else {
+ final Optional<Node> matchingChild = node.getChildren().stream().filter(this::isRequestedNode).findAny();
+ if (matchingChild.isPresent()) {
+ final Node child = matchingChild.get();
+ debugLog.append(child.getName()).append('(').append(child.toString()).append(')');
+ node.getChildren().remove(child);
+ configurationBinder.bindObject(factory, child.getObject());
+ } else {
+ debugLog.append(name).append("=null");
+ configurationBinder.bindObject(factory, null);
+ }
+ }
+ }
+
+ private boolean isRequestedNode(final Node child) {
+ final PluginType<?> type = child.getType();
+ return name.equalsIgnoreCase(type.getElementName()) || TypeUtil.isAssignable(conversionType, type.getPluginClass());
+ }
+
+ private static Optional<Class<?>> getComponentType(final Type type) {
+ if (type instanceof Class<?>) {
+ final Class<?> clazz = (Class<?>) type;
+ if (clazz.isArray()) {
+ return Optional.of(clazz.getComponentType());
+ }
+ }
+ return Optional.empty();
+ }
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/PluginNodeInjector.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/PluginNodeInjector.java
new file mode 100644
index 0000000..23d19ff
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/PluginNodeInjector.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.plugins.inject;
+
+import org.apache.logging.log4j.plugins.PluginNode;
+import org.apache.logging.log4j.plugins.util.TypeUtil;
+
+public class PluginNodeInjector extends AbstractConfigurationInjector<PluginNode, Object> {
+ @Override
+ public void inject(final Object factory) {
+ if (TypeUtil.isAssignable(conversionType, node.getClass())) {
+ debugLog.append("Node=").append(node.getName());
+ configurationBinder.bindObject(factory, node);
+ } else {
+ LOGGER.error("Element with type {} annotated with @PluginNode not compatible with type {}.", conversionType, node.getClass());
+ }
+ }
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/PluginValueInjector.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/PluginValueInjector.java
new file mode 100644
index 0000000..8d4cade
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/PluginValueInjector.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.plugins.inject;
+
+import org.apache.logging.log4j.plugins.PluginValue;
+import org.apache.logging.log4j.util.StringBuilders;
+import org.apache.logging.log4j.util.Strings;
+
+public class PluginValueInjector extends AbstractConfigurationInjector<PluginValue, Object> {
+ @Override
+ public void inject(final Object factory) {
+ final String elementValue = node.getValue();
+ final String attributeValue = node.getAttributes().get(name);
+ String rawValue = null; // if neither is specified, return null (LOG4J2-1313)
+ if (Strings.isNotEmpty(elementValue)) {
+ if (Strings.isNotEmpty(attributeValue)) {
+ LOGGER.error("Configuration contains {} with both attribute value ({}) AND element" +
+ " value ({}). Please specify only one value. Using the element value.",
+ node.getName(), attributeValue, elementValue);
+ }
+ rawValue = elementValue;
+ } else {
+ rawValue = findAndRemoveNodeAttribute().orElse(null);
+ }
+ final String value = stringSubstitutionStrategy.apply(rawValue);
+ StringBuilders.appendKeyDqValue(debugLog, name, value);
+ configurationBinder.bindString(factory, value);
+ }
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/package-info.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/package-info.java
new file mode 100644
index 0000000..a2afb2b
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/inject/package-info.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.
+ */
+
+/**
+ * Injection builder classes for parsing data from a {@code Configuration} or {@link org.apache.logging.log4j.plugins.Node}
+ * corresponding to an {@link org.apache.logging.log4j.plugins.inject.InjectorStrategy}-annotated annotation.
+ * Injection strategies must implement {@link org.apache.logging.log4j.plugins.inject.ConfigurationInjector}.
+ */
+package org.apache.logging.log4j.plugins.inject;
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/name/AnnotatedElementNameProvider.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/name/AnnotatedElementNameProvider.java
new file mode 100644
index 0000000..34349d6
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/name/AnnotatedElementNameProvider.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.plugins.name;
+
+import org.apache.logging.log4j.util.ReflectionUtil;
+
+import java.beans.Introspector;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.AnnotatedElement;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import java.lang.reflect.Parameter;
+import java.util.Optional;
+
+/**
+ * Extracts a specified name for some configurable annotated element. A specified name is one given in a non-empty
+ * string in an annotation as opposed to relying on the default name taken from the annotated element itself.
+ *
+ * @param <A> plugin configuration annotation
+ */
+public interface AnnotatedElementNameProvider<A extends Annotation> {
+
+ static String getName(final AnnotatedElement element) {
+ for (final Annotation annotation : element.getAnnotations()) {
+ final Optional<String> specifiedName = getSpecifiedNameForAnnotation(annotation);
+ if (specifiedName.isPresent()) {
+ return specifiedName.get();
+ }
+ }
+
+ if (element instanceof Field) {
+ return ((Field) element).getName();
+ }
+
+ if (element instanceof Method) {
+ final Method method = (Method) element;
+ final String methodName = method.getName();
+ if (methodName.startsWith("set")) {
+ return Introspector.decapitalize(methodName.substring(3));
+ }
+ if (methodName.startsWith("with")) {
+ return Introspector.decapitalize(methodName.substring(4));
+ }
+ return methodName;
+ }
+
+ if (element instanceof Parameter) {
+ return ((Parameter) element).getName();
+ }
+
+ throw new IllegalArgumentException("Unknown element type for naming: " + element.getClass());
+ }
+
+ static <A extends Annotation> Optional<String> getSpecifiedNameForAnnotation(final A annotation) {
+ return Optional.ofNullable(annotation.annotationType().getAnnotation(NameProvider.class))
+ .map(NameProvider::value)
+ .flatMap(clazz -> {
+ @SuppressWarnings("unchecked") final AnnotatedElementNameProvider<A> factory =
+ (AnnotatedElementNameProvider<A>) ReflectionUtil.instantiate(clazz);
+ return factory.getSpecifiedName(annotation);
+ });
+ }
+
+ /**
+ * Returns the specified name from this annotation if given or {@code Optional.empty()} if none given.
+ *
+ * @param annotation annotation value of configuration element
+ * @return specified name of configuration element or empty if none specified
+ */
+ Optional<String> getSpecifiedName(final A annotation);
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/name/NameProvider.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/name/NameProvider.java
new file mode 100644
index 0000000..ac9e643
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/name/NameProvider.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.plugins.name;
+
+import java.lang.annotation.Annotation;
+import java.lang.annotation.Documented;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.ANNOTATION_TYPE)
+public @interface NameProvider {
+ Class<? extends AnnotatedElementNameProvider<? extends Annotation>> value();
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/name/PluginAttributeNameProvider.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/name/PluginAttributeNameProvider.java
new file mode 100644
index 0000000..3792a4d
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/name/PluginAttributeNameProvider.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.plugins.name;
+
+import org.apache.logging.log4j.plugins.PluginAttribute;
+import org.apache.logging.log4j.util.Strings;
+
+import java.util.Optional;
+
+public class PluginAttributeNameProvider implements AnnotatedElementNameProvider<PluginAttribute> {
+ @Override
+ public Optional<String> getSpecifiedName(final PluginAttribute annotation) {
+ return Strings.trimToOptional(annotation.value());
+ }
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/name/PluginBuilderAttributeNameProvider.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/name/PluginBuilderAttributeNameProvider.java
new file mode 100644
index 0000000..0c32fe8
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/name/PluginBuilderAttributeNameProvider.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.plugins.name;
+
+import org.apache.logging.log4j.plugins.PluginBuilderAttribute;
+import org.apache.logging.log4j.util.Strings;
+
+import java.util.Optional;
+
+public class PluginBuilderAttributeNameProvider implements AnnotatedElementNameProvider<PluginBuilderAttribute> {
+ @Override
+ public Optional<String> getSpecifiedName(final PluginBuilderAttribute annotation) {
+ return Strings.trimToOptional(annotation.value());
+ }
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/name/PluginElementNameProvider.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/name/PluginElementNameProvider.java
new file mode 100644
index 0000000..53122fb
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/name/PluginElementNameProvider.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.plugins.name;
+
+import org.apache.logging.log4j.plugins.PluginElement;
+import org.apache.logging.log4j.util.Strings;
+
+import java.util.Optional;
+
+public class PluginElementNameProvider implements AnnotatedElementNameProvider<PluginElement> {
+ @Override
+ public Optional<String> getSpecifiedName(final PluginElement annotation) {
+ return Strings.trimToOptional(annotation.value());
+ }
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/name/PluginValueNameProvider.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/name/PluginValueNameProvider.java
new file mode 100644
index 0000000..536db27
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/name/PluginValueNameProvider.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.plugins.name;
+
+import org.apache.logging.log4j.plugins.PluginValue;
+import org.apache.logging.log4j.util.Strings;
+
+import java.util.Optional;
+
+public class PluginValueNameProvider implements AnnotatedElementNameProvider<PluginValue> {
+ @Override
+ public Optional<String> getSpecifiedName(final PluginValue annotation) {
+ return Strings.trimToOptional(annotation.value());
+ }
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/osgi/Activator.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/osgi/Activator.java
new file mode 100644
index 0000000..2364aa9
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/osgi/Activator.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.plugins.osgi;
+
+import org.apache.logging.log4j.Level;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.plugins.processor.PluginService;
+import org.apache.logging.log4j.plugins.util.PluginRegistry;
+import org.apache.logging.log4j.status.StatusLogger;
+import org.osgi.framework.*;
+import org.osgi.framework.wiring.BundleWiring;
+
+import java.security.Permission;
+import java.util.Collection;
+import java.util.Hashtable;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * OSGi BundleActivator.
+ */
+public final class Activator implements BundleActivator, SynchronousBundleListener {
+
+ private static final Logger LOGGER = StatusLogger.getLogger();
+
+ private static final SecurityManager SECURITY_MANAGER = System.getSecurityManager();
+
+ private final AtomicReference<BundleContext> contextRef = new AtomicReference<>();
+
+ @Override
+ public void start(final BundleContext bundleContext) throws Exception {
+ loadPlugins(bundleContext);
+ bundleContext.addBundleListener(this);
+ final Bundle[] bundles = bundleContext.getBundles();
+ for (final Bundle bundle : bundles) {
+ loadPlugins(bundle);
+ }
+ scanInstalledBundlesForPlugins(bundleContext);
+ this.contextRef.compareAndSet(null, bundleContext);
+ }
+
+ private void loadPlugins(final BundleContext bundleContext) {
+ try {
+ final Collection<ServiceReference<PluginService>> serviceReferences = bundleContext.getServiceReferences(PluginService.class, null);
+ for (final ServiceReference<PluginService> serviceReference : serviceReferences) {
+ final PluginService pluginService = bundleContext.getService(serviceReference);
+ PluginRegistry.getInstance().loadFromBundle(pluginService.getCategories(), bundleContext.getBundle().getBundleId());
+ }
+ } catch (final InvalidSyntaxException ex) {
+ LOGGER.error("Error accessing Plugins", ex);
+ }
+ }
+
+ private void loadPlugins(final Bundle bundle) {
+ if (bundle.getState() == Bundle.UNINSTALLED) {
+ return;
+ }
+ try {
+ checkPermission(new AdminPermission(bundle, AdminPermission.RESOURCE));
+ 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 plugins", bundle.getSymbolicName(), toStateString(bundle.getState()));
+ } else {
+ loadPlugins(bundleContext);
+ }
+ } catch (final SecurityException e) {
+ LOGGER.debug("Cannot access bundle [{}] contents. Ignoring.", bundle.getSymbolicName(), e);
+ } catch (final Exception e) {
+ LOGGER.warn("Problem checking bundle {} for Log4j 2 provider.", bundle.getSymbolicName(), e);
+ }
+ }
+
+ private static void checkPermission(final Permission permission) {
+ if (SECURITY_MANAGER != null) {
+ SECURITY_MANAGER.checkPermission(permission);
+ }
+ }
+
+ 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);
+ }
+ }
+
+ private static void scanInstalledBundlesForPlugins(final BundleContext context) {
+ final Bundle[] bundles = context.getBundles();
+ for (final Bundle bundle : bundles) {
+ // TODO: bundle state can change during this
+ scanBundleForPlugins(bundle);
+ }
+ }
+
+ private static void scanBundleForPlugins(final Bundle bundle) {
+ final long bundleId = bundle.getBundleId();
+ // LOG4J2-920: don't scan system bundle for plugins
+ if (bundle.getState() == Bundle.ACTIVE && bundleId != 0) {
+ LOGGER.trace("Scanning bundle [{}, id=%d] for plugins.", bundle.getSymbolicName(), bundleId);
+ PluginRegistry.getInstance().loadFromBundle(bundleId,
+ bundle.adapt(BundleWiring.class).getClassLoader());
+ }
+ }
+
+ private static void stopBundlePlugins(final Bundle bundle) {
+ LOGGER.trace("Stopping bundle [{}] plugins.", bundle.getSymbolicName());
+ // TODO: plugin lifecycle code
+ PluginRegistry.getInstance().clearBundlePlugins(bundle.getBundleId());
+ }
+
+ @Override
+ public void stop(final BundleContext context) throws Exception {
+ final Bundle[] bundles = context.getBundles();
+ for (final Bundle bundle : bundles) {
+ stopBundlePlugins(bundle);
+ }
+ stopBundlePlugins(context.getBundle());
+ this.contextRef.compareAndSet(context, null);
+ }
+
+ @Override
+ public void bundleChanged(final BundleEvent event) {
+ switch (event.getType()) {
+ // FIXME: STARTING instead of STARTED?
+ case BundleEvent.STARTED:
+ loadPlugins(event.getBundle());
+ scanBundleForPlugins(event.getBundle());
+ break;
+
+ case BundleEvent.STOPPING:
+ stopBundlePlugins(event.getBundle());
+ break;
+
+ default:
+ break;
+ }
+ }
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/osgi/package-info.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/osgi/package-info.java
new file mode 100644
index 0000000..37dadd4
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/osgi/package-info.java
@@ -0,0 +1,21 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+
+/**
+ * Collection of OSGi-specific classes for bundles.
+ */
+package org.apache.logging.log4j.plugins.osgi;
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/package-info.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/package-info.java
new file mode 100644
index 0000000..b515d96
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/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.
+ */
+
+/**
+ * Annotations for Log4j 2 plugins.
+ *
+ * @see org.apache.logging.log4j.plugins.Plugin
+ * @see org.apache.logging.log4j.plugins.PluginFactory
+ */
+package org.apache.logging.log4j.plugins;
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/processor/PluginCache.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/processor/PluginCache.java
new file mode 100644
index 0000000..5f64a2b
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/processor/PluginCache.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.plugins.processor;
+
+import java.io.BufferedInputStream;
+import java.io.DataInputStream;
+import java.io.IOException;
+import java.net.URL;
+import java.util.Enumeration;
+import java.util.Map;
+import java.util.TreeMap;
+
+/**
+ *
+ */
+public class PluginCache {
+ private final Map<String, Map<String, PluginEntry>> categories =
+ new TreeMap<>();
+
+ /**
+ * Returns all categories of plugins in this cache.
+ *
+ * @return all categories of plugins in this cache.
+ * @since 2.1
+ */
+ public Map<String, Map<String, PluginEntry>> getAllCategories() {
+ return categories;
+ }
+
+ /**
+ * Gets or creates a category of plugins.
+ *
+ * @param category name of category to look up.
+ * @return plugin mapping of names to plugin entries.
+ */
+ public Map<String, PluginEntry> getCategory(final String category) {
+ final String key = category.toLowerCase();
+ return categories.computeIfAbsent(key, ignored -> new TreeMap<>());
+ }
+
+ /**
+ * Loads and merges all the Log4j plugin cache files specified. Usually, this is obtained via a ClassLoader.
+ *
+ * @param resources URLs to all the desired plugin cache files to load.
+ * @throws IOException if an I/O exception occurs.
+ */
+ public void loadCacheFiles(final Enumeration<URL> resources) throws IOException {
+ categories.clear();
+ while (resources.hasMoreElements()) {
+ final URL url = resources.nextElement();
+ try (final DataInputStream in = new DataInputStream(new BufferedInputStream(url.openStream()))) {
+ final int count = in.readInt();
+ for (int i = 0; i < count; i++) {
+ final String category = in.readUTF();
+ final Map<String, PluginEntry> m = getCategory(category);
+ final int entries = in.readInt();
+ for (int j = 0; j < entries; j++) {
+ // Must always read all parts of the entry, even if not adding, so that the stream progresses
+ final String key = in.readUTF();
+ final String className = in.readUTF();
+ final String name = in.readUTF();
+ final boolean printable = in.readBoolean();
+ final boolean defer = in.readBoolean();
+ m.computeIfAbsent(key, k -> new PluginEntry(k, className, name, printable, defer, category));
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Gets the number of plugin categories registered.
+ *
+ * @return number of plugin categories in cache.
+ */
+ public int size() {
+ return categories.size();
+ }
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/processor/PluginEntry.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/processor/PluginEntry.java
new file mode 100644
index 0000000..bd452d3
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/processor/PluginEntry.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.plugins.processor;
+
+import java.io.Serializable;
+
+/**
+ * Memento object for storing a plugin entry to a cache file.
+ */
+public class PluginEntry implements Serializable {
+ private static final long serialVersionUID = 1L;
+
+ private String key;
+ private String className;
+ private String name;
+ private boolean printable;
+ private boolean defer;
+ private transient String category;
+
+ public PluginEntry() {
+ }
+
+ public PluginEntry(String key, String className, String name, boolean printable, boolean defer, String category) {
+ this.key = key;
+ this.className = className;
+ this.name = name;
+ this.printable = printable;
+ this.defer = defer;
+ this.category = category;
+ }
+
+ public String getKey() {
+ return key;
+ }
+
+ public void setKey(final String key) {
+ this.key = key;
+ }
+
+ public String getClassName() {
+ return className;
+ }
+
+ public void setClassName(final String className) {
+ this.className = className;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(final String name) {
+ this.name = name;
+ }
+
+ public boolean isPrintable() {
+ return printable;
+ }
+
+ public void setPrintable(final boolean printable) {
+ this.printable = printable;
+ }
+
+ public boolean isDefer() {
+ return defer;
+ }
+
+ public void setDefer(final boolean defer) {
+ this.defer = defer;
+ }
+
+ public String getCategory() {
+ return category;
+ }
+
+ public void setCategory(final String category) {
+ this.category = category;
+ }
+
+ @Override
+ public String toString() {
+ return "PluginEntry [key=" + key + ", className=" + className + ", name=" + name + ", printable=" + printable
+ + ", defer=" + defer + ", category=" + category + "]";
+ }
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/processor/PluginProcessor.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/processor/PluginProcessor.java
new file mode 100644
index 0000000..6ccfdd2
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/processor/PluginProcessor.java
@@ -0,0 +1,280 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+
+package org.apache.logging.log4j.plugins.processor;
+
+import org.apache.logging.log4j.LoggingException;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginAliases;
+import org.apache.logging.log4j.util.Strings;
+
+import javax.annotation.processing.AbstractProcessor;
+import javax.annotation.processing.Messager;
+import javax.annotation.processing.RoundEnvironment;
+import javax.annotation.processing.SupportedAnnotationTypes;
+import javax.lang.model.SourceVersion;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementVisitor;
+import javax.lang.model.element.Name;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.util.Elements;
+import javax.lang.model.util.SimpleElementVisitor7;
+import javax.tools.Diagnostic.Kind;
+import javax.tools.FileObject;
+import javax.tools.JavaFileObject;
+import javax.tools.StandardLocation;
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+/**
+ * Annotation processor for pre-scanning Log4j 2 plugins.
+ */
+@SupportedAnnotationTypes({"org.apache.logging.log4j.plugins.*", "org.apache.logging.log4j.core.config.plugins.*"})
+public class PluginProcessor extends AbstractProcessor {
+
+ // TODO: this could be made more abstract to allow for compile-time and run-time plugin processing
+
+ /**
+ * The location of the plugin cache data file. This file is written to by this processor, and read from by
+ * {@link org.apache.logging.log4j.plugins.util.PluginManager}.
+ */
+ public static final String PLUGIN_CACHE_FILE =
+ "META-INF/org/apache/logging/log4j/core/config/plugins/Log4j2Plugins.dat";
+ private static final String SERVICE_FILE_NAME =
+ "META-INF/services/org.apache.logging.log4j.plugins.processor.PluginService";
+
+ @Override
+ public SourceVersion getSupportedSourceVersion() {
+ return SourceVersion.latest();
+ }
+
+ @Override
+ public boolean process(final Set<? extends TypeElement> annotations, final RoundEnvironment roundEnv) {
+ Map<String, String> options = processingEnv.getOptions();
+ String packageName = options.get("pluginPackage");
+ Messager messager = processingEnv.getMessager();
+ messager.printMessage(Kind.NOTE, "Processing Log4j annotations");
+ try {
+ final Set<? extends Element> elements = roundEnv.getElementsAnnotatedWith(Plugin.class);
+ if (elements.isEmpty()) {
+ messager.printMessage(Kind.NOTE, "No elements to process");
+ return false;
+ }
+ List<PluginEntry> list = new ArrayList<>();
+ packageName = collectPlugins(packageName, elements, list);
+ writeClassFile(packageName, list);
+ writeServiceFile(packageName);
+ messager.printMessage(Kind.NOTE, "Annotations processed");
+ return true;
+ } catch (final IOException e) {
+ e.printStackTrace();
+ error(e.getMessage());
+ return false;
+ } catch (final Exception ex) {
+ ex.printStackTrace();
+ error(ex.getMessage());
+ return false;
+ }
+ }
+
+ private void error(final CharSequence message) {
+ processingEnv.getMessager().printMessage(Kind.ERROR, message);
+ }
+
+ private String collectPlugins(String packageName, final Iterable<? extends Element> elements, List<PluginEntry> list) {
+ boolean calculatePackage = packageName == null;
+ final Elements elementUtils = processingEnv.getElementUtils();
+ final ElementVisitor<PluginEntry, Plugin> pluginVisitor = new PluginElementVisitor(elementUtils);
+ final ElementVisitor<Collection<PluginEntry>, Plugin> pluginAliasesVisitor = new PluginAliasesElementVisitor(
+ elementUtils);
+ for (final Element element : elements) {
+ final Plugin plugin = element.getAnnotation(Plugin.class);
+ if (plugin == null) {
+ continue;
+ }
+ final PluginEntry entry = element.accept(pluginVisitor, plugin);
+ list.add(entry);
+ if (calculatePackage) {
+ packageName = calculatePackage(elementUtils, element, packageName);
+ }
+ final Collection<PluginEntry> entries = element.accept(pluginAliasesVisitor, plugin);
+ for (final PluginEntry pluginEntry : entries) {
+ list.add(pluginEntry);
+ }
+ }
+ return packageName;
+ }
+
+ private String calculatePackage(Elements elements, Element element, String packageName) {
+ Name name = elements.getPackageOf(element).getQualifiedName();
+ if (name == null) {
+ return null;
+ }
+ String pkgName = name.toString();
+ if (packageName == null) {
+ return pkgName;
+ }
+ if (pkgName.length() == packageName.length()) {
+ return packageName;
+ }
+ if (pkgName.length() < packageName.length() && packageName.startsWith(pkgName)) {
+ return pkgName;
+ }
+
+ return commonPrefix(pkgName, packageName);
+ }
+
+ private void writeServiceFile(String pkgName) throws IOException {
+ final FileObject fileObject = processingEnv.getFiler().createResource(StandardLocation.CLASS_OUTPUT, Strings.EMPTY,
+ SERVICE_FILE_NAME);
+ try (final PrintWriter writer = new PrintWriter(new BufferedWriter(new OutputStreamWriter(fileObject.openOutputStream(), UTF_8)))) {
+ writer.println(createFqcn(pkgName));
+ }
+ }
+
+ private void writeClassFile(String pkg, List<PluginEntry> list) {
+ String fqcn = createFqcn(pkg);
+ try (final PrintWriter writer = createSourceFile(fqcn)) {
+ writer.println("package " + pkg + ".plugins;");
+ writer.println("");
+ writer.println("import org.apache.logging.log4j.plugins.processor.PluginEntry;");
+ writer.println("import org.apache.logging.log4j.plugins.processor.PluginService;");
+ writer.println("");
+ writer.println("public class Log4jPlugins extends PluginService {");
+ writer.println("");
+ writer.println(" private static PluginEntry[] entries = new PluginEntry[] {");
+ StringBuilder sb = new StringBuilder();
+ int max = list.size() - 1;
+ for (int i = 0; i < list.size(); ++i) {
+ PluginEntry entry = list.get(i);
+ sb.append(" ").append("new PluginEntry(\"");
+ sb.append(entry.getKey()).append("\", \"");
+ sb.append(entry.getClassName()).append("\", \"");
+ sb.append(entry.getName()).append("\", ");
+ sb.append(entry.isPrintable()).append(", ");
+ sb.append(entry.isDefer()).append(", \"");
+ sb.append(entry.getCategory()).append("\")");
+ if (i < max) {
+ sb.append(",");
+ }
+ writer.println(sb.toString());
+ sb.setLength(0);
+ }
+ writer.println(" };");
+ writer.println(" @Override");
+ writer.println(" public PluginEntry[] getEntries() { return entries;}");
+ writer.println("}");
+ }
+ }
+
+ private PrintWriter createSourceFile(String fqcn) {
+ try {
+ JavaFileObject sourceFile = processingEnv.getFiler().createSourceFile(fqcn);
+ return new PrintWriter(sourceFile.openWriter());
+ } catch (IOException e) {
+ throw new LoggingException("Unable to create Plugin Service Class " + fqcn, e);
+ }
+ }
+
+ private String createFqcn(String packageName) {
+ return packageName + ".plugins.Log4jPlugins";
+ }
+
+ /**
+ * ElementVisitor to scan the Plugin annotation.
+ */
+ private static class PluginElementVisitor extends SimpleElementVisitor7<PluginEntry, Plugin> {
+
+ private final Elements elements;
+
+ private PluginElementVisitor(final Elements elements) {
+ this.elements = elements;
+ }
+
+ @Override
+ public PluginEntry visitType(final TypeElement e, final Plugin plugin) {
+ Objects.requireNonNull(plugin, "Plugin annotation is null.");
+ final PluginEntry entry = new PluginEntry();
+ entry.setKey(plugin.name().toLowerCase(Locale.US));
+ entry.setClassName(elements.getBinaryName(e).toString());
+ entry.setName(Plugin.EMPTY.equals(plugin.elementType()) ? plugin.name() : plugin.elementType());
+ entry.setPrintable(plugin.printObject());
+ entry.setDefer(plugin.deferChildren());
+ entry.setCategory(plugin.category());
+ return entry;
+ }
+ }
+
+ private String commonPrefix(String str1, String str2) {
+ int minLength = str1.length() < str2.length() ? str1.length() : str2.length();
+ for (int i = 0; i < minLength; i++) {
+ if (str1.charAt(i) != str2.charAt(i)) {
+ if (i > 1 && str1.charAt(i-1) == '.') {
+ return str1.substring(0, i-1);
+ } else {
+ return str1.substring(0, i);
+ }
+ }
+ }
+ return str1.substring(0, minLength);
+ }
+
+ /**
+ * ElementVisitor to scan the PluginAliases annotation.
+ */
+ private static class PluginAliasesElementVisitor extends SimpleElementVisitor7<Collection<PluginEntry>, Plugin> {
+
+ private final Elements elements;
+
+ private PluginAliasesElementVisitor(final Elements elements) {
+ super(Collections.<PluginEntry> emptyList());
+ this.elements = elements;
+ }
+
+ @Override
+ public Collection<PluginEntry> visitType(final TypeElement e, final Plugin plugin) {
+ final PluginAliases aliases = e.getAnnotation(PluginAliases.class);
+ if (aliases == null) {
+ return DEFAULT_VALUE;
+ }
+ final Collection<PluginEntry> entries = new ArrayList<>(aliases.value().length);
+ for (final String alias : aliases.value()) {
+ final PluginEntry entry = new PluginEntry();
+ entry.setKey(alias.toLowerCase(Locale.US));
+ entry.setClassName(elements.getBinaryName(e).toString());
+ entry.setName(Plugin.EMPTY.equals(plugin.elementType()) ? alias : plugin.elementType());
+ entry.setPrintable(plugin.printObject());
+ entry.setDefer(plugin.deferChildren());
+ entry.setCategory(plugin.category());
+ entries.add(entry);
+ }
+ return entries;
+ }
+ }
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/processor/PluginService.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/processor/PluginService.java
new file mode 100644
index 0000000..86c068a
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/processor/PluginService.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.plugins.processor;
+
+import org.apache.logging.log4j.plugins.util.PluginType;
+
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Class Description goes here.
+ */
+public abstract class PluginService {
+
+ private final Map<String, List<PluginType<?>>> categories = new LinkedHashMap<>();
+
+ public PluginService() {
+ PluginEntry[] entries = getEntries();
+ for (PluginEntry entry : entries) {
+ String category = entry.getCategory().toLowerCase();
+ List<PluginType<?>> list = categories.computeIfAbsent(category, ignored -> new LinkedList<>());
+ PluginType<?> type = new PluginType<>(entry, this.getClass().getClassLoader());
+ list.add(type);
+ }
+ }
+
+ public abstract PluginEntry[] getEntries();
+
+ public Map<String, List<PluginType<?>>> getCategories() {
+ return Collections.unmodifiableMap(categories);
+ }
+
+ public List<PluginType<?>> getCategory(String category) {
+ return Collections.unmodifiableList(categories.get(category.toLowerCase()));
+ }
+
+ public long size() {
+ return categories.size();
+ }
+
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/processor/package-info.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/processor/package-info.java
new file mode 100644
index 0000000..481d2eb
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/processor/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.
+ */
+
+/**
+ * Java annotation processor for pre-scanning Log4j 2 plugins. This is provided as an alternative to using the
+ * executable {@link org.apache.logging.log4j.plugins.util.PluginManager} class in your build process.
+ */
+package org.apache.logging.log4j.plugins.processor;
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/util/Builder.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/util/Builder.java
new file mode 100644
index 0000000..3ef6052
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/util/Builder.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.plugins.util;
+
+/**
+ * A type of builder that can be used to configure and create a instances using a Java DSL instead of
+ * through a configuration file. These builders are primarily useful for internal code and unit tests, but they can
+ * technically be used as a verbose alternative to configuration files.
+ *
+ * <p>
+ * When creating <em>plugin</em> builders, it is customary to create the builder class as a public static inner class
+ * called {@code Builder}. For instance, the builder class for
+ * org.apache.logging.log4j.core.layout.PatternLayout PatternLayout would be
+ * {@code PatternLayout.Builder}.
+ * </p>
+ *
+ * @param <T> This builder creates instances of this class.
+ */
+public interface Builder<T> {
+
+ /**
+ * Builds the object after all configuration has been set. This will use default values for any
+ * unspecified attributes for the object.
+ *
+ * @return the configured instance.
+ * object.
+ */
+ T build();
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/util/PluginManager.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/util/PluginManager.java
new file mode 100644
index 0000000..b6f02f5
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/util/PluginManager.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.plugins.util;
+
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.status.StatusLogger;
+import org.apache.logging.log4j.util.Strings;
+
+import java.util.*;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ * Loads and manages all the plugins.
+ */
+public class PluginManager {
+
+ private static final CopyOnWriteArrayList<String> PACKAGES = new CopyOnWriteArrayList<>();
+ private static final String LOG4J_PACKAGES = "org.apache.logging.log4j.core";
+
+ private static final Logger LOGGER = StatusLogger.getLogger();
+
+ private Map<String, PluginType<?>> plugins = new HashMap<>();
+ private final String category;
+
+ /**
+ * Constructs a PluginManager for the plugin category name given.
+ *
+ * @param category The plugin category name.
+ */
+ public PluginManager(final String category) {
+ this.category = category;
+ }
+
+ /**
+ * Adds a package name to be scanned for plugins. Must be invoked prior to plugins being collected.
+ *
+ * @param p The package name. Ignored if {@code null} or empty.
+ */
+ public static void addPackage(final String p) {
+ if (Strings.isBlank(p)) {
+ return;
+ }
+ PACKAGES.addIfAbsent(p);
+ }
+
+ /**
+ * Adds a list of package names to be scanned for plugins. Convenience method for {@link #addPackage(String)}.
+ *
+ * @param packages collection of package names to add. Empty and null package names are ignored.
+ */
+ public static void addPackages(final Collection<String> packages) {
+ for (final String pkg : packages) {
+ if (Strings.isNotBlank(pkg)) {
+ PACKAGES.addIfAbsent(pkg);
+ }
+ }
+ }
+
+ /**
+ * Returns the type of a specified plugin.
+ *
+ * @param name The name of the plugin.
+ * @return The plugin's type.
+ */
+ public PluginType<?> getPluginType(final String name) {
+ return plugins.get(name.toLowerCase());
+ }
+
+ /**
+ * Returns all the matching plugins.
+ *
+ * @return A Map containing the name of the plugin and its type.
+ */
+ public Map<String, PluginType<?>> getPlugins() {
+ return plugins;
+ }
+
+ /**
+ * Locates all the plugins.
+ */
+ public void collectPlugins() {
+ collectPlugins(null);
+ }
+
+ /**
+ * Locates all the plugins including search of specific packages. Warns about name collisions.
+ *
+ * @param packages the list of packages to scan for plugins
+ * @since 2.1
+ */
+ public void collectPlugins(final List<String> packages) {
+ final String categoryLowerCase = category.toLowerCase();
+ final Map<String, PluginType<?>> newPlugins = new LinkedHashMap<>();
+
+ // First, iterate the Log4j2Plugin.dat files found in the main CLASSPATH
+ Map<String, List<PluginType<?>>> builtInPlugins = PluginRegistry.getInstance().loadFromMainClassLoader();
+ if (builtInPlugins.isEmpty()) {
+ // If we didn't find any plugins above, someone must have messed with the log4j-core.jar.
+ // Search the standard package in the hopes we can find our core plugins.
+ builtInPlugins = PluginRegistry.getInstance().loadFromPackage(LOG4J_PACKAGES);
+ }
+ mergeByName(newPlugins, builtInPlugins.get(categoryLowerCase));
+
+ // Next, iterate any Log4j2Plugin.dat files from OSGi Bundles
+ for (final Map<String, List<PluginType<?>>> pluginsByCategory : PluginRegistry.getInstance().getPluginsByCategoryByBundleId().values()) {
+ mergeByName(newPlugins, pluginsByCategory.get(categoryLowerCase));
+ }
+
+ // Next iterate any packages passed to the static addPackage method.
+ for (final String pkg : PACKAGES) {
+ mergeByName(newPlugins, PluginRegistry.getInstance().loadFromPackage(pkg).get(categoryLowerCase));
+ }
+ // Finally iterate any packages provided in the configuration (note these can be changed at runtime).
+ if (packages != null) {
+ for (final String pkg : packages) {
+ mergeByName(newPlugins, PluginRegistry.getInstance().loadFromPackage(pkg).get(categoryLowerCase));
+ }
+ }
+
+ LOGGER.debug("PluginManager '{}' found {} plugins", category, newPlugins.size());
+
+ plugins = newPlugins;
+ }
+
+ private static void mergeByName(final Map<String, PluginType<?>> newPlugins, final List<PluginType<?>> plugins) {
+ if (plugins == null) {
+ return;
+ }
+ for (final PluginType<?> pluginType : plugins) {
+ final String key = pluginType.getKey();
+ final PluginType<?> existing = newPlugins.get(key);
+ if (existing == null) {
+ newPlugins.put(key, pluginType);
+ } else if (!existing.getPluginClass().equals(pluginType.getPluginClass())) {
+ LOGGER.warn("Plugin [{}] is already mapped to {}, ignoring {}",
+ key, existing.getPluginClass(), pluginType.getPluginClass());
+ }
+ }
+ }
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/util/PluginRegistry.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/util/PluginRegistry.java
new file mode 100644
index 0000000..bae5c8a
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/util/PluginRegistry.java
@@ -0,0 +1,396 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+
+package org.apache.logging.log4j.plugins.util;
+
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginAliases;
+import org.apache.logging.log4j.plugins.processor.PluginCache;
+import org.apache.logging.log4j.plugins.processor.PluginEntry;
+import org.apache.logging.log4j.plugins.processor.PluginProcessor;
+import org.apache.logging.log4j.plugins.processor.PluginService;
+import org.apache.logging.log4j.status.StatusLogger;
+import org.apache.logging.log4j.util.LoaderUtil;
+import org.apache.logging.log4j.util.Strings;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URL;
+import java.text.DecimalFormat;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.ServiceLoader;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * Registry singleton for PluginType maps partitioned by source type and then by category names.
+ */
+public class PluginRegistry {
+
+ private static final Logger LOGGER = StatusLogger.getLogger();
+
+ private static volatile PluginRegistry INSTANCE;
+ private static final Object INSTANCE_LOCK = new Object();
+ protected static final Lock STARTUP_LOCK = new ReentrantLock();
+
+ /**
+ * Contains plugins found in Log4j2Plugins.dat cache files in the main CLASSPATH.
+ */
+ private final AtomicReference<Map<String, List<PluginType<?>>>> pluginsByCategoryRef =
+ new AtomicReference<>();
+
+ /**
+ * Contains plugins found in Log4j2Plugins.dat cache files in OSGi Bundles.
+ */
+ private final ConcurrentMap<Long, Map<String, List<PluginType<?>>>> pluginsByCategoryByBundleId =
+ new ConcurrentHashMap<>();
+
+ /**
+ * Contains plugins found by searching for annotated classes at runtime.
+ */
+ private final ConcurrentMap<String, Map<String, List<PluginType<?>>>> pluginsByCategoryByPackage =
+ new ConcurrentHashMap<>();
+
+ private PluginRegistry() {
+ }
+
+ /**
+ * Returns the global PluginRegistry instance.
+ *
+ * @return the global PluginRegistry instance.
+ * @since 2.1
+ */
+ public static PluginRegistry getInstance() {
+ PluginRegistry result = INSTANCE;
+ if (result == null) {
+ synchronized (INSTANCE_LOCK) {
+ result = INSTANCE;
+ if (result == null) {
+ INSTANCE = result = new PluginRegistry();
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Resets the registry to an empty state.
+ */
+ public void clear() {
+ pluginsByCategoryRef.set(null);
+ pluginsByCategoryByPackage.clear();
+ pluginsByCategoryByBundleId.clear();
+ }
+
+ /**
+ * Retrieve plugins by their category and bundle id.
+ * @return The Map of plugin maps.
+ * @since 2.1
+ */
+ public Map<Long, Map<String, List<PluginType<?>>>> getPluginsByCategoryByBundleId() {
+ return pluginsByCategoryByBundleId;
+ }
+
+ /**
+ * Retrieve plugins from the main classloader.
+ * @return Map of the List of PluginTypes by category.
+ * @since 2.1
+ */
+ public Map<String, List<PluginType<?>>> loadFromMainClassLoader() {
+ final Map<String, List<PluginType<?>>> existing = pluginsByCategoryRef.get();
+ if (existing != null) {
+ // already loaded
+ return existing;
+ }
+ final Map<String, List<PluginType<?>>> newPluginsByCategory = decodeCacheFiles(LoaderUtil.getClassLoader());
+ loadPlugins(newPluginsByCategory);
+
+ // Note multiple threads could be calling this method concurrently. Both will do the work,
+ // but only one will be allowed to store the result in the AtomicReference.
+ // Return the map produced by whichever thread won the race, so all callers will get the same result.
+ if (pluginsByCategoryRef.compareAndSet(null, newPluginsByCategory)) {
+ return newPluginsByCategory;
+ }
+ return pluginsByCategoryRef.get();
+ }
+
+ /**
+ * Remove the bundle plugins.
+ * @param bundleId The bundle id.
+ * @since 2.1
+ */
+ public void clearBundlePlugins(final long bundleId) {
+ pluginsByCategoryByBundleId.remove(bundleId);
+ }
+
+ /**
+ * Load plugins from a bundle.
+ * @param bundleId The bundle id.
+ * @param loader The ClassLoader.
+ * @return the Map of Lists of plugins organized by category.
+ * @since 2.1
+ */
+ public Map<String, List<PluginType<?>>> loadFromBundle(final long bundleId, final ClassLoader loader) {
+ Map<String, List<PluginType<?>>> existing = pluginsByCategoryByBundleId.get(bundleId);
+ if (existing != null) {
+ // already loaded from this classloader
+ return existing;
+ }
+ final Map<String, List<PluginType<?>>> newPluginsByCategory = decodeCacheFiles(loader);
+ loadPlugins(loader, newPluginsByCategory);
+
+ // Note multiple threads could be calling this method concurrently. Both will do the work,
+ // but only one will be allowed to store the result in the outer map.
+ // Return the inner map produced by whichever thread won the race, so all callers will get the same result.
+ existing = pluginsByCategoryByBundleId.putIfAbsent(bundleId, newPluginsByCategory);
+ if (existing != null) {
+ return existing;
+ }
+ return newPluginsByCategory;
+ }
+
+ /**
+ * Loads all the plugins in a Bundle.
+ * @param categories All the categories in the bundle.
+ * @param bundleId The bundle Id.
+ * @since 3.0
+ */
+ public void loadFromBundle(Map<String, List<PluginType<?>>> categories, Long bundleId) {
+ pluginsByCategoryByBundleId.put(bundleId, categories);
+ for (Map.Entry<String, List<PluginType<?>>> entry: categories.entrySet()) {
+ if (!categories.containsKey(entry.getKey())) {
+
+ categories.put(entry.getKey(), new LinkedList<>());
+ }
+ categories.get(entry.getKey()).addAll(entry.getValue());
+ }
+ }
+
+ /**
+ * Load plugins across all ClassLoaders.
+ * @param map The Map of the lists of plugins organized by category.
+ * @since 3.0
+ */
+ public void loadPlugins(Map<String, List<PluginType<?>>> map) {
+ for (ClassLoader classLoader : LoaderUtil.getClassLoaders()) {
+ try {
+ loadPlugins(classLoader, map);
+ } catch (Throwable ex) {
+ LOGGER.debug("Unable to retrieve provider from ClassLoader {}", classLoader, ex);
+ }
+ }
+ }
+
+ /**
+ * Load plugins from a specific ClassLoader.
+ * @param classLoader The ClassLoader.
+ * @param map The Map of the list of plugin types organized by category.
+ * @since 3.0
+ */
+ public void loadPlugins(ClassLoader classLoader, Map<String, List<PluginType<?>>> map) {
+ final long startTime = System.nanoTime();
+ final ServiceLoader<PluginService> serviceLoader = ServiceLoader.load(PluginService.class, classLoader);
+ int pluginCount = 0;
+ for (final PluginService pluginService : serviceLoader) {
+ PluginEntry[] entries = pluginService.getEntries();
+ for (PluginEntry entry : entries) {
+ final PluginType<?> type = new PluginType<>(entry, classLoader);
+ String category = entry.getCategory().toLowerCase();
+ if (!map.containsKey(category)) {
+ map.put(category, new ArrayList<>());
+ }
+ List<PluginType<?>> list = map.get(category);
+ list.add(type);
+ ++pluginCount;
+ }
+ }
+ final int numPlugins = pluginCount;
+ LOGGER.debug(() -> {
+ final long endTime = System.nanoTime();
+ StringBuilder sb = new StringBuilder("Took ");
+ final DecimalFormat numFormat = new DecimalFormat("#0.000000");
+ sb.append(numFormat.format((endTime - startTime) * 1e-9));
+ sb.append(" seconds to load ").append(numPlugins);
+ sb.append(" plugins from ").append(classLoader);
+ return sb.toString();
+ });
+ }
+
+ private Map<String, List<PluginType<?>>> decodeCacheFiles(final ClassLoader loader) {
+ final long startTime = System.nanoTime();
+ final PluginCache cache = new PluginCache();
+ try {
+ final Enumeration<URL> resources = loader.getResources(PluginProcessor.PLUGIN_CACHE_FILE);
+ if (resources == null) {
+ LOGGER.info("Plugin preloads not available from class loader {}", loader);
+ } else {
+ cache.loadCacheFiles(resources);
+ }
+ } catch (final IOException ioe) {
+ LOGGER.warn("Unable to preload plugins", ioe);
+ }
+ final Map<String, List<PluginType<?>>> newPluginsByCategory = new HashMap<>();
+ int pluginCount = 0;
+ for (final Map.Entry<String, Map<String, PluginEntry>> outer : cache.getAllCategories().entrySet()) {
+ final String categoryLowerCase = outer.getKey();
+ final List<PluginType<?>> types = new ArrayList<>(outer.getValue().size());
+ newPluginsByCategory.put(categoryLowerCase, types);
+ for (final Map.Entry<String, PluginEntry> inner : outer.getValue().entrySet()) {
+ final PluginEntry entry = inner.getValue();
+ final String className = entry.getClassName();
+ final PluginType<?> type = new PluginType<>(entry, loader);
+ types.add(type);
+ ++pluginCount;
+ }
+ }
+ final int numPlugins = pluginCount;
+ LOGGER.debug(() -> {
+ final long endTime = System.nanoTime();
+ StringBuilder sb = new StringBuilder("Took ");
+ final DecimalFormat numFormat = new DecimalFormat("#0.000000");
+ sb.append(numFormat.format((endTime - startTime) * 1e-9));
+ sb.append(" seconds to load ").append(numPlugins);
+ sb.append(" plugins from ").append(loader);
+ return sb.toString();
+ });
+ return newPluginsByCategory;
+ }
+
+ /**
+ * Load plugin types from a package.
+ * @param pkg The package name.
+ * @return A Map of the lists of plugin types organized by category.
+ * @since 2.1
+ */
+ public Map<String, List<PluginType<?>>> loadFromPackage(final String pkg) {
+ if (Strings.isBlank(pkg)) {
+ // happens when splitting an empty string
+ return Collections.emptyMap();
+ }
+ Map<String, List<PluginType<?>>> existing = pluginsByCategoryByPackage.get(pkg);
+ if (existing != null) {
+ // already loaded this package
+ return existing;
+ }
+
+ final long startTime = System.nanoTime();
+ final ResolverUtil resolver = new ResolverUtil();
+ final ClassLoader classLoader = LoaderUtil.getClassLoader();
+ if (classLoader != null) {
+ resolver.setClassLoader(classLoader);
+ }
+ resolver.findInPackage(new PluginTest(), pkg);
+
+ final Map<String, List<PluginType<?>>> newPluginsByCategory = new HashMap<>();
+ for (final Class<?> clazz : resolver.getClasses()) {
+ final Plugin plugin = clazz.getAnnotation(Plugin.class);
+ final String categoryLowerCase = plugin.category().toLowerCase();
+ List<PluginType<?>> list = newPluginsByCategory.get(categoryLowerCase);
+ if (list == null) {
+ newPluginsByCategory.put(categoryLowerCase, list = new ArrayList<>());
+ }
+ final PluginEntry mainEntry = new PluginEntry();
+ final String mainElementName = plugin.elementType().equals(
+ Plugin.EMPTY) ? plugin.name() : plugin.elementType();
+ mainEntry.setKey(plugin.name().toLowerCase());
+ mainEntry.setName(plugin.name());
+ mainEntry.setCategory(plugin.category());
+ mainEntry.setClassName(clazz.getName());
+ mainEntry.setPrintable(plugin.printObject());
+ mainEntry.setDefer(plugin.deferChildren());
+ final PluginType<?> mainType = new PluginType<>(mainEntry, clazz, mainElementName);
+ list.add(mainType);
+ final PluginAliases pluginAliases = clazz.getAnnotation(PluginAliases.class);
+ if (pluginAliases != null) {
+ for (final String alias : pluginAliases.value()) {
+ final PluginEntry aliasEntry = new PluginEntry();
+ final String aliasElementName = plugin.elementType().equals(
+ Plugin.EMPTY) ? alias.trim() : plugin.elementType();
+ aliasEntry.setKey(alias.trim().toLowerCase());
+ aliasEntry.setName(plugin.name());
+ aliasEntry.setCategory(plugin.category());
+ aliasEntry.setClassName(clazz.getName());
+ aliasEntry.setPrintable(plugin.printObject());
+ aliasEntry.setDefer(plugin.deferChildren());
+ final PluginType<?> aliasType = new PluginType<>(aliasEntry, clazz, aliasElementName);
+ list.add(aliasType);
+ }
+ }
+ }
+ LOGGER.debug(() -> {
+ final long endTime = System.nanoTime();
+ StringBuilder sb = new StringBuilder("Took ");
+ final DecimalFormat numFormat = new DecimalFormat("#0.000000");
+ sb.append(numFormat.format((endTime - startTime) * 1e-9));
+ sb.append(" seconds to load ").append(resolver.getClasses().size());
+ sb.append(" plugins from package ").append(pkg);
+ return sb.toString();
+ });
+
+ // Note multiple threads could be calling this method concurrently. Both will do the work,
+ // but only one will be allowed to store the result in the outer map.
+ // Return the inner map produced by whichever thread won the race, so all callers will get the same result.
+ existing = pluginsByCategoryByPackage.putIfAbsent(pkg, newPluginsByCategory);
+ if (existing != null) {
+ return existing;
+ }
+ return newPluginsByCategory;
+ }
+
+ /**
+ * A Test that checks to see if each class is annotated with the 'Plugin' annotation. If it
+ * is, then the test returns true, otherwise false.
+ *
+ * @since 2.1
+ */
+ public static class PluginTest implements ResolverUtil.Test {
+ @Override
+ public boolean matches(final Class<?> type) {
+ return type != null && type.isAnnotationPresent(Plugin.class);
+ }
+
+ @Override
+ public String toString() {
+ return "annotated with @" + Plugin.class.getSimpleName();
+ }
+
+ @Override
+ public boolean matches(final URI resource) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean doesMatchClass() {
+ return true;
+ }
+
+ @Override
+ public boolean doesMatchResource() {
+ return false;
+ }
+ }
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/util/PluginType.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/util/PluginType.java
new file mode 100644
index 0000000..3e1a4b1
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/util/PluginType.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.plugins.util;
+
+
+import org.apache.logging.log4j.plugins.processor.PluginEntry;
+
+/**
+ * Plugin Descriptor. This is a memento object for Plugin annotations paired to their annotated classes.
+ *
+ * @param <T> The plug-in class, which can be any kind of class.
+ * @see org.apache.logging.log4j.plugins.Plugin
+ */
+public class PluginType<T> {
+
+ private final PluginEntry pluginEntry;
+ private volatile Class<T> pluginClass;
+ private final ClassLoader classLoader;
+ private final String elementName;
+
+ /**
+ * Constructor.
+ * @param pluginEntry The PluginEntry.
+ * @param pluginClass The plugin Class.
+ * @param elementName The name of the element.
+ * @since 2.1
+ */
+ public PluginType(final PluginEntry pluginEntry, final Class<T> pluginClass, final String elementName) {
+ this.pluginEntry = pluginEntry;
+ this.pluginClass = pluginClass;
+ this.elementName = elementName;
+ this.classLoader = null;
+ }
+
+ /**
+ * The Constructor.
+ * @since 3.0
+ * @param pluginEntry The PluginEntry.
+ * @param classLoader The ClassLoader to use to load the Plugin.
+ */
+ public PluginType(final PluginEntry pluginEntry, final ClassLoader classLoader) {
+ this.pluginEntry = pluginEntry;
+ this.classLoader = classLoader;
+ this.elementName = pluginEntry.getName();
+ this.pluginClass = null;
+ }
+
+
+ public PluginEntry getPluginEntry() {
+ return this.pluginEntry;
+ }
+
+ @SuppressWarnings("unchecked")
+ public Class<T> getPluginClass() {
+ if (pluginClass == null) {
+ try {
+ pluginClass = (Class<T>) this.classLoader.loadClass(pluginEntry.getClassName());
+ } catch (ClassNotFoundException | LinkageError ex) {
+ throw new IllegalStateException("No class named " + pluginEntry.getClassName() +
+ " located for element " + elementName, ex);
+ }
+ }
+ return this.pluginClass;
+ }
+
+ public String getElementName() {
+ return this.elementName;
+ }
+
+ /**
+ * Return The plugin's key.
+ * @return The plugin key.
+ * @since 2.1
+ */
+ public String getKey() {
+ return this.pluginEntry.getKey();
+ }
+
+ public boolean isObjectPrintable() {
+ return this.pluginEntry.isPrintable();
+ }
+
+ public boolean isDeferChildren() {
+ return this.pluginEntry.isDefer();
+ }
+
+ /**
+ * Return the plugin category.
+ * @return the Plugin category.
+ * @since 2.1
+ */
+ public String getCategory() {
+ return this.pluginEntry.getCategory();
+ }
+
+ @Override
+ public String toString() {
+ return "PluginType [pluginClass=" + pluginClass +
+ ", key=" + pluginEntry.getKey() +
+ ", elementName=" + pluginEntry.getName() +
+ ", isObjectPrintable=" + pluginEntry.isPrintable() +
+ ", isDeferChildren==" + pluginEntry.isDefer() +
+ ", category=" + pluginEntry.getCategory() +
+ "]";
+ }
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/util/ResolverUtil.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/util/ResolverUtil.java
new file mode 100644
index 0000000..81e2347
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/util/ResolverUtil.java
@@ -0,0 +1,488 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+package org.apache.logging.log4j.plugins.util;
+
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.util.LoaderUtil;
+import org.apache.logging.log4j.status.StatusLogger;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.wiring.BundleWiring;
+
+import java.io.*;
+import java.net.JarURLConnection;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.net.URLDecoder;
+import java.nio.charset.StandardCharsets;
+import java.util.*;
+import java.util.jar.JarEntry;
+import java.util.jar.JarFile;
+import java.util.jar.JarInputStream;
+
+/**
+ * <p>
+ * ResolverUtil is used to locate classes that are available in the/a class path and meet arbitrary conditions. The two
+ * most common conditions are that a class implements/extends another class, or that is it annotated with a specific
+ * annotation. However, through the use of the {@link Test} class it is possible to search using arbitrary conditions.
+ * </p>
+ *
+ * <p>
+ * A ClassLoader is used to locate all locations (directories and jar files) in the class path that contain classes
+ * within certain packages, and then to load those classes and check them. By default the ClassLoader returned by
+ * {@code Thread.currentThread().getContextClassLoader()} is used, but this can be overridden by calling
+ * {@link #setClassLoader(ClassLoader)} prior to invoking any of the {@code find()} methods.
+ * </p>
+ *
+ * <p>
+ * General searches are initiated by calling the {@link #find(ResolverUtil.Test, String...)} method and supplying a
+ * package name and a Test instance. This will cause the named package <b>and all sub-packages</b> to be scanned for
+ * classes that meet the test. There are also utility methods for the common use cases of scanning multiple packages for
+ * extensions of particular classes, or classes annotated with a specific annotation.
+ * </p>
+ *
+ * <p>
+ * The standard usage pattern for the ResolverUtil class is as follows:
+ * </p>
+ *
+ * <pre>
+ * ResolverUtil resolver = new ResolverUtil();
+ * resolver.findInPackage(new CustomTest(), pkg1);
+ * resolver.find(new CustomTest(), pkg1);
+ * resolver.find(new CustomTest(), pkg1, pkg2);
+ * Set<Class<?>> beans = resolver.getClasses();
+ * </pre>
+ *
+ * <p>
+ * This class was copied and modified from Stripes - http://stripes.mc4j.org/confluence/display/stripes/Home
+ * </p>
+ */
+public class ResolverUtil {
+ /** An instance of Log to use for logging in this class. */
+ private static final Logger LOGGER = StatusLogger.getLogger();
+
+ private static final String VFSZIP = "vfszip";
+
+ private static final String VFS = "vfs";
+
+ private static final String JAR = "jar";
+
+ private static final String BUNDLE_RESOURCE = "bundleresource";
+
+ /** The set of matches being accumulated. */
+ private final Set<Class<?>> classMatches = new HashSet<>();
+
+ /** The set of matches being accumulated. */
+ private final Set<URI> resourceMatches = new HashSet<>();
+
+ /**
+ * The ClassLoader to use when looking for classes. If null then the ClassLoader returned by
+ * Thread.currentThread().getContextClassLoader() will be used.
+ */
+ private ClassLoader classloader;
+
+ /**
+ * Provides access to the classes discovered so far. If no calls have been made to any of the {@code find()}
+ * methods, this set will be empty.
+ *
+ * @return the set of classes that have been discovered.
+ */
+ public Set<Class<?>> getClasses() {
+ return classMatches;
+ }
+
+ /**
+ * Returns the matching resources.
+ *
+ * @return A Set of URIs that match the criteria.
+ */
+ public Set<URI> getResources() {
+ return resourceMatches;
+ }
+
+ /**
+ * Returns the ClassLoader that will be used for scanning for classes. If no explicit ClassLoader has been set by
+ * the calling, the context class loader will be used.
+ *
+ * @return the ClassLoader that will be used to scan for classes
+ */
+ public ClassLoader getClassLoader() {
+ return classloader != null ? classloader : (classloader = LoaderUtil.getClassLoader(ResolverUtil.class, null));
+ }
+
+ /**
+ * Sets an explicit ClassLoader that should be used when scanning for classes. If none is set then the context
+ * ClassLoader will be used.
+ *
+ * @param aClassloader
+ * a ClassLoader to use when scanning for classes
+ */
+ public void setClassLoader(final ClassLoader aClassloader) {
+ this.classloader = aClassloader;
+ }
+
+ /**
+ * Attempts to discover classes that pass the test. Accumulated classes can be accessed by calling
+ * {@link #getClasses()}.
+ *
+ * @param test
+ * the test to determine matching classes
+ * @param packageNames
+ * one or more package names to scan (including subpackages) for classes
+ */
+ public void find(final Test test, final String... packageNames) {
+ if (packageNames == null) {
+ return;
+ }
+
+ for (final String pkg : packageNames) {
+ findInPackage(test, pkg);
+ }
+ }
+
+ /**
+ * Scans for classes starting at the package provided and descending into subpackages. Each class is offered up to
+ * the Test as it is discovered, and if the Test returns true the class is retained. Accumulated classes can be
+ * fetched by calling {@link #getClasses()}.
+ *
+ * @param test
+ * an instance of {@link Test} that will be used to filter classes
+ * @param packageName
+ * the name of the package from which to start scanning for classes, e.g. {@code net.sourceforge.stripes}
+ */
+ public void findInPackage(final Test test, String packageName) {
+ packageName = packageName.replace('.', '/');
+ final ClassLoader loader = getClassLoader();
+ Enumeration<URL> urls;
+
+ try {
+ urls = loader.getResources(packageName);
+ } catch (final IOException ioe) {
+ LOGGER.warn("Could not read package: {}", packageName, ioe);
+ return;
+ }
+
+ while (urls.hasMoreElements()) {
+ try {
+ final URL url = urls.nextElement();
+ final String urlPath = extractPath(url);
+
+ LOGGER.info("Scanning for classes in '{}' matching criteria {}", urlPath , test);
+ // Check for a jar in a war in JBoss
+ if (VFSZIP.equals(url.getProtocol())) {
+ final String path = urlPath.substring(0, urlPath.length() - packageName.length() - 2);
+ final URL newURL = new URL(url.getProtocol(), url.getHost(), path);
+ @SuppressWarnings("resource")
+ final JarInputStream stream = new JarInputStream(newURL.openStream());
+ try {
+ loadImplementationsInJar(test, packageName, path, stream);
+ } finally {
+ close(stream, newURL);
+ }
+ } else if (VFS.equals(url.getProtocol())) {
+ final String containerPath = urlPath.substring(1, urlPath.length() - packageName.length() - 2);
+ final File containerFile = new File(containerPath);
+ if (containerFile.exists()) {
+ if (containerFile.isDirectory()) {
+ loadImplementationsInDirectory(test, packageName, new File(containerFile, packageName));
+ } else {
+ loadImplementationsInJar(test, packageName, containerFile);
+ }
+ } else {
+ // fallback code for Jboss/Wildfly, if the file couldn't be found
+ // by loading the path as a file, try to read the jar as a stream
+ final String path = urlPath.substring(0, urlPath.length() - packageName.length() - 2);
+ final URL newURL = new URL(url.getProtocol(), url.getHost(), path);
+
+ try (final InputStream is = newURL.openStream()) {
+ final JarInputStream jarStream;
+ if (is instanceof JarInputStream) {
+ jarStream = (JarInputStream) is;
+ } else {
+ jarStream = new JarInputStream(is);
+ }
+ loadImplementationsInJar(test, packageName, path, jarStream);
+ }
+ }
+ } else if (BUNDLE_RESOURCE.equals(url.getProtocol())) {
+ loadImplementationsInBundle(test, packageName);
+ } else if (JAR.equals(url.getProtocol())) {
+ loadImplementationsInJar(test, packageName, url);
+ } else {
+ final File file = new File(urlPath);
+ if (file.isDirectory()) {
+ loadImplementationsInDirectory(test, packageName, file);
+ } else {
+ loadImplementationsInJar(test, packageName, file);
+ }
+ }
+ } catch (final IOException | URISyntaxException ioe) {
+ LOGGER.warn("Could not read entries", ioe);
+ }
+ }
+ }
+
+ String extractPath(final URL url) throws UnsupportedEncodingException, URISyntaxException {
+ String urlPath = url.getPath(); // same as getFile but without the Query portion
+ // System.out.println(url.getProtocol() + "->" + urlPath);
+
+ // I would be surprised if URL.getPath() ever starts with "jar:" but no harm in checking
+ if (urlPath.startsWith("jar:")) {
+ urlPath = urlPath.substring(4);
+ }
+ // For jar: URLs, the path part starts with "file:"
+ if (urlPath.startsWith("file:")) {
+ urlPath = urlPath.substring(5);
+ }
+ // If it was in a JAR, grab the path to the jar
+ final int bangIndex = urlPath.indexOf('!');
+ if (bangIndex > 0) {
+ urlPath = urlPath.substring(0, bangIndex);
+ }
+
+ // LOG4J2-445
+ // Finally, decide whether to URL-decode the file name or not...
+ final String protocol = url.getProtocol();
+ final List<String> neverDecode = Arrays.asList(VFS, VFSZIP, BUNDLE_RESOURCE);
+ if (neverDecode.contains(protocol)) {
+ return urlPath;
+ }
+ final String cleanPath = new URI(urlPath).getPath();
+ if (new File(cleanPath).exists()) {
+ // if URL-encoded file exists, don't decode it
+ return cleanPath;
+ }
+ return URLDecoder.decode(urlPath, StandardCharsets.UTF_8.name());
+ }
+
+ private void loadImplementationsInBundle(final Test test, final String packageName) {
+ final BundleWiring wiring = FrameworkUtil.getBundle(ResolverUtil.class).adapt(BundleWiring.class);
+ final Collection<String> list = wiring.listResources(packageName, "*.class",
+ BundleWiring.LISTRESOURCES_RECURSE);
+ for (final String name : list) {
+ addIfMatching(test, name);
+ }
+ }
+
+ /**
+ * Finds matches in a physical directory on a file system. Examines all files within a directory - if the File object
+ * is not a directory, and ends with <i>.class</i> the file is loaded and tested to see if it is acceptable
+ * according to the Test. Operates recursively to find classes within a folder structure matching the package
+ * structure.
+ *
+ * @param test
+ * a Test used to filter the classes that are discovered
+ * @param parent
+ * the package name up to this directory in the package hierarchy. E.g. if /classes is in the classpath and
+ * we wish to examine files in /classes/org/apache then the values of <i>parent</i> would be
+ * <i>org/apache</i>
+ * @param location
+ * a File object representing a directory
+ */
+ private void loadImplementationsInDirectory(final Test test, final String parent, final File location) {
+ final File[] files = location.listFiles();
+ if (files == null) {
+ return;
+ }
+
+ StringBuilder builder;
+ for (final File file : files) {
+ builder = new StringBuilder();
+ builder.append(parent).append('/').append(file.getName());
+ final String packageOrClass = parent == null ? file.getName() : builder.toString();
+
+ if (file.isDirectory()) {
+ loadImplementationsInDirectory(test, packageOrClass, file);
+ } else if (isTestApplicable(test, file.getName())) {
+ addIfMatching(test, packageOrClass);
+ }
+ }
+ }
+
+ private boolean isTestApplicable(final Test test, final String path) {
+ return test.doesMatchResource() || path.endsWith(".class") && test.doesMatchClass();
+ }
+
+ /**
+ * Finds matching classes within a jar files that contains a folder structure matching the package structure. If the
+ * File is not a JarFile or does not exist a warning will be logged, but no error will be raised.
+ *
+ * @param test
+ * a Test used to filter the classes that are discovered
+ * @param parent
+ * the parent package under which classes must be in order to be considered
+ * @param jarFile
+ * the jar file to be examined for classes
+ */
+ private void loadImplementationsInJar(final Test test, final String parent, final File jarFile) {
+ JarInputStream jarStream = null;
+ try {
+ jarStream = new JarInputStream(new FileInputStream(jarFile));
+ loadImplementationsInJar(test, parent, jarFile.getPath(), jarStream);
+ } catch (final IOException ex) {
+ LOGGER.error("Could not search JAR file '{}' for classes matching criteria {}, file not found", jarFile,
+ test, ex);
+ } finally {
+ close(jarStream, jarFile);
+ }
+ }
+
+ /**
+ * @param jarStream
+ * @param source
+ */
+ private void close(final JarInputStream jarStream, final Object source) {
+ if (jarStream != null) {
+ try {
+ jarStream.close();
+ } catch (final IOException e) {
+ LOGGER.error("Error closing JAR file stream for {}", source, e);
+ }
+ }
+ }
+
+ /**
+ * Finds matching classes within a jar files that contains a folder structure matching the package structure. If the
+ * File is not a JarFile or does not exist a warning will be logged, but no error will be raised.
+ *
+ * @param test
+ * a Test used to filter the classes that are discovered
+ * @param parent
+ * the parent package under which classes must be in order to be considered
+ * @param url
+ * the url that identifies the jar containing the resource.
+ */
+ private void loadImplementationsInJar(final Test test, final String parent, final URL url) {
+ JarURLConnection connection = null;
+ try {
+ connection = (JarURLConnection) url.openConnection();
+ if (connection != null) {
+ try (JarFile jarFile = connection.getJarFile()) {
+ Enumeration<JarEntry> entries = jarFile.entries();
+ while (entries.hasMoreElements()) {
+ JarEntry entry = entries.nextElement();
+ final String name = entry.getName();
+ if (!entry.isDirectory() && name.startsWith(parent) && isTestApplicable(test, name)) {
+ addIfMatching(test, name);
+ }
+ }
+ }
+ } else {
+ LOGGER.error("Could not establish connection to {}", url.toString());
+ }
+ } catch (final IOException ex) {
+ LOGGER.error("Could not search JAR file '{}' for classes matching criteria {}, file not found",
+ url.toString(), test, ex);
+ }
+ }
+
+ /**
+ * Finds matching classes within a jar files that contains a folder structure matching the package structure. If the
+ * File is not a JarFile or does not exist a warning will be logged, but no error will be raised.
+ *
+ * @param test
+ * a Test used to filter the classes that are discovered
+ * @param parent
+ * the parent package under which classes must be in order to be considered
+ * @param stream
+ * The jar InputStream
+ */
+ private void loadImplementationsInJar(final Test test, final String parent, final String path,
+ final JarInputStream stream) {
+
+ try {
+ JarEntry entry;
+
+ while ((entry = stream.getNextJarEntry()) != null) {
+ final String name = entry.getName();
+ if (!entry.isDirectory() && name.startsWith(parent) && isTestApplicable(test, name)) {
+ addIfMatching(test, name);
+ }
+ }
+ } catch (final IOException ioe) {
+ LOGGER.error("Could not search JAR file '{}' for classes matching criteria {} due to an IOException", path,
+ test, ioe);
+ }
+ }
+
+ /**
+ * Add the class designated by the fully qualified class name provided to the set of resolved classes if and only if
+ * it is approved by the Test supplied.
+ *
+ * @param test
+ * the test used to determine if the class matches
+ * @param fqn
+ * the fully qualified name of a class
+ */
+ protected void addIfMatching(final Test test, final String fqn) {
+ try {
+ final ClassLoader loader = getClassLoader();
+ if (test.doesMatchClass()) {
+ final String externalName = fqn.substring(0, fqn.indexOf('.')).replace('/', '.');
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER.debug("Checking to see if class {} matches criteria {}", externalName, test);
+ }
+
+ final Class<?> type = loader.loadClass(externalName);
+ if (test.matches(type)) {
+ classMatches.add(type);
+ }
+ }
+ if (test.doesMatchResource()) {
+ URL url = loader.getResource(fqn);
+ if (url == null) {
+ url = loader.getResource(fqn.substring(1));
+ }
+ if (url != null && test.matches(url.toURI())) {
+ resourceMatches.add(url.toURI());
+ }
+ }
+ } catch (final Throwable t) {
+ LOGGER.warn("Could not examine class {}", fqn, t);
+ }
+ }
+
+ /**
+ * A simple interface that specifies how to test classes to determine if they are to be included in the results
+ * produced by the ResolverUtil.
+ */
+ public interface Test {
+ /**
+ * Will be called repeatedly with candidate classes. Must return True if a class is to be included in the
+ * results, false otherwise.
+ *
+ * @param type
+ * The Class to match against.
+ * @return true if the Class matches.
+ */
+ boolean matches(Class<?> type);
+
+ /**
+ * Test for a resource.
+ *
+ * @param resource
+ * The URI to the resource.
+ * @return true if the resource matches.
+ */
+ boolean matches(URI resource);
+
+ boolean doesMatchClass();
+
+ boolean doesMatchResource();
+ }
+
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/util/TypeUtil.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/util/TypeUtil.java
new file mode 100644
index 0000000..6adf661
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/util/TypeUtil.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.plugins.util;
+
+import java.lang.reflect.*;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+
+/**
+ * Utility class for working with Java {@link Type}s and derivatives. This class is adapted heavily from the
+ * <a href="http://projects.spring.io/spring-framework/">Spring Framework</a>, specifically the
+ * <a href="http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/util/TypeUtils.html">TypeUtils</a>
+ * class.
+ *
+ * @see Type
+ * @see GenericArrayType
+ * @see ParameterizedType
+ * @see WildcardType
+ * @see Class
+ * @since 2.1
+ */
+public final class TypeUtil {
+
+ private TypeUtil() {
+ }
+
+ /**
+ * Gets all declared fields for the given class (including superclasses).
+ *
+ * @param cls the class to examine
+ * @return all declared fields for the given class (including superclasses).
+ * @see Class#getDeclaredFields()
+ */
+ public static List<Field> getAllDeclaredFields(Class<?> cls) {
+ final List<Field> fields = new ArrayList<>();
+ while (cls != null) {
+ fields.addAll(Arrays.asList(cls.getDeclaredFields()));
+ cls = cls.getSuperclass();
+ }
+ return fields;
+ }
+ /**
+ * Indicates if two {@link Type}s are assignment compatible.
+ *
+ * @param lhs the left hand side to check assignability to
+ * @param rhs the right hand side to check assignability from
+ * @return {@code true} if it is legal to assign a variable of type {@code rhs} to a variable of type {@code lhs}
+ * @see Class#isAssignableFrom(Class)
+ */
+ public static boolean isAssignable(final Type lhs, final Type rhs) {
+ Objects.requireNonNull(lhs, "No left hand side type provided");
+ Objects.requireNonNull(rhs, "No right hand side type provided");
+ if (lhs.equals(rhs)) {
+ return true;
+ }
+ if (Object.class.equals(lhs)) {
+ // everything is assignable to Object
+ return true;
+ }
+ // raw type on left
+ if (lhs instanceof Class<?>) {
+ final Class<?> lhsClass = (Class<?>) lhs;
+ if (rhs instanceof Class<?>) {
+ // no generics involved
+ final Class<?> rhsClass = (Class<?>) rhs;
+ return lhsClass.isAssignableFrom(rhsClass);
+ }
+ if (rhs instanceof ParameterizedType) {
+ // check to see if the parameterized type has the same raw type as the lhs; this is legal
+ final Type rhsRawType = ((ParameterizedType) rhs).getRawType();
+ if (rhsRawType instanceof Class<?>) {
+ return lhsClass.isAssignableFrom((Class<?>) rhsRawType);
+ }
+ }
+ if (lhsClass.isArray() && rhs instanceof GenericArrayType) {
+ // check for compatible array component types
+ return isAssignable(lhsClass.getComponentType(), ((GenericArrayType) rhs).getGenericComponentType());
+ }
+ }
+ // parameterized type on left
+ if (lhs instanceof ParameterizedType) {
+ final ParameterizedType lhsType = (ParameterizedType) lhs;
+ if (rhs instanceof Class<?>) {
+ final Type lhsRawType = lhsType.getRawType();
+ if (lhsRawType instanceof Class<?>) {
+ return ((Class<?>) lhsRawType).isAssignableFrom((Class<?>) rhs);
+ }
+ } else if (rhs instanceof ParameterizedType) {
+ final ParameterizedType rhsType = (ParameterizedType) rhs;
+ return isParameterizedAssignable(lhsType, rhsType);
+ }
+ }
+ // generic array type on left
+ if (lhs instanceof GenericArrayType) {
+ final Type lhsComponentType = ((GenericArrayType) lhs).getGenericComponentType();
+ if (rhs instanceof Class<?>) {
+ // raw type on right
+ final Class<?> rhsClass = (Class<?>) rhs;
+ if (rhsClass.isArray()) {
+ return isAssignable(lhsComponentType, rhsClass.getComponentType());
+ }
+ } else if (rhs instanceof GenericArrayType) {
+ return isAssignable(lhsComponentType, ((GenericArrayType) rhs).getGenericComponentType());
+ }
+ }
+ // wildcard type on left
+ if (lhs instanceof WildcardType) {
+ return isWildcardAssignable((WildcardType) lhs, rhs);
+ }
+ // strange...
+ return false;
+ }
+
+ private static boolean isParameterizedAssignable(final ParameterizedType lhs, final ParameterizedType rhs) {
+ if (lhs.equals(rhs)) {
+ // that was easy
+ return true;
+ }
+ final Type[] lhsTypeArguments = lhs.getActualTypeArguments();
+ final Type[] rhsTypeArguments = rhs.getActualTypeArguments();
+ final int size = lhsTypeArguments.length;
+ if (rhsTypeArguments.length != size) {
+ // clearly incompatible types
+ return false;
+ }
+ for (int i = 0; i < size; i++) {
+ // verify all type arguments are assignable
+ final Type lhsArgument = lhsTypeArguments[i];
+ final Type rhsArgument = rhsTypeArguments[i];
+ if (!lhsArgument.equals(rhsArgument) &&
+ !(lhsArgument instanceof WildcardType &&
+ isWildcardAssignable((WildcardType) lhsArgument, rhsArgument))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private static boolean isWildcardAssignable(final WildcardType lhs, final Type rhs) {
+ final Type[] lhsUpperBounds = getEffectiveUpperBounds(lhs);
+ final Type[] lhsLowerBounds = getEffectiveLowerBounds(lhs);
+ if (rhs instanceof WildcardType) {
+ // oh boy, this scenario requires checking a lot of assignability!
+ final WildcardType rhsType = (WildcardType) rhs;
+ final Type[] rhsUpperBounds = getEffectiveUpperBounds(rhsType);
+ final Type[] rhsLowerBounds = getEffectiveLowerBounds(rhsType);
+ for (final Type lhsUpperBound : lhsUpperBounds) {
+ for (final Type rhsUpperBound : rhsUpperBounds) {
+ if (!isBoundAssignable(lhsUpperBound, rhsUpperBound)) {
+ return false;
+ }
+ }
+ for (final Type rhsLowerBound : rhsLowerBounds) {
+ if (!isBoundAssignable(lhsUpperBound, rhsLowerBound)) {
+ return false;
+ }
+ }
+ }
+ for (final Type lhsLowerBound : lhsLowerBounds) {
+ for (final Type rhsUpperBound : rhsUpperBounds) {
+ if (!isBoundAssignable(rhsUpperBound, lhsLowerBound)) {
+ return false;
+ }
+ }
+ for (final Type rhsLowerBound : rhsLowerBounds) {
+ if (!isBoundAssignable(rhsLowerBound, lhsLowerBound)) {
+ return false;
+ }
+ }
+ }
+ } else {
+ // phew, far less bounds to check
+ for (final Type lhsUpperBound : lhsUpperBounds) {
+ if (!isBoundAssignable(lhsUpperBound, rhs)) {
+ return false;
+ }
+ }
+ for (final Type lhsLowerBound : lhsLowerBounds) {
+ if (!isBoundAssignable(lhsLowerBound, rhs)) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ private static Type[] getEffectiveUpperBounds(final WildcardType type) {
+ final Type[] upperBounds = type.getUpperBounds();
+ return upperBounds.length == 0 ? new Type[]{Object.class} : upperBounds;
+ }
+
+ private static Type[] getEffectiveLowerBounds(final WildcardType type) {
+ final Type[] lowerBounds = type.getLowerBounds();
+ return lowerBounds.length == 0 ? new Type[]{null} : lowerBounds;
+ }
+
+ private static boolean isBoundAssignable(final Type lhs, final Type rhs) {
+ return (rhs == null) || ((lhs != null) && isAssignable(lhs, rhs));
+ }
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/util/package-info.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/util/package-info.java
new file mode 100644
index 0000000..ee1e584
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/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.
+ */
+
+/**
+ * Java annotation processor for pre-scanning Log4j 2 plugins. This is provided as an alternative to using the
+ * executable {@link org.apache.logging.log4j.plugins.util.PluginManager} class in your build process.
+ */
+package org.apache.logging.log4j.plugins.util;
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/validation/Constraint.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/validation/Constraint.java
new file mode 100644
index 0000000..6c31f1c
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/validation/Constraint.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.plugins.validation;
+
+import java.lang.annotation.*;
+
+/**
+ * Meta annotation to mark an annotation as a validation constraint. This annotation must specify a
+ * {@link ConstraintValidator} implementation class that has a default constructor.
+ *
+ * @since 2.1
+ */
+@Documented
+@Target(ElementType.ANNOTATION_TYPE)
+@Retention(RetentionPolicy.RUNTIME)
+public @interface Constraint {
+
+ /**
+ * {@link ConstraintValidator} class that implements the validation logic for the annotated constraint annotation.
+ * @return the class that implements the validation logic.
+ */
+ Class<? extends ConstraintValidator<? extends Annotation>> value();
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/validation/ConstraintValidator.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/validation/ConstraintValidator.java
new file mode 100644
index 0000000..2f638a7
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/validation/ConstraintValidator.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.plugins.validation;
+
+import java.lang.annotation.Annotation;
+
+/**
+ * Interface that {@link Constraint} annotations must implement to perform validation logic.
+ *
+ * @param <A> the {@link Constraint} annotation this interface validates.
+ * @since 2.1
+ */
+public interface ConstraintValidator<A extends Annotation> {
+
+ /**
+ * Called before this validator is used with the constraint annotation value.
+ *
+ * @param annotation the annotation value this validator will be validating.
+ */
+ void initialize(A annotation);
+
+ /**
+ * Indicates if the given value is valid.
+ *
+ * @param name the name to use for error reporting
+ * @param value the value to validate.
+ * @return {@code true} if the given value is valid.
+ */
+ boolean isValid(String name, Object value);
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/validation/ConstraintValidators.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/validation/ConstraintValidators.java
new file mode 100644
index 0000000..6236bb6
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/validation/ConstraintValidators.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.plugins.validation;
+
+import org.apache.logging.log4j.util.ReflectionUtil;
+
+import java.lang.annotation.Annotation;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Collection;
+
+/**
+ * Utility class to locate an appropriate {@link ConstraintValidator} implementation for an annotation.
+ *
+ * @since 2.1
+ */
+public final class ConstraintValidators {
+
+ private ConstraintValidators() {
+ }
+
+ /**
+ * Finds all relevant {@link ConstraintValidator} objects from an array of annotations. All validators will be
+ * {@link ConstraintValidator#initialize(Annotation) initialized} before being returned.
+ *
+ * @param annotations the annotations to find constraint validators for
+ * @return a collection of ConstraintValidators for the given annotations
+ */
+ public static Collection<ConstraintValidator<?>> findValidators(final Annotation... annotations) {
+ final Collection<ConstraintValidator<?>> validators =
+ new ArrayList<>();
+ for (final Annotation annotation : annotations) {
+ final Class<? extends Annotation> type = annotation.annotationType();
+ if (type.isAnnotationPresent(Constraint.class)) {
+ final ConstraintValidator<?> validator = getValidator(annotation, type);
+ if (validator != null) {
+ validators.add(validator);
+ }
+ }
+ }
+ return validators;
+ }
+
+ private static <A extends Annotation> ConstraintValidator<A> getValidator(final A annotation,
+ final Class<? extends A> type) {
+ final Constraint constraint = type.getAnnotation(Constraint.class);
+ final Class<? extends ConstraintValidator<?>> validatorClass = constraint.value();
+ if (type.equals(getConstraintValidatorAnnotationType(validatorClass))) {
+ @SuppressWarnings("unchecked") // I don't think we could be any more thorough in validation here
+ final ConstraintValidator<A> validator = (ConstraintValidator<A>)
+ ReflectionUtil.instantiate(validatorClass);
+ validator.initialize(annotation);
+ return validator;
+ }
+ return null;
+ }
+
+ private static Type getConstraintValidatorAnnotationType(final Class<? extends ConstraintValidator<?>> type) {
+ for (final Type parentType : type.getGenericInterfaces()) {
+ if (parentType instanceof ParameterizedType) {
+ final ParameterizedType parameterizedType = (ParameterizedType) parentType;
+ if (ConstraintValidator.class.equals(parameterizedType.getRawType())) {
+ return parameterizedType.getActualTypeArguments()[0];
+ }
+ }
+ }
+ return Void.TYPE;
+ }
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/validation/constraints/Required.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/validation/constraints/Required.java
new file mode 100644
index 0000000..7901606
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/validation/constraints/Required.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.plugins.validation.constraints;
+
+import org.apache.logging.log4j.plugins.validation.Constraint;
+import org.apache.logging.log4j.plugins.validation.validators.RequiredValidator;
+
+import java.lang.annotation.*;
+
+/**
+ * Marks a plugin builder field or plugin factory parameter as required.
+ *
+ * @since 2.1
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD})
+@Constraint(RequiredValidator.class)
+public @interface Required {
+
+ /**
+ * The message to be logged if this constraint is violated. This should normally be overridden.
+ * @return the message to be logged if the constraint is violated.
+ */
+ String message() default "The parameter is null";
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/validation/constraints/ValidHost.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/validation/constraints/ValidHost.java
new file mode 100644
index 0000000..484c879
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/validation/constraints/ValidHost.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.plugins.validation.constraints;
+
+import org.apache.logging.log4j.plugins.validation.Constraint;
+import org.apache.logging.log4j.plugins.validation.validators.ValidHostValidator;
+
+import java.lang.annotation.*;
+import java.net.InetAddress;
+
+/**
+ * Indicates that a plugin attribute must be a valid host. This relies on the same validation rules as
+ * {@link InetAddress#getByName(String)}.
+ *
+ * @since 2.8
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD})
+@Constraint(ValidHostValidator.class)
+public @interface ValidHost {
+
+ /**
+ * The message to be logged if this constraint is violated. This should normally be overridden.
+ * @return The message to be logged if this constraint is violated.
+ */
+ String message() default "The hostname is invalid";
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/validation/constraints/ValidPort.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/validation/constraints/ValidPort.java
new file mode 100644
index 0000000..a664a28
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/validation/constraints/ValidPort.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.plugins.validation.constraints;
+
+import org.apache.logging.log4j.plugins.validation.Constraint;
+import org.apache.logging.log4j.plugins.validation.validators.ValidPortValidator;
+
+import java.lang.annotation.*;
+
+/**
+ * Indicates that a plugin attribute must be a valid port number. A valid port number is an integer between 0 and
+ * 65535.
+ *
+ * @since 2.8
+ */
+@Documented
+@Retention(RetentionPolicy.RUNTIME)
+@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD})
+@Constraint(ValidPortValidator.class)
+public @interface ValidPort {
+
+ /**
+ * The message to be logged if this constraint is violated. This should normally be overridden.
+ * @return The message to be logged if the constraint is violated.
+ */
+ String message() default "The port number is invalid";
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/validation/constraints/package-info.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/validation/constraints/package-info.java
new file mode 100644
index 0000000..298cd5a
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/validation/constraints/package-info.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.
+ */
+
+/**
+ * Validation annotations.
+ *
+ * @since 2.1
+ */
+package org.apache.logging.log4j.plugins.validation.constraints;
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/validation/package-info.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/validation/package-info.java
new file mode 100644
index 0000000..15955cb
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/validation/package-info.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.
+ */
+
+/**
+ * Constraint validators for plugin factory methods.
+ *
+ * @since 2.1
+ */
+package org.apache.logging.log4j.plugins.validation;
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/validation/validators/RequiredValidator.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/validation/validators/RequiredValidator.java
new file mode 100644
index 0000000..9df6d3b
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/validation/validators/RequiredValidator.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.plugins.validation.validators;
+
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.plugins.validation.ConstraintValidator;
+import org.apache.logging.log4j.plugins.validation.constraints.Required;
+import org.apache.logging.log4j.util.Assert;
+import org.apache.logging.log4j.status.StatusLogger;
+
+import java.util.Collection;
+import java.util.Map;
+
+/**
+ * Validator that checks an object for emptiness. Emptiness is defined here as:
+ * <ul>
+ * <li>The value {@code null}</li>
+ * <li>An object of type {@link CharSequence} with length 0</li>
+ * <li>An empty array</li>
+ * <li>An empty {@link Collection}</li>
+ * <li>An empty {@link Map}</li>
+ * </ul>
+ *
+ * @since 2.1
+ */
+public class RequiredValidator implements ConstraintValidator<Required> {
+
+ private static final Logger LOGGER = StatusLogger.getLogger();
+
+ private Required annotation;
+
+ @Override
+ public void initialize(final Required anAnnotation) {
+ this.annotation = anAnnotation;
+ }
+
+ @Override
+ public boolean isValid(final String name, final Object value) {
+ return Assert.isNonEmpty(value) || err(name);
+ }
+
+ private boolean err(final String name) {
+ LOGGER.error(annotation.message() + ": " + name);
+ return false;
+ }
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/validation/validators/ValidHostValidator.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/validation/validators/ValidHostValidator.java
new file mode 100644
index 0000000..41abbfd
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/validation/validators/ValidHostValidator.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.plugins.validation.validators;
+
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.plugins.validation.ConstraintValidator;
+import org.apache.logging.log4j.plugins.validation.constraints.ValidHost;
+import org.apache.logging.log4j.status.StatusLogger;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+/**
+ * Validator that checks an object to verify it is a valid hostname or IP address. Validation rules follow the same
+ * logic as in {@link InetAddress#getByName(String)}.
+ *
+ * @since 2.8
+ */
+public class ValidHostValidator implements ConstraintValidator<ValidHost> {
+
+ private static final Logger LOGGER = StatusLogger.getLogger();
+
+ private ValidHost annotation;
+
+ @Override
+ public void initialize(final ValidHost annotation) {
+ this.annotation = annotation;
+ }
+
+ @Override
+ public boolean isValid(final String name, final Object value) {
+ if (value == null) {
+ LOGGER.error(annotation.message());
+ return false;
+ }
+ if (value instanceof InetAddress) {
+ // InetAddress factory methods all have built in validation
+ return true;
+ }
+ try {
+ InetAddress.getByName(value.toString());
+ return true;
+ } catch (final UnknownHostException e) {
+ LOGGER.error(annotation.message(), e);
+ return false;
+ }
+ }
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/validation/validators/ValidPortValidator.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/validation/validators/ValidPortValidator.java
new file mode 100644
index 0000000..27e97f0
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/validation/validators/ValidPortValidator.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.plugins.validation.validators;
+
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.plugins.validation.ConstraintValidator;
+import org.apache.logging.log4j.plugins.validation.constraints.ValidPort;
+import org.apache.logging.log4j.plugins.convert.TypeConverters;
+import org.apache.logging.log4j.status.StatusLogger;
+
+/**
+ * Validator that checks an object to verify it is a valid port number (an integer between 0 and 65535).
+ *
+ * @since 2.8
+ */
+public class ValidPortValidator implements ConstraintValidator<ValidPort> {
+
+ private static final Logger LOGGER = StatusLogger.getLogger();
+
+ private ValidPort annotation;
+
+ @Override
+ public void initialize(final ValidPort annotation) {
+ this.annotation = annotation;
+ }
+
+ @Override
+ public boolean isValid(final String name, final Object value) {
+ if (value instanceof CharSequence) {
+ return isValid(name, TypeConverters.convert(value.toString(), Integer.class, -1));
+ }
+ if (!Integer.class.isInstance(value)) {
+ LOGGER.error(annotation.message());
+ return false;
+ }
+ final int port = (int) value;
+ if (port < 0 || port > 65535) {
+ LOGGER.error(annotation.message());
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/validation/validators/package-info.java b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/validation/validators/package-info.java
new file mode 100644
index 0000000..cfe2041
--- /dev/null
+++ b/log4j-plugins/src/main/java/org/apache/logging/log4j/plugins/validation/validators/package-info.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.
+ */
+
+/**
+ * ConstraintValidator implementations for the constraint annotations.
+ *
+ * @since 2.1
+ */
+package org.apache.logging.log4j.plugins.validation.validators;
diff --git a/log4j-plugins/src/main/resources/META-INF/services/javax.annotation.processing.Processor b/log4j-plugins/src/main/resources/META-INF/services/javax.annotation.processing.Processor
new file mode 100644
index 0000000..5d6951a
--- /dev/null
+++ b/log4j-plugins/src/main/resources/META-INF/services/javax.annotation.processing.Processor
@@ -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.
+#
+org.apache.logging.log4j.plugins.processor.PluginProcessor
diff --git a/log4j-plugins/src/test/java/org/apache/logging/log4j/plugins/convert/TypeConverterRegistryTest.java b/log4j-plugins/src/test/java/org/apache/logging/log4j/plugins/convert/TypeConverterRegistryTest.java
new file mode 100644
index 0000000..6e4b059
--- /dev/null
+++ b/log4j-plugins/src/test/java/org/apache/logging/log4j/plugins/convert/TypeConverterRegistryTest.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.plugins.convert;
+
+import org.junit.Test;
+
+import static org.hamcrest.Matchers.instanceOf;
+import static org.junit.Assert.*;
+
+public class TypeConverterRegistryTest {
+
+ @Test(expected = NullPointerException.class)
+ public void testFindNullConverter() throws Exception {
+ TypeConverterRegistry.getInstance().findCompatibleConverter(null);
+ }
+
+ @Test
+ public void testFindBooleanConverter() throws Exception {
+ final TypeConverter<?> converter = TypeConverterRegistry.getInstance().findCompatibleConverter(Boolean.class);
+ assertNotNull(converter);
+ assertTrue((Boolean) converter.convert("TRUE"));
+ }
+
+ @Test
+ public void testFindPrimitiveBooleanConverter() throws Exception {
+ final TypeConverter<?> converter = TypeConverterRegistry.getInstance().findCompatibleConverter(Boolean.TYPE);
+ assertNotNull(converter);
+ assertTrue((Boolean) converter.convert("tRUe"));
+ }
+
+ @SuppressWarnings("unchecked")
+ @Test
+ public void testFindCharSequenceConverterUsingStringConverter() throws Exception {
+ final TypeConverter<CharSequence> converter = (TypeConverter<CharSequence>)
+ 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
+ public void testFindNumberConverter() throws Exception {
+ final TypeConverter<Number> numberTypeConverter = (TypeConverter<Number>)
+ 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
+ public void testFindEnumConverter() throws Exception {
+ final TypeConverter<Foo> fooTypeConverter = (TypeConverter<Foo>)
+ 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-plugins/src/test/java/org/apache/logging/log4j/plugins/processor/FakePlugin.java b/log4j-plugins/src/test/java/org/apache/logging/log4j/plugins/processor/FakePlugin.java
new file mode 100644
index 0000000..48ea7dc
--- /dev/null
+++ b/log4j-plugins/src/test/java/org/apache/logging/log4j/plugins/processor/FakePlugin.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.plugins.processor;
+
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginAliases;
+
+/**
+ * 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 {
+ }
+}
diff --git a/log4j-plugins/src/test/java/org/apache/logging/log4j/plugins/processor/PluginCacheTest.java b/log4j-plugins/src/test/java/org/apache/logging/log4j/plugins/processor/PluginCacheTest.java
new file mode 100644
index 0000000..4a56066
--- /dev/null
+++ b/log4j-plugins/src/test/java/org/apache/logging/log4j/plugins/processor/PluginCacheTest.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.plugins.processor;
+
+import static org.junit.Assert.assertEquals;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class PluginCacheTest {
+
+ @Test
+ public void testOutputIsReproducibleWhenInputOrderingChanges() throws IOException {
+ PluginCache cacheA = new PluginCache();
+ createCategory(cacheA, "one", Arrays.asList("bravo", "alpha", "charlie"));
+ createCategory(cacheA, "two", Arrays.asList("alpha", "charlie", "bravo"));
+ assertEquals(cacheA.getAllCategories().size(), 2);
+ assertEquals(cacheA.getAllCategories().get("one").size(), 3);
+ assertEquals(cacheA.getAllCategories().get("two").size(), 3);
+ PluginCache cacheB = new PluginCache();
+ createCategory(cacheB, "two", Arrays.asList("bravo", "alpha", "charlie"));
+ createCategory(cacheB, "one", Arrays.asList("alpha", "charlie", "bravo"));
+ assertEquals(cacheB.getAllCategories().size(), 2);
+ assertEquals(cacheB.getAllCategories().get("one").size(), 3);
+ assertEquals(cacheB.getAllCategories().get("two").size(), 3);
+ assertEquals(Objects.toString(cacheA.getAllCategories()), Objects.toString(cacheB.getAllCategories()));
+ }
+
+ private void createCategory(PluginCache cache, String categoryName, List<String> entryNames) {
+ Map<String, PluginEntry> category = cache.getCategory(categoryName);
+ for (String entryName: entryNames) {
+ PluginEntry entry = new PluginEntry();
+ entry.setKey(entryName);
+ entry.setClassName("com.example.Plugin");
+ entry.setName("name");
+ entry.setCategory(categoryName);
+ category.put(entryName, entry);
+ }
+ }
+
+}
diff --git a/log4j-plugins/src/test/java/org/apache/logging/log4j/plugins/processor/PluginProcessorTest.java b/log4j-plugins/src/test/java/org/apache/logging/log4j/plugins/processor/PluginProcessorTest.java
new file mode 100644
index 0000000..b3bd425
--- /dev/null
+++ b/log4j-plugins/src/test/java/org/apache/logging/log4j/plugins/processor/PluginProcessorTest.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.plugins.processor;
+
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginAliases;
+import org.apache.logging.log4j.plugins.util.PluginType;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.Assert.*;
+
+@RunWith(JUnit4.class)
+public class PluginProcessorTest {
+
+ private static PluginService pluginService;
+
+ private final Plugin p = FakePlugin.class.getAnnotation(Plugin.class);
+
+ @BeforeClass
+ public static void setUpClass() throws Exception {
+ Class<?> clazz = PluginProcessor.class.getClassLoader().loadClass("org.apache.logging.log4j.plugins.plugins.Log4jPlugins");
+ assertNotNull("Could not locate plugins class", clazz);
+ pluginService = (PluginService) clazz.getDeclaredConstructor().newInstance();;
+ }
+
+ @Test
+ public void testTestCategoryFound() throws Exception {
+ assertNotNull("No plugin annotation on FakePlugin.", p);
+ final List<PluginType<?>> testCategory = pluginService.getCategory(p.category());
+ assertNotEquals("No plugins were found.", 0, pluginService.size());
+ assertNotNull("The category '" + p.category() + "' was not found.", testCategory);
+ assertFalse(testCategory.isEmpty());
+ }
+
+ @Test
+ public void testFakePluginFoundWithCorrectInformation() throws Exception {
+ final List<PluginType<?>> list = pluginService.getCategory(p.category());
+ assertNotNull(list);
+ final PluginEntry fake = getEntry(list, p.name());
+ assertNotNull(fake);
+ verifyFakePluginEntry(p.name(), fake);
+ }
+
+ @Test
+ public void testFakePluginAliasesContainSameInformation() throws Exception {
+ final PluginAliases aliases = FakePlugin.class.getAnnotation(PluginAliases.class);
+ for (final String alias : aliases.value()) {
+ final List<PluginType<?>> list = pluginService.getCategory(p.category());
+ assertNotNull(list);
+ final PluginEntry fake = getEntry(list, alias);
+ assertNotNull(fake);
+ verifyFakePluginEntry(alias, fake);
+ }
+ }
+
+ private void verifyFakePluginEntry(final String name, final PluginEntry fake) {
+ assertNotNull("The plugin '" + name.toLowerCase() + "' was not found.", fake);
+ assertEquals(FakePlugin.class.getName(), fake.getClassName());
+ assertEquals(name.toLowerCase(), fake.getKey());
+ assertEquals(Plugin.EMPTY, p.elementType());
+ assertEquals(name, fake.getName());
+ assertEquals(p.printObject(), fake.isPrintable());
+ assertEquals(p.deferChildren(), fake.isDefer());
+ }
+
+ @Test
+ public void testNestedPlugin() throws Exception {
+ final Plugin p = FakePlugin.Nested.class.getAnnotation(Plugin.class);
+ final List<PluginType<?>> list = pluginService.getCategory(p.category());
+ assertNotNull(list);
+ final PluginEntry nested = getEntry(list, p.name());
+ assertNotNull(nested);
+ assertEquals(p.name().toLowerCase(), 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());
+ }
+
+ private PluginEntry getEntry(List<PluginType<?>> list, String name) {
+ for (PluginType<?> type : list) {
+ if (type.getPluginEntry().getName().equalsIgnoreCase(name)) {
+ return type.getPluginEntry();
+ }
+ }
+ return null;
+ }
+}
diff --git a/log4j-plugins/src/test/java/org/apache/logging/log4j/plugins/util/ResolverUtilCustomProtocolTest.java b/log4j-plugins/src/test/java/org/apache/logging/log4j/plugins/util/ResolverUtilCustomProtocolTest.java
new file mode 100644
index 0000000..d0b35d1
--- /dev/null
+++ b/log4j-plugins/src/test/java/org/apache/logging/log4j/plugins/util/ResolverUtilCustomProtocolTest.java
@@ -0,0 +1,204 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache license, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the license for the specific language governing permissions and
+ * limitations under the license.
+ */
+
+package org.apache.logging.log4j.plugins.util;
+
+import org.apache.logging.log4j.junit.CleanFolders;
+import org.apache.logging.log4j.junit.URLStreamHandlerFactoryRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.RuleChain;
+
+import java.io.IOException;
+import java.net.*;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Enumeration;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Tests the ResolverUtil class for custom protocol like bundleresource, vfs, vfszip.
+ */
+public class ResolverUtilCustomProtocolTest {
+
+ @Rule
+ public URLStreamHandlerFactoryRule rule = new URLStreamHandlerFactoryRule(new NoopURLStreamHandlerFactory());
+
+ @Rule
+ public RuleChain chain = RuleChain.outerRule(new CleanFolders(ResolverUtilTest.WORK_DIR));
+
+ 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() throws IOException {
+ // do nothing
+ }
+ };
+ }
+
+ @Override
+ protected URLConnection openConnection(final URL url, final Proxy proxy) {
+ return open(url, proxy);
+ }
+
+ @Override
+ protected int getDefaultPort() {
+ return 1;
+ }
+ };
+ }
+ }
+
+ 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<URL> getResources(final String name) throws IOException {
+ return findResources(name);
+ }
+
+ @Override
+ protected Enumeration<URL> findResources(final String name) throws IOException {
+ return Collections.enumeration(Arrays.asList(findResource(name)));
+ }
+ }
+
+ @Test
+ public void testExtractPathFromVfsEarJarWindowsUrl() throws Exception {
+ final URL url = new URL(
+ "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/");
+ final String expected = "/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/";
+ assertEquals(expected, new ResolverUtil().extractPath(url));
+ }
+
+ @Test
+ public void testExtractPathFromVfsWarClassesWindowsUrl() throws Exception {
+ final URL url = new URL(
+ "vfs:/C:/jboss/jboss-eap-6.4/standalone/deployments/test-log4j2-web-standalone.war/WEB-INF/classes/org/hypik/test/jboss/eap7/logging/config/");
+ final String expected = "/C:/jboss/jboss-eap-6.4/standalone/deployments/test-log4j2-web-standalone.war/WEB-INF/classes/org/hypik/test/jboss/eap7/logging/config/";
+ assertEquals(expected, new ResolverUtil().extractPath(url));
+ }
+
+ @Test
+ public void testExtractPathFromVfsWarClassesLinuxUrl() throws Exception {
+ final URL url = new URL(
+ "vfs:/content/mycustomweb.war/WEB-INF/classes/org/hypik/test/jboss/log4j2/logging/pluginweb/");
+ final String expected = "/content/mycustomweb.war/WEB-INF/classes/org/hypik/test/jboss/log4j2/logging/pluginweb/";
+ assertEquals(expected, new ResolverUtil().extractPath(url));
+ }
+
+ @Test
+ public void testExtractPathFromVfszipUrl() throws Exception {
+ final URL url = new URL(
+ "vfszip:/home2/jboss-5.0.1.CR2/jboss-as/server/ais/ais-deploy/myear.ear/mywar.war/WEB-INF/some.xsd");
+ final String expected = "/home2/jboss-5.0.1.CR2/jboss-as/server/ais/ais-deploy/myear.ear/mywar.war/WEB-INF/some.xsd";
+ assertEquals(expected, new ResolverUtil().extractPath(url));
+ }
+
+ @Test
+ public void testExtractPathFromVfsEarJarLinuxUrl() throws Exception {
+ final URL url = new URL(
+ "vfs:/content/test-log4k2-ear.ear/lib/test-log4j2-jar-plugins.jar/org/hypik/test/jboss/log4j2/pluginjar/");
+ final String expected = "/content/test-log4k2-ear.ear/lib/test-log4j2-jar-plugins.jar/org/hypik/test/jboss/log4j2/pluginjar/";
+ assertEquals(expected, new ResolverUtil().extractPath(url));
+ }
+
+ @Test
+ public void testExtractPathFromVfszipUrlWithPlusCharacters() throws Exception {
+ final URL url = new URL("vfszip:/path+with+plus/file+name+with+plus.xml");
+ final String expected = "/path+with+plus/file+name+with+plus.xml";
+ assertEquals(expected, new ResolverUtil().extractPath(url));
+ }
+
+ @Test
+ public void testExtractPathFromVfsUrlWithPlusCharacters() throws Exception {
+ final URL url = new URL("vfs:/path+with+plus/file+name+with+plus.xml");
+ final String expected = "/path+with+plus/file+name+with+plus.xml";
+ assertEquals(expected, new ResolverUtil().extractPath(url));
+ }
+
+ @Test
+ public void testExtractPathFromResourceBundleUrl() throws Exception {
+ final URL url = new URL("bundleresource:/some/path/some/file.properties");
+ final String expected = "/some/path/some/file.properties";
+ assertEquals(expected, new ResolverUtil().extractPath(url));
+ }
+
+ @Test
+ public void testExtractPathFromResourceBundleUrlWithPlusCharacters() throws Exception {
+ final URL url = new URL("bundleresource:/some+path/some+file.properties");
+ final String expected = "/some+path/some+file.properties";
+ assertEquals(expected, new ResolverUtil().extractPath(url));
+ }
+
+ @Test
+ public void testFindInPackageFromVfsDirectoryURL() throws Exception {
+ try (final URLClassLoader cl = ResolverUtilTest.compileAndCreateClassLoader("3")) {
+ final ResolverUtil resolverUtil = new ResolverUtil();
+ resolverUtil
+ .setClassLoader(new SingleURLClassLoader(new URL("vfs:/" + ResolverUtilTest.WORK_DIR + "/resolverutil3/customplugin3/"), cl));
+ resolverUtil.findInPackage(new PluginRegistry.PluginTest(), "customplugin3");
+ assertEquals("Class not found in packages", 1, resolverUtil.getClasses().size());
+ assertEquals("Unexpected class resolved", cl.loadClass("customplugin3.FixedString3"),
+ resolverUtil.getClasses().iterator().next());
+ }
+ }
+
+ @Test
+ public void testFindInPackageFromVfsJarURL() throws Exception {
+ try (final URLClassLoader cl = ResolverUtilTest.compileJarAndCreateClassLoader("4")) {
+ final ResolverUtil resolverUtil = new ResolverUtil();
+ resolverUtil.setClassLoader(new SingleURLClassLoader(
+ new URL("vfs:/" + ResolverUtilTest.WORK_DIR + "/resolverutil4/customplugin4.jar/customplugin4/"), cl));
+ resolverUtil.findInPackage(new PluginRegistry.PluginTest(), "customplugin4");
+ assertEquals("Class not found in packages", 1, resolverUtil.getClasses().size());
+ assertEquals("Unexpected class resolved", cl.loadClass("customplugin4.FixedString4"),
+ resolverUtil.getClasses().iterator().next());
+ }
+ }
+
+}
diff --git a/log4j-plugins/src/test/java/org/apache/logging/log4j/plugins/util/ResolverUtilTest.java b/log4j-plugins/src/test/java/org/apache/logging/log4j/plugins/util/ResolverUtilTest.java
new file mode 100644
index 0000000..361fe7b
--- /dev/null
+++ b/log4j-plugins/src/test/java/org/apache/logging/log4j/plugins/util/ResolverUtilTest.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.logging.log4j.plugins.util;
+
+import org.apache.logging.log4j.junit.CleanFolders;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.RuleChain;
+
+import javax.tools.*;
+import java.io.File;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.*;
+import java.nio.file.*;
+import java.util.*;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertNotNull;
+
+/**
+ * Tests the ResolverUtil class.
+ */
+public class ResolverUtilTest {
+
+ static final String WORK_DIR = "target/testpluginsutil";
+
+ @Rule
+ public RuleChain chain = RuleChain.outerRule(new CleanFolders(WORK_DIR));
+
+ @Test
+ public void testExtractPathFromJarUrl() throws Exception {
+ final URL url = new URL("jar:file:/C:/Users/me/.m2/repository/junit/junit/4.11/junit-4.11.jar!/org/junit/Test.class");
+ final String expected = "/C:/Users/me/.m2/repository/junit/junit/4.11/junit-4.11.jar";
+ assertEquals(expected, new ResolverUtil().extractPath(url));
+ }
+
+ @Test
+ public 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);
+ assertNotNull("No url returned for " + existingFile, url);
+ if (!url.getProtocol().equals("jar")) {
+ // create fake jar: URL that resolves to existing file
+ url = new URL("jar:" + url.toExternalForm() + "!/some/entry");
+ }
+ final String actual = new ResolverUtil().extractPath(url);
+ assertTrue("should not be decoded: " + actual, actual.endsWith(existingFile));
+ }
+
+ @Test
+ public void testFileFromUriWithSpacesAndPlusCharactersInName() throws Exception {
+ final String existingFile = "/s p a c e s/log4j+config+with+plus+characters.xml";
+ testExtractPathFromJarUrlNotDecodedIfFileExists(existingFile);
+ }
+
+ @Test
+ public void testExtractPathFromJarUrlDecodedIfFileDoesNotExist() throws Exception {
+ final URL url = new URL("jar:file:/path+with+plus/file+does+not+exist.jar!/some/file");
+ final String expected = "/path with plus/file does not exist.jar";
+ assertEquals(expected, new ResolverUtil().extractPath(url));
+ }
+
+ @Test
+ public void testExtractPathFromFileUrl() throws Exception {
+ final URL url = new URL("file:/C:/Users/me/workspace/log4j2/log4j-core/target/test-classes/log4j2-config.xml");
+ final String expected = "/C:/Users/me/workspace/log4j2/log4j-core/target/test-classes/log4j2-config.xml";
+ assertEquals(expected, new ResolverUtil().extractPath(url));
+ }
+
+ @Test
+ public void testExtractPathFromFileUrlNotDecodedIfFileExists() throws Exception {
+ final String existingFile = "/log4j+config+with+plus+characters.xml";
+ final URL url = ResolverUtilTest.class.getResource(existingFile);
+ assertTrue("should be file url but was " + url, "file".equals(url.getProtocol()));
+
+ final String actual = new ResolverUtil().extractPath(url);
+ assertTrue("should not be decoded: " + actual, actual.endsWith(existingFile));
+ }
+
+ @Test
+ public void testExtractPathFromFileUrlDecodedIfFileDoesNotExist() throws Exception {
+ final URL url = new URL("file:///path+with+plus/file+does+not+exist.xml");
+ final String expected = "/path with plus/file does not exist.xml";
+ assertEquals(expected, new ResolverUtil().extractPath(url));
+ }
+
+ @Test
+ public void testExtractPathFromHttpUrl() throws Exception {
+ final URL url = new URL("http://java.sun.com/index.html#chapter1");
+ final String expected = "/index.html";
+ assertEquals(expected, new ResolverUtil().extractPath(url));
+ }
+
+ @Test
+ public void testExtractPathFromHttpUrlWithPlusCharacters() throws Exception {
+ final URL url = new URL("http://www.server.com/path+with+plus/file+name+with+plus.jar!/org/junit/Test.class");
+ final String expected = "/path with plus/file name with plus.jar";
+ assertEquals(expected, new ResolverUtil().extractPath(url));
+ }
+
+ @Test
+ public void testExtractPathFromHttpsComplexUrl() throws Exception {
+ final URL url = new URL("https://issues.apache.org/jira/browse/LOG4J2-445?focusedCommentId=13862479&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-13862479");
+ final String expected = "/jira/browse/LOG4J2-445";
+ assertEquals(expected, new ResolverUtil().extractPath(url));
+ }
+
+ @Test
+ public void testExtractPathFromFtpUrl() throws Exception {
+ final URL url = new URL("ftp://user001:secretpassword@private.ftp-servers.example.com/mydirectory/myfile.txt");
+ final String expected = "/mydirectory/myfile.txt";
+ assertEquals(expected, new ResolverUtil().extractPath(url));
+ }
+
+ @Test
+ public void testExtractPathFromFtpUrlWithPlusCharacters() throws Exception {
+ final URL url = new URL("ftp://user001:secretpassword@private.ftp-servers.example.com/my+directory/my+file.txt");
+ final String expected = "/my directory/my file.txt";
+ assertEquals(expected, new ResolverUtil().extractPath(url));
+ }
+
+ @Test
+ public void testFindInPackageFromDirectoryPath() throws Exception {
+ try (final URLClassLoader cl = compileAndCreateClassLoader("1")) {
+ final ResolverUtil resolverUtil = new ResolverUtil();
+ resolverUtil.setClassLoader(cl);
+ resolverUtil.findInPackage(new PluginRegistry.PluginTest(), "customplugin1");
+ assertEquals("Class not found in packages", 1, resolverUtil.getClasses().size());
+ assertEquals("Unexpected class resolved", cl.loadClass("customplugin1.FixedString1"),
+ resolverUtil.getClasses().iterator().next());
+ }
+ }
+
+ @Test
+ public void testFindInPackageFromJarPath() throws Exception {
+ try (final URLClassLoader cl = compileJarAndCreateClassLoader("2")) {
+ final ResolverUtil resolverUtil = new ResolverUtil();
+ resolverUtil.setClassLoader(cl);
+ resolverUtil.findInPackage(new PluginRegistry.PluginTest(), "customplugin2");
+ assertEquals("Class not found in packages", 1, resolverUtil.getClasses().size());
+ assertEquals("Unexpected class resolved", cl.loadClass("customplugin2.FixedString2"),
+ resolverUtil.getClasses().iterator().next());
+ }
+ }
+
+ static URLClassLoader compileJarAndCreateClassLoader(final String suffix) throws IOException, Exception {
+ final File workDir = compile(suffix);
+ final File jarFile = new File(workDir, "customplugin" + suffix + ".jar");
+ final URI jarURI = jarFile.toURI();
+ createJar(jarURI, workDir, new File(workDir,
+ "customplugin" + suffix + "/FixedString" + suffix + ".class"));
+ return URLClassLoader.newInstance(new URL[] {jarURI.toURL()});
+ }
+
+ static URLClassLoader compileAndCreateClassLoader(final String suffix) throws IOException {
+ final File workDir = compile(suffix);
+ return URLClassLoader.newInstance(new URL[] {workDir.toURI().toURL()});
+ }
+
+ static File compile(final String suffix) throws IOException {
+ final File orig = new File("target/test-classes/customplugin/FixedString.java.source");
+ final File workDir = new File(WORK_DIR, "resolverutil" + suffix);
+ final File f = new File(workDir, "customplugin" + suffix + "/FixedString" + suffix + ".java");
+ final File parent = f.getParentFile();
+ if (!parent.exists()) {
+ assertTrue("Create customplugin" + suffix + " folder KO", f.getParentFile().mkdirs());
+ }
+
+ final String content = new String(Files.readAllBytes(orig.toPath()))
+ .replaceAll("FixedString", "FixedString" + suffix)
+ .replaceAll("customplugin", "customplugin" + suffix);
+ Files.write(f.toPath(), content.getBytes());
+
+ compile(f);
+ return workDir;
+ }
+
+ static void createJar(final URI jarURI, final File workDir, final File f) throws Exception {
+ final Map<String, String> env = new HashMap<>();
+ env.put("create", "true");
+ final URI uri = URI.create("jar:file://" + jarURI.getRawPath());
+ try (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 );
+ }
+ }
+
+ static void compile(final File f) throws IOException {
+ // set up compiler
+ final JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
+ final DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<>();
+ final List<String> errors = new ArrayList<>();
+ try (final StandardJavaFileManager fileManager = compiler.getStandardFileManager(diagnostics, null, null)) {
+ final Iterable<? extends JavaFileObject> compilationUnits = fileManager.getJavaFileObjectsFromFiles(Arrays
+ .asList(f));
+
+ // compile generated source
+ // (switch off annotation processing: no need to create Log4j2Plugins.dat)
+ final List<String> options = Arrays.asList("-proc:none");
+ compiler.getTask(null, fileManager, diagnostics, options, null, compilationUnits).call();
+
+ // check we don't have any compilation errors
+ for (final Diagnostic<? extends JavaFileObject> diagnostic : diagnostics.getDiagnostics()) {
+ if (diagnostic.getKind() == Diagnostic.Kind.ERROR) {
+ errors.add(String.format("Compile error at line %d, column %d: %s%n", diagnostic.getLineNumber(),
+ diagnostic.getColumnNumber(), diagnostic.getMessage(Locale.getDefault())));
+ }
+ }
+ }
+ assertTrue(errors.toString(), errors.isEmpty());
+ }
+
+}
diff --git a/log4j-plugins/src/test/java/org/apache/logging/log4j/plugins/validation/AbstractPluginWithGenericBuilder.java b/log4j-plugins/src/test/java/org/apache/logging/log4j/plugins/validation/AbstractPluginWithGenericBuilder.java
new file mode 100644
index 0000000..0e243f2
--- /dev/null
+++ b/log4j-plugins/src/test/java/org/apache/logging/log4j/plugins/validation/AbstractPluginWithGenericBuilder.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.plugins.validation;
+
+import org.apache.logging.log4j.plugins.PluginBuilderAttribute;
+import org.apache.logging.log4j.plugins.validation.constraints.Required;
+
+/**
+ *
+ */
+public class AbstractPluginWithGenericBuilder {
+
+ public static abstract class Builder<B extends Builder<B>> {
+
+ @PluginBuilderAttribute
+ @Required(message = "The thing given by the builder is null")
+ private String thing;
+
+ @SuppressWarnings("unchecked")
+ public B asBuilder() {
+ return (B) this;
+ }
+
+ public String getThing() {
+ return thing;
+ }
+
+ public B setThing(final String name) {
+ this.thing = name;
+ return asBuilder();
+ }
+
+ }
+
+ private final String thing;
+
+ public AbstractPluginWithGenericBuilder(final String thing) {
+ super();
+ this.thing = thing;
+ }
+
+ public String getThing() {
+ return thing;
+ }
+}
diff --git a/log4j-plugins/src/test/java/org/apache/logging/log4j/plugins/validation/HostAndPort.java b/log4j-plugins/src/test/java/org/apache/logging/log4j/plugins/validation/HostAndPort.java
new file mode 100644
index 0000000..335ea49
--- /dev/null
+++ b/log4j-plugins/src/test/java/org/apache/logging/log4j/plugins/validation/HostAndPort.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.plugins.validation;
+
+import org.apache.logging.log4j.plugins.PluginAttribute;
+import org.apache.logging.log4j.plugins.PluginFactory;
+import org.apache.logging.log4j.plugins.validation.constraints.ValidHost;
+import org.apache.logging.log4j.plugins.validation.constraints.ValidPort;
+import org.apache.logging.log4j.plugins.Plugin;
+
+import java.net.InetSocketAddress;
+
+@Plugin(name = "HostAndPort", category = "Test")
+public 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 final String host,
+ @ValidPort(message = "Unit test (port)") @PluginAttribute final int port) {
+ return new HostAndPort(new InetSocketAddress(host, port));
+ }
+
+ @Override
+ public String toString() {
+ return "HostAndPort{" +
+ "address=" + address +
+ '}';
+ }
+}
diff --git a/log4j-plugins/src/test/java/org/apache/logging/log4j/plugins/validation/PluginWithGenericSubclassFoo1Builder.java b/log4j-plugins/src/test/java/org/apache/logging/log4j/plugins/validation/PluginWithGenericSubclassFoo1Builder.java
new file mode 100644
index 0000000..0ae991a
--- /dev/null
+++ b/log4j-plugins/src/test/java/org/apache/logging/log4j/plugins/validation/PluginWithGenericSubclassFoo1Builder.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.plugins.validation;
+
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginAttribute;
+import org.apache.logging.log4j.plugins.PluginFactory;
+import org.apache.logging.log4j.plugins.validation.constraints.Required;
+
+@Plugin(name = "PluginWithGenericSubclassFoo1Builder", category = "Test")
+public class PluginWithGenericSubclassFoo1Builder extends AbstractPluginWithGenericBuilder {
+
+ public static class Builder<B extends Builder<B>> extends AbstractPluginWithGenericBuilder.Builder<B>
+ implements org.apache.logging.log4j.plugins.util.Builder<PluginWithGenericSubclassFoo1Builder> {
+
+ @PluginAttribute
+ @Required(message = "The foo1 given by the builder is null")
+ private String foo1;
+
+ @Override
+ public PluginWithGenericSubclassFoo1Builder build() {
+ return new PluginWithGenericSubclassFoo1Builder(getThing(), getFoo1());
+ }
+
+ public String getFoo1() {
+ return foo1;
+ }
+
+ public B setFoo1(final String foo1) {
+ this.foo1 = foo1;
+ return asBuilder();
+ }
+
+ }
+
+ @PluginFactory
+ public static <B extends Builder<B>> B newBuilder() {
+ return new Builder<B>().asBuilder();
+ }
+
+ private final String foo1;
+
+ public PluginWithGenericSubclassFoo1Builder(final String thing, final String foo1) {
+ super(thing);
+ this.foo1 = foo1;
+ }
+
+ public String getFoo1() {
+ return foo1;
+ }
+
+}
diff --git a/log4j-plugins/src/test/java/org/apache/logging/log4j/plugins/validation/ValidatingPlugin.java b/log4j-plugins/src/test/java/org/apache/logging/log4j/plugins/validation/ValidatingPlugin.java
new file mode 100644
index 0000000..eda7145
--- /dev/null
+++ b/log4j-plugins/src/test/java/org/apache/logging/log4j/plugins/validation/ValidatingPlugin.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.plugins.validation;
+
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginBuilderAttribute;
+import org.apache.logging.log4j.plugins.PluginFactory;
+import org.apache.logging.log4j.plugins.validation.constraints.Required;
+
+import java.util.Objects;
+
+/**
+ *
+ */
+@Plugin(name = "Validator", category = "Test")
+public class ValidatingPlugin {
+
+ private final String name;
+
+ public ValidatingPlugin(final String name) {
+ this.name = Objects.requireNonNull(name, "name");
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ @PluginFactory
+ public static ValidatingPlugin newValidatingPlugin(
+ @Required(message = "The name given by the factory is null") final String name) {
+ return new ValidatingPlugin(name);
+ }
+
+ @PluginFactory
+ public static Builder newBuilder() {
+ return new Builder();
+ }
+
+ public static class Builder implements org.apache.logging.log4j.plugins.util.Builder<ValidatingPlugin> {
+
+ @PluginBuilderAttribute
+ @Required(message = "The name given by the builder is null")
+ private String name;
+
+ public Builder setName(final String name) {
+ this.name = name;
+ return this;
+ }
+
+ @Override
+ public ValidatingPlugin build() {
+ return new ValidatingPlugin(name);
+ }
+ }
+}
diff --git a/log4j-plugins/src/test/java/org/apache/logging/log4j/plugins/validation/ValidatingPluginWithGenericBuilder.java b/log4j-plugins/src/test/java/org/apache/logging/log4j/plugins/validation/ValidatingPluginWithGenericBuilder.java
new file mode 100644
index 0000000..eb9db3f
--- /dev/null
+++ b/log4j-plugins/src/test/java/org/apache/logging/log4j/plugins/validation/ValidatingPluginWithGenericBuilder.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.plugins.validation;
+
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginAttribute;
+import org.apache.logging.log4j.plugins.PluginFactory;
+import org.apache.logging.log4j.plugins.validation.constraints.Required;
+
+import java.util.Objects;
+
+/**
+ *
+ */
+@Plugin(name = "ValidatingPluginWithGenericBuilder", category = "Test")
+public class ValidatingPluginWithGenericBuilder {
+
+ private final String name;
+
+ public ValidatingPluginWithGenericBuilder(final String name) {
+ this.name = Objects.requireNonNull(name, "name");
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ @PluginFactory
+ public static <B extends Builder<B>> B newBuilder() {
+ return new Builder<B>().asBuilder();
+ }
+
+ public static class Builder<B extends Builder<B>> implements org.apache.logging.log4j.plugins.util.Builder<ValidatingPluginWithGenericBuilder> {
+
+ @PluginAttribute
+ @Required(message = "The name given by the builder is null")
+ private String name;
+
+ public B setName(final String name) {
+ this.name = name;
+ return asBuilder();
+ }
+
+ @SuppressWarnings("unchecked")
+ public B asBuilder() {
+ return (B) this;
+ }
+
+ @Override
+ public ValidatingPluginWithGenericBuilder build() {
+ return new ValidatingPluginWithGenericBuilder(name);
+ }
+ }
+}
diff --git a/log4j-plugins/src/test/java/org/apache/logging/log4j/plugins/validation/ValidatingPluginWithTypedBuilder.java b/log4j-plugins/src/test/java/org/apache/logging/log4j/plugins/validation/ValidatingPluginWithTypedBuilder.java
new file mode 100644
index 0000000..fc736b3
--- /dev/null
+++ b/log4j-plugins/src/test/java/org/apache/logging/log4j/plugins/validation/ValidatingPluginWithTypedBuilder.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.plugins.validation;
+
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginBuilderAttribute;
+import org.apache.logging.log4j.plugins.PluginFactory;
+import org.apache.logging.log4j.plugins.validation.constraints.Required;
+
+import java.util.Objects;
+
+/**
+ *
+ */
+@Plugin(name = "ValidatingPluginWithTypedBuilder", category = "Test")
+public class ValidatingPluginWithTypedBuilder {
+
+ private final String name;
+
+ public ValidatingPluginWithTypedBuilder(final String name) {
+ this.name = Objects.requireNonNull(name, "name");
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ @PluginFactory
+ public static ValidatingPluginWithTypedBuilder newValidatingPlugin(
+ @Required(message = "The name given by the factory is null") final String name) {
+ return new ValidatingPluginWithTypedBuilder(name);
+ }
+
+ @PluginFactory
+ public static Builder<Integer> newBuilder() {
+ return new Builder<>();
+ }
+
+ public static class Builder<T> implements org.apache.logging.log4j.plugins.util.Builder<ValidatingPluginWithTypedBuilder> {
+
+ @PluginBuilderAttribute
+ @Required(message = "The name given by the builder is null")
+ private String name;
+
+ public Builder<T> setName(final String name) {
+ this.name = name;
+ return this;
+ }
+
+ @Override
+ public ValidatingPluginWithTypedBuilder build() {
+ return new ValidatingPluginWithTypedBuilder(name);
+ }
+ }
+}
diff --git a/log4j-plugins/src/test/resources/customplugin/FixedString.java.source b/log4j-plugins/src/test/resources/customplugin/FixedString.java.source
new file mode 100644
index 0000000..85a62ec
--- /dev/null
+++ b/log4j-plugins/src/test/resources/customplugin/FixedString.java.source
@@ -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 customplugin;
+
+import java.util.Collections;
+import java.util.Map;
+
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginAttribute;
+import org.apache.logging.log4j.plugins.PluginFactory;
+
+@Plugin(name = "FixedString", category = "Core", elementType = "plugin", printObject = true)
+public class FixedString {
+
+ private String fixedString;
+
+ @PluginFactory
+ public static FixedString create(
+ @PluginAttribute("fixedString") final String fixedString) {
+ return new FixedString(fixedString);
+ }
+
+ public FixedString(String fixedString) {
+ this.fixedString = fixedString;
+ }
+
+ public Map<String, String> getContentFormat() {
+ return Collections.emptyMap();
+ }
+}
diff --git a/log4j-plugins/src/test/resources/log4j+config+with+plus+characters.xml b/log4j-plugins/src/test/resources/log4j+config+with+plus+characters.xml
new file mode 100644
index 0000000..b85475a
--- /dev/null
+++ b/log4j-plugins/src/test/resources/log4j+config+with+plus+characters.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-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 "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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="XMLConfigTest">
+
+ <Appenders>
+ <List name="List">
+ </List>
+ </Appenders>
+ <Loggers>
+ <Root level="trace">
+ <AppenderRef ref="List"/>
+ </Root>
+ </Loggers>
+
+</Configuration>
\ No newline at end of file
diff --git a/log4j-plugins/src/test/resources/s p a c e s/log4j+config+with+plus+characters.xml b/log4j-plugins/src/test/resources/s p a c e s/log4j+config+with+plus+characters.xml
new file mode 100644
index 0000000..b85475a
--- /dev/null
+++ b/log4j-plugins/src/test/resources/s p a c e s/log4j+config+with+plus+characters.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-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 "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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="XMLConfigTest">
+
+ <Appenders>
+ <List name="List">
+ </List>
+ </Appenders>
+ <Loggers>
+ <Root level="trace">
+ <AppenderRef ref="List"/>
+ </Root>
+ </Loggers>
+
+</Configuration>
\ No newline at end of file
diff --git a/log4j-samples/log4j-samples-configuration/src/main/java/org/apache/logging/log4j/configuration/CustomConfiguration.java b/log4j-samples/log4j-samples-configuration/src/main/java/org/apache/logging/log4j/configuration/CustomConfiguration.java
index 6e3bddb..1e80d77 100644
--- a/log4j-samples/log4j-samples-configuration/src/main/java/org/apache/logging/log4j/configuration/CustomConfiguration.java
+++ b/log4j-samples/log4j-samples-configuration/src/main/java/org/apache/logging/log4j/configuration/CustomConfiguration.java
@@ -63,8 +63,8 @@
setName(CONFIG_NAME);
final Layout<? extends Serializable> layout = PatternLayout.newBuilder()
- .withPattern(DEFAULT_PATTERN)
- .withConfiguration(this)
+ .setPattern(DEFAULT_PATTERN)
+ .setConfiguration(this)
.build();
final Appender appender = ConsoleAppender.createDefaultAppenderForLayout(layout);
appender.start();
diff --git a/log4j-samples/log4j-samples-configuration/src/main/java/org/apache/logging/log4j/configuration/CustomConfigurationFactory.java b/log4j-samples/log4j-samples-configuration/src/main/java/org/apache/logging/log4j/configuration/CustomConfigurationFactory.java
index 53b70c7..8f536fe 100644
--- a/log4j-samples/log4j-samples-configuration/src/main/java/org/apache/logging/log4j/configuration/CustomConfigurationFactory.java
+++ b/log4j-samples/log4j-samples-configuration/src/main/java/org/apache/logging/log4j/configuration/CustomConfigurationFactory.java
@@ -23,7 +23,7 @@
import org.apache.logging.log4j.core.config.ConfigurationFactory;
import org.apache.logging.log4j.core.config.ConfigurationSource;
import org.apache.logging.log4j.core.config.Order;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.plugins.Plugin;
/**
* Factory to construct a CustomConfiguration.
diff --git a/log4j-samples/log4j-samples-flume-common/src/main/java/org/apache/logging/log4j/samples/app/LoggingController.java b/log4j-samples/log4j-samples-flume-common/src/main/java/org/apache/logging/log4j/samples/app/LoggingController.java
index e77650a..02d47b3 100755
--- a/log4j-samples/log4j-samples-flume-common/src/main/java/org/apache/logging/log4j/samples/app/LoggingController.java
+++ b/log4j-samples/log4j-samples-flume-common/src/main/java/org/apache/logging/log4j/samples/app/LoggingController.java
@@ -45,7 +45,7 @@
*/
private static Logger logger = LogManager.getLogger(LoggingController.class);
- private volatile boolean generateLog = false;
+ private volatile boolean generateLog;
private final Random ran = new Random();
private List<AuditEvent> events;
diff --git a/log4j-samples/log4j-samples-flume-common/src/main/java/org/apache/logging/log4j/samples/events/Alert.java b/log4j-samples/log4j-samples-flume-common/src/main/java/org/apache/logging/log4j/samples/events/Alert.java
index 532519f..12a3c48 100755
--- a/log4j-samples/log4j-samples-flume-common/src/main/java/org/apache/logging/log4j/samples/events/Alert.java
+++ b/log4j-samples/log4j-samples-flume-common/src/main/java/org/apache/logging/log4j/samples/events/Alert.java
@@ -75,4 +75,4 @@
*/
@Constraint(required = true)
void setType(String type);
-}
\ No newline at end of file
+}
diff --git a/log4j-samples/log4j-samples-flume-common/src/main/java/org/apache/logging/log4j/samples/events/ChangePassword.java b/log4j-samples/log4j-samples-flume-common/src/main/java/org/apache/logging/log4j/samples/events/ChangePassword.java
index 6f725db..b0e7e17 100755
--- a/log4j-samples/log4j-samples-flume-common/src/main/java/org/apache/logging/log4j/samples/events/ChangePassword.java
+++ b/log4j-samples/log4j-samples-flume-common/src/main/java/org/apache/logging/log4j/samples/events/ChangePassword.java
@@ -31,4 +31,4 @@
*/
@Constraint(required = true)
void setMember(String member);
-}
\ No newline at end of file
+}
diff --git a/log4j-samples/log4j-samples-flume-common/src/main/java/org/apache/logging/log4j/samples/events/Login.java b/log4j-samples/log4j-samples-flume-common/src/main/java/org/apache/logging/log4j/samples/events/Login.java
index 4cb2d33..2f16043 100755
--- a/log4j-samples/log4j-samples-flume-common/src/main/java/org/apache/logging/log4j/samples/events/Login.java
+++ b/log4j-samples/log4j-samples-flume-common/src/main/java/org/apache/logging/log4j/samples/events/Login.java
@@ -43,4 +43,4 @@
*/
void setStartPageOption(String startPageOption);
-}
\ No newline at end of file
+}
diff --git a/log4j-samples/log4j-samples-flume-common/src/main/java/org/apache/logging/log4j/samples/events/ScheduledTransaction.java b/log4j-samples/log4j-samples-flume-common/src/main/java/org/apache/logging/log4j/samples/events/ScheduledTransaction.java
index e231f28..acb03ea 100755
--- a/log4j-samples/log4j-samples-flume-common/src/main/java/org/apache/logging/log4j/samples/events/ScheduledTransaction.java
+++ b/log4j-samples/log4j-samples-flume-common/src/main/java/org/apache/logging/log4j/samples/events/ScheduledTransaction.java
@@ -125,4 +125,4 @@
void setToAccount(String toAccount);
-}
\ No newline at end of file
+}
diff --git a/log4j-samples/log4j-samples-flume-common/src/main/java/org/apache/logging/log4j/samples/events/Transfer.java b/log4j-samples/log4j-samples-flume-common/src/main/java/org/apache/logging/log4j/samples/events/Transfer.java
index 13e5764..17377a9 100755
--- a/log4j-samples/log4j-samples-flume-common/src/main/java/org/apache/logging/log4j/samples/events/Transfer.java
+++ b/log4j-samples/log4j-samples-flume-common/src/main/java/org/apache/logging/log4j/samples/events/Transfer.java
@@ -169,4 +169,4 @@
void setType(String type);
-}
\ No newline at end of file
+}
diff --git a/log4j-samples/log4j-samples-flume-embedded/pom.xml b/log4j-samples/log4j-samples-flume-embedded/pom.xml
index 00b95d0..f6246ed 100644
--- a/log4j-samples/log4j-samples-flume-embedded/pom.xml
+++ b/log4j-samples/log4j-samples-flume-embedded/pom.xml
@@ -28,6 +28,7 @@
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ <deploy.plugin.version>2.8.2</deploy.plugin.version>
</properties>
<dependencies>
<dependency>
diff --git a/log4j-samples/log4j-samples-flume-remote/pom.xml b/log4j-samples/log4j-samples-flume-remote/pom.xml
index c5e16f3..ef40dc3 100644
--- a/log4j-samples/log4j-samples-flume-remote/pom.xml
+++ b/log4j-samples/log4j-samples-flume-remote/pom.xml
@@ -28,6 +28,7 @@
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ <deploy.plugin.version>2.8.2</deploy.plugin.version>
</properties>
<dependencies>
<dependency>
diff --git a/log4j-samples/log4j-samples-loggerProperties/src/main/java/org/apache/logging/log4j/lookup/CustomLookup.java b/log4j-samples/log4j-samples-loggerProperties/src/main/java/org/apache/logging/log4j/lookup/CustomLookup.java
index 8bd3415..0000f88 100644
--- a/log4j-samples/log4j-samples-loggerProperties/src/main/java/org/apache/logging/log4j/lookup/CustomLookup.java
+++ b/log4j-samples/log4j-samples-loggerProperties/src/main/java/org/apache/logging/log4j/lookup/CustomLookup.java
@@ -20,7 +20,7 @@
import org.apache.logging.log4j.Marker;
import org.apache.logging.log4j.MarkerManager;
import org.apache.logging.log4j.core.LogEvent;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.plugins.Plugin;
import org.apache.logging.log4j.core.lookup.AbstractLookup;
import org.apache.logging.log4j.core.lookup.StrLookup;
import org.apache.logging.log4j.status.StatusLogger;
diff --git a/log4j-samples/log4j-samples-loggerProperties/src/main/java/org/apache/logging/log4j/lookup/MapMessageLookup.java b/log4j-samples/log4j-samples-loggerProperties/src/main/java/org/apache/logging/log4j/lookup/MapMessageLookup.java
index 1184382..a800a21 100644
--- a/log4j-samples/log4j-samples-loggerProperties/src/main/java/org/apache/logging/log4j/lookup/MapMessageLookup.java
+++ b/log4j-samples/log4j-samples-loggerProperties/src/main/java/org/apache/logging/log4j/lookup/MapMessageLookup.java
@@ -20,9 +20,10 @@
import org.apache.logging.log4j.Marker;
import org.apache.logging.log4j.MarkerManager;
import org.apache.logging.log4j.core.LogEvent;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.plugins.Plugin;
import org.apache.logging.log4j.core.lookup.AbstractLookup;
import org.apache.logging.log4j.core.lookup.StrLookup;
+import org.apache.logging.log4j.message.MapMessage;
import org.apache.logging.log4j.message.StringMapMessage;
import org.apache.logging.log4j.message.Message;
import org.apache.logging.log4j.status.StatusLogger;
@@ -52,26 +53,13 @@
@Override
public String lookup(final LogEvent event, final String key) {
final Message msg = event.getMessage();
- if (msg instanceof StringMapMessage) {
+ if (msg instanceof MapMessage) {
try {
- final Map<String, String> properties = ((StringMapMessage) msg).getData();
- if (properties == null) {
- return "";
- }
+ MapMessage<?, ?> mapMessage = (MapMessage) msg;
if (key == null || key.length() == 0 || key.equals("*")) {
- final StringBuilder sb = new StringBuilder("{");
- boolean first = true;
- for (final Map.Entry<String, String> entry : properties.entrySet()) {
- if (!first) {
- sb.append(", ");
- }
- sb.append(entry.getKey()).append("=").append(entry.getValue());
- first = false;
- }
- sb.append("}");
- return sb.toString();
+ return mapMessage.asString(MapMessage.MapFormat.JAVA_UNQUOTED.name());
}
- return properties.get(key);
+ return mapMessage.get(key);
} catch (final Exception ex) {
LOGGER.warn(LOOKUP, "Error while getting property [{}].", key, ex);
return null;
diff --git a/log4j-samples/log4j-samples-loggerProperties/src/test/java/org/apache/logging/log4j/MapMessageLookupTest.java b/log4j-samples/log4j-samples-loggerProperties/src/test/java/org/apache/logging/log4j/MapMessageLookupTest.java
new file mode 100644
index 0000000..d2f55cc
--- /dev/null
+++ b/log4j-samples/log4j-samples-loggerProperties/src/test/java/org/apache/logging/log4j/MapMessageLookupTest.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;
+
+import static org.junit.Assert.assertEquals;
+
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.impl.Log4jLogEvent;
+import org.apache.logging.log4j.lookup.MapMessageLookup;
+import org.apache.logging.log4j.message.MapMessage;
+import org.apache.logging.log4j.message.StringMapMessage;
+import org.apache.logging.log4j.message.StructuredDataMessage;
+import org.junit.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Tests {@link MapMessageLookup}
+ */
+public class MapMessageLookupTest
+{
+ @Test
+ public void testStructuredDataMessageLookup() {
+ // GIVEN: A StructuredDataMessage object
+ final StructuredDataMessage message = new StructuredDataMessage("id", "msg", "type");
+
+ message.put("A", "a");
+ message.put("B", "b");
+ message.put("C", "c");
+
+ // AND: An event with that message
+ final LogEvent event = Log4jLogEvent.newBuilder().setLevel(Level.DEBUG).setMessage(message).build();
+
+ // AND: A MapMessageLookup object
+ final MapMessageLookup lookup = new MapMessageLookup();
+
+ // WHEN: Lookup is performed
+ final String a = lookup.lookup(event, "A");
+ final String b = lookup.lookup(event, "B");
+ final String c = lookup.lookup(event, "C");
+
+ // THEN: The looked up values are correct
+ assertEquals("a", a);
+ assertEquals("b", b);
+ assertEquals("c", c);
+ }
+
+ @Test
+ public void testStringMapMessageLookup() {
+ // GIVEN: A StringMapMessage object
+ final Map<String, String> values = new HashMap<>(3);
+ values.put("A", "a");
+ values.put("B", "b");
+ values.put("C", "c");
+ final MapMessage message = new StringMapMessage(values);
+
+ // AND: An event with that message
+ final LogEvent event = Log4jLogEvent.newBuilder().setLevel(Level.DEBUG).setMessage(message).build();
+
+ // AND: A MapMessageLookup object
+ final MapMessageLookup lookup = new MapMessageLookup();
+
+ // WHEN: Lookup is performed
+ final String a = lookup.lookup(event, "A");
+ final String b = lookup.lookup(event, "B");
+ final String c = lookup.lookup(event, "C");
+
+ // THEN: The looked up values are correct
+ assertEquals("a", a);
+ assertEquals("b", b);
+ assertEquals("c", c);
+ }
+}
diff --git a/log4j-samples/pom.xml b/log4j-samples/pom.xml
index d479b3c..2148957 100644
--- a/log4j-samples/pom.xml
+++ b/log4j-samples/pom.xml
@@ -30,6 +30,7 @@
<url>http://maven.apache.org</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ <revapi.skip>true</revapi.skip>
</properties>
<dependencyManagement>
<dependencies>
diff --git a/log4j-slf4j-impl/pom.xml b/log4j-slf4j-impl/pom.xml
index 3ed84fb..af0b355 100644
--- a/log4j-slf4j-impl/pom.xml
+++ b/log4j-slf4j-impl/pom.xml
@@ -32,6 +32,7 @@
<docLabel>SLF4J Documentation</docLabel>
<projectDir>/slf4j-impl</projectDir>
<slf4j.version>1.7.25</slf4j.version>
+ <module.name>org.apache.logging.log4j.slf4j</module.name>
</properties>
<dependencies>
<dependency>
@@ -77,6 +78,12 @@
<scope>test</scope>
</dependency>
<dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-to-slf4j</artifactId>
+ <scope>test</scope>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
@@ -121,6 +128,7 @@
<Implementation-Vendor-Id>org.apache</Implementation-Vendor-Id>
<X-Compile-Source-JDK>${maven.compiler.source}</X-Compile-Source-JDK>
<X-Compile-Target-JDK>${maven.compiler.target}</X-Compile-Target-JDK>
+ <Automatic-Module-Name>${module.name}</Automatic-Module-Name>
</manifestEntries>
</archive>
</configuration>
@@ -150,6 +158,42 @@
</executions>
</plugin>
<plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>loop-test</id>
+ <phase>test</phase>
+ <goals>
+ <goal>test</goal>
+ </goals>
+ <configuration>
+ <includes>
+ <include>**/OverflowTest.java</include>
+ </includes>
+ </configuration>
+ </execution>
+ <execution>
+ <id>default-test</id>
+ <phase>test</phase>
+ <goals>
+ <goal>test</goal>
+ </goals>
+ <configuration>
+ <includes>
+ <include>**/*Test.java</include>
+ </includes>
+ <excludes>
+ <exclude>**/OverflowTest.java</exclude>
+ </excludes>
+ <classpathDependencyExcludes>
+ <classpathDependencyExcludes>org.apache.logging.log4j:log4j-to-slf4j</classpathDependencyExcludes>
+ </classpathDependencyExcludes>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<configuration>
@@ -206,6 +250,7 @@
project -->
<detectOfflineLinks>false</detectOfflineLinks>
<linksource>true</linksource>
+ <source>8</source>
</configuration>
<reportSets>
<reportSet>
@@ -217,15 +262,14 @@
</reportSets>
</plugin>
<plugin>
- <groupId>org.codehaus.mojo</groupId>
- <artifactId>findbugs-maven-plugin</artifactId>
- <version>${findbugs.plugin.version}</version>
+ <groupId>com.github.spotbugs</groupId>
+ <artifactId>spotbugs-maven-plugin</artifactId>
<configuration>
<fork>true</fork>
<jvmArgs>-Duser.language=en</jvmArgs>
<threshold>Normal</threshold>
<effort>Default</effort>
- <excludeFilterFile>${log4jParentDir}/findbugs-exclude-filter.xml</excludeFilterFile>
+ <excludeFilterFile>${log4jParentDir}/spotbugs-exclude-filter.xml</excludeFilterFile>
</configuration>
</plugin>
<plugin>
diff --git a/log4j-slf4j-impl/src/main/java/org/apache/logging/slf4j/Log4jLogger.java b/log4j-slf4j-impl/src/main/java/org/apache/logging/slf4j/Log4jLogger.java
index ec4decb..1fa8080 100644
--- a/log4j-slf4j-impl/src/main/java/org/apache/logging/slf4j/Log4jLogger.java
+++ b/log4j-slf4j-impl/src/main/java/org/apache/logging/slf4j/Log4jLogger.java
@@ -42,16 +42,16 @@
private static final long serialVersionUID = 7869000638091304316L;
private static final Marker EVENT_MARKER = MarkerFactory.getMarker("EVENT");
+ private static final EventDataConverter CONVERTER = createConverter();
+
private final boolean eventLogger;
private transient ExtendedLogger logger;
private final String name;
- private transient EventDataConverter converter;
public Log4jLogger(final ExtendedLogger logger, final String name) {
this.logger = logger;
this.eventLogger = "EventLogger".equals(name);
this.name = name;
- this.converter = createConverter();
}
@Override
@@ -363,8 +363,8 @@
return;
}
final Message msg;
- if (eventLogger && marker != null && marker.contains(EVENT_MARKER) && converter != null) {
- msg = converter.convertEvent(message, params, throwable);
+ if (CONVERTER != null && eventLogger && marker != null && marker.contains(EVENT_MARKER)) {
+ msg = CONVERTER.convertEvent(message, params, throwable);
} else if (params == null) {
msg = new SimpleMessage(message);
} else {
@@ -400,7 +400,6 @@
// always perform the default de-serialization first
aInputStream.defaultReadObject();
logger = LogManager.getContext().getLogger(name);
- converter = createConverter();
}
/**
diff --git a/log4j-slf4j-impl/src/main/java/org/apache/logging/slf4j/Log4jLoggerFactory.java b/log4j-slf4j-impl/src/main/java/org/apache/logging/slf4j/Log4jLoggerFactory.java
index 37cff9f..4ad0c5f 100644
--- a/log4j-slf4j-impl/src/main/java/org/apache/logging/slf4j/Log4jLoggerFactory.java
+++ b/log4j-slf4j-impl/src/main/java/org/apache/logging/slf4j/Log4jLoggerFactory.java
@@ -17,6 +17,7 @@
package org.apache.logging.slf4j;
import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.LoggingException;
import org.apache.logging.log4j.spi.AbstractLoggerAdapter;
import org.apache.logging.log4j.spi.LoggerContext;
import org.apache.logging.log4j.util.StackLocatorUtil;
@@ -30,11 +31,12 @@
private static final String FQCN = Log4jLoggerFactory.class.getName();
private static final String PACKAGE = "org.slf4j";
+ private static final String TO_SLF4J_CONTEXT = "org.apache.logging.slf4j.SLF4JLoggerContext";
@Override
protected Logger newLogger(final String name, final LoggerContext context) {
final String key = Logger.ROOT_LOGGER_NAME.equals(name) ? LogManager.ROOT_LOGGER_NAME : name;
- return new Log4jLogger(context.getLogger(key), name);
+ return new Log4jLogger(validateContext(context).getLogger(key), name);
}
@Override
@@ -43,4 +45,10 @@
return anchor == null ? LogManager.getContext() : getContext(StackLocatorUtil.getCallerClass(anchor));
}
+ private LoggerContext validateContext(final LoggerContext context) {
+ if (TO_SLF4J_CONTEXT.equals(context.getClass().getName())) {
+ throw new LoggingException("log4j-slf4j-impl cannot be present with log4j-to-slf4j");
+ }
+ return context;
+ }
}
diff --git a/log4j-slf4j-impl/src/test/java/org/apache/logging/slf4j/Log4j2_1482_Slf4jTest.java b/log4j-slf4j-impl/src/test/java/org/apache/logging/slf4j/Log4j2_1482_Slf4jTest.java
index 8934f29..e04cbaf 100644
--- a/log4j-slf4j-impl/src/test/java/org/apache/logging/slf4j/Log4j2_1482_Slf4jTest.java
+++ b/log4j-slf4j-impl/src/test/java/org/apache/logging/slf4j/Log4j2_1482_Slf4jTest.java
@@ -38,4 +38,4 @@
logger.info("Info Message!", val1, val2, val3);
}
-}
\ No newline at end of file
+}
diff --git a/log4j-slf4j-impl/src/test/java/org/apache/logging/slf4j/LoggerContextTest.java b/log4j-slf4j-impl/src/test/java/org/apache/logging/slf4j/LoggerContextTest.java
new file mode 100644
index 0000000..742fc9b
--- /dev/null
+++ b/log4j-slf4j-impl/src/test/java/org/apache/logging/slf4j/LoggerContextTest.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.slf4j;
+
+import org.apache.logging.log4j.core.LifeCycle;
+import org.apache.logging.log4j.spi.LoggerContext;
+import org.junit.Test;
+import org.slf4j.impl.StaticLoggerBinder;
+
+import java.util.Set;
+
+import static org.junit.Assert.*;
+
+/**
+ * Tests cleanup of the LoggerContexts.
+ */
+public class LoggerContextTest {
+
+ @Test
+ public void testCleanup() throws Exception {
+ Log4jLoggerFactory factory = (Log4jLoggerFactory) StaticLoggerBinder.getSingleton().getLoggerFactory();
+ factory.getLogger("test");
+ Set<LoggerContext> set = factory.getLoggerContexts();
+ LoggerContext ctx1 = set.toArray(new LoggerContext[0])[0];
+ assertTrue("LoggerContext is not enabled for shutdown", ctx1 instanceof LifeCycle);
+ ((LifeCycle) ctx1).stop();
+ set = factory.getLoggerContexts();
+ assertTrue("Expected no LoggerContexts", set.isEmpty());
+ }
+}
diff --git a/log4j-slf4j-impl/src/test/java/org/apache/logging/slf4j/OverflowTest.java b/log4j-slf4j-impl/src/test/java/org/apache/logging/slf4j/OverflowTest.java
new file mode 100644
index 0000000..10e683e
--- /dev/null
+++ b/log4j-slf4j-impl/src/test/java/org/apache/logging/slf4j/OverflowTest.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.slf4j;
+
+import org.apache.logging.log4j.LoggingException;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static org.junit.Assert.fail;
+
+/**
+ * Tests StackOverflow when slf4j-impl and to-slf4j are both present.
+ */
+public class OverflowTest {
+
+ @Test
+ public void log() {
+ try {
+ final Logger logger = LoggerFactory.getLogger(OverflowTest.class);
+ fail("Failed to detect inclusion of log4j-to-slf4j");
+ } catch (LoggingException ex) {
+ // Expected exception.
+ } catch (StackOverflowError error) {
+ fail("Failed to detect inclusion of log4j-to-slf4j, caught StackOverflowError");
+ }
+ }
+
+}
diff --git a/log4j-slf4j18-impl/pom.xml b/log4j-slf4j18-impl/pom.xml
index 6d204d1..f9353ef 100644
--- a/log4j-slf4j18-impl/pom.xml
+++ b/log4j-slf4j18-impl/pom.xml
@@ -31,7 +31,7 @@
<log4jParentDir>${basedir}/..</log4jParentDir>
<docLabel>SLF4J Documentation</docLabel>
<projectDir>/slf4j18</projectDir>
- <slf4j.version>1.8.0-alpha2</slf4j.version>
+ <slf4j.version>1.8.0-beta4</slf4j.version>
<module.name>org.apache.logging.log4j.slf4j</module.name>
</properties>
<dependencies>
@@ -78,6 +78,12 @@
<scope>test</scope>
</dependency>
<dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-to-slf4j</artifactId>
+ <scope>test</scope>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
@@ -101,6 +107,42 @@
</executions>
</plugin>
<plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>loop-test</id>
+ <phase>test</phase>
+ <goals>
+ <goal>test</goal>
+ </goals>
+ <configuration>
+ <includes>
+ <include>**/OverflowTest.java</include>
+ </includes>
+ </configuration>
+ </execution>
+ <execution>
+ <id>default-test</id>
+ <phase>test</phase>
+ <goals>
+ <goal>test</goal>
+ </goals>
+ <configuration>
+ <includes>
+ <include>**/*Test.java</include>
+ </includes>
+ <excludes>
+ <exclude>**/OverflowTest.java</exclude>
+ </excludes>
+ <classpathDependencyExcludes>
+ <classpathDependencyExcludes>org.apache.logging.log4j:log4j-to-slf4j</classpathDependencyExcludes>
+ </classpathDependencyExcludes>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<configuration>
@@ -163,6 +205,7 @@
project -->
<detectOfflineLinks>false</detectOfflineLinks>
<linksource>true</linksource>
+ <source>8</source>
</configuration>
<reportSets>
<reportSet>
@@ -174,15 +217,14 @@
</reportSets>
</plugin>
<plugin>
- <groupId>org.codehaus.mojo</groupId>
- <artifactId>findbugs-maven-plugin</artifactId>
- <version>${findbugs.plugin.version}</version>
+ <groupId>com.github.spotbugs</groupId>
+ <artifactId>spotbugs-maven-plugin</artifactId>
<configuration>
<fork>true</fork>
<jvmArgs>-Duser.language=en</jvmArgs>
<threshold>Normal</threshold>
<effort>Default</effort>
- <excludeFilterFile>${log4jParentDir}/findbugs-exclude-filter.xml</excludeFilterFile>
+ <excludeFilterFile>${log4jParentDir}/spotbugs-exclude-filter.xml</excludeFilterFile>
</configuration>
</plugin>
<plugin>
diff --git a/log4j-slf4j18-impl/src/main/java/org/apache/logging/slf4j/EventDataConverter.java b/log4j-slf4j18-impl/src/main/java/org/apache/logging/slf4j/EventDataConverter.java
deleted file mode 100644
index 620232a..0000000
--- a/log4j-slf4j18-impl/src/main/java/org/apache/logging/slf4j/EventDataConverter.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.slf4j;
-
-import java.util.Map;
-
-import org.apache.logging.log4j.message.Message;
-import org.apache.logging.log4j.message.ParameterizedMessage;
-import org.apache.logging.log4j.message.StructuredDataMessage;
-import org.slf4j.ext.EventData;
-
-/**
- *
- */
-public class EventDataConverter {
-
- public Message convertEvent(final String message, final Object[] objects, final Throwable throwable) {
- try {
- final EventData data = objects != null && objects[0] instanceof EventData ?
- (EventData) objects[0] : new EventData(message);
- final StructuredDataMessage msg =
- new StructuredDataMessage(data.getEventId(), data.getMessage(), data.getEventType());
- for (final Map.Entry<String, Object> entry : data.getEventMap().entrySet()) {
- final String key = entry.getKey();
- if (EventData.EVENT_TYPE.equals(key) || EventData.EVENT_ID.equals(key)
- || EventData.EVENT_MESSAGE.equals(key)) {
- continue;
- }
- msg.put(key, String.valueOf(entry.getValue()));
- }
- return msg;
- } catch (final Exception ex) {
- return new ParameterizedMessage(message, objects, throwable);
- }
- }
-}
diff --git a/log4j-slf4j18-impl/src/main/java/org/apache/logging/slf4j/Log4jLogger.java b/log4j-slf4j18-impl/src/main/java/org/apache/logging/slf4j/Log4jLogger.java
index 6cbb7c4..10ad49c 100644
--- a/log4j-slf4j18-impl/src/main/java/org/apache/logging/slf4j/Log4jLogger.java
+++ b/log4j-slf4j18-impl/src/main/java/org/apache/logging/slf4j/Log4jLogger.java
@@ -27,9 +27,7 @@
import org.apache.logging.log4j.message.ParameterizedMessage;
import org.apache.logging.log4j.message.SimpleMessage;
import org.apache.logging.log4j.spi.ExtendedLogger;
-import org.apache.logging.log4j.util.LoaderUtil;
import org.slf4j.Marker;
-import org.slf4j.MarkerFactory;
import org.slf4j.spi.LocationAwareLogger;
/**
@@ -40,19 +38,14 @@
public static final String FQCN = Log4jLogger.class.getName();
private static final long serialVersionUID = 7869000638091304316L;
- private static final Marker EVENT_MARKER = MarkerFactory.getMarker("EVENT");
- private final boolean eventLogger;
private transient ExtendedLogger logger;
private final String name;
- private transient EventDataConverter converter;
private transient Log4jMarkerFactory markerFactory;
public Log4jLogger(final Log4jMarkerFactory markerFactory, final ExtendedLogger logger, final String name) {
this.markerFactory = markerFactory;
this.logger = logger;
- this.eventLogger = "EventLogger".equals(name);
this.name = name;
- this.converter = createConverter();
}
@Override
@@ -364,9 +357,7 @@
return;
}
final Message msg;
- if (eventLogger && marker != null && marker.contains(EVENT_MARKER) && converter != null) {
- msg = converter.convertEvent(message, params, throwable);
- } else if (params == null) {
+ if (params == null) {
msg = new SimpleMessage(message);
} else {
msg = new ParameterizedMessage(message, params, throwable);
@@ -400,7 +391,6 @@
// always perform the default de-serialization first
aInputStream.defaultReadObject();
logger = LogManager.getContext().getLogger(name);
- converter = createConverter();
markerFactory = ((Log4jLoggerFactory) org.slf4j.LoggerFactory.getILoggerFactory()).getMarkerFactory();
}
@@ -412,15 +402,6 @@
aOutputStream.defaultWriteObject();
}
- private static EventDataConverter createConverter() {
- try {
- LoaderUtil.loadClass("org.slf4j.ext.EventData");
- return new EventDataConverter();
- } catch (final ClassNotFoundException cnfe) {
- return null;
- }
- }
-
private static Level getLevel(final int i) {
switch (i) {
case TRACE_INT:
diff --git a/log4j-slf4j18-impl/src/main/java/org/apache/logging/slf4j/Log4jLoggerFactory.java b/log4j-slf4j18-impl/src/main/java/org/apache/logging/slf4j/Log4jLoggerFactory.java
index fcf1d66..6bfc45b 100644
--- a/log4j-slf4j18-impl/src/main/java/org/apache/logging/slf4j/Log4jLoggerFactory.java
+++ b/log4j-slf4j18-impl/src/main/java/org/apache/logging/slf4j/Log4jLoggerFactory.java
@@ -17,6 +17,7 @@
package org.apache.logging.slf4j;
import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.LoggingException;
import org.apache.logging.log4j.spi.AbstractLoggerAdapter;
import org.apache.logging.log4j.spi.LoggerContext;
import org.apache.logging.log4j.util.StackLocatorUtil;
@@ -31,6 +32,7 @@
private static final String FQCN = Log4jLoggerFactory.class.getName();
private static final String PACKAGE = "org.slf4j";
private final Log4jMarkerFactory markerFactory;
+ private static final String TO_SLF4J_CONTEXT = "org.apache.logging.slf4j.SLF4JLoggerContext";
public Log4jLoggerFactory(Log4jMarkerFactory markerFactory) {
this.markerFactory = markerFactory;
@@ -40,7 +42,7 @@
@Override
protected Logger newLogger(final String name, final LoggerContext context) {
final String key = Logger.ROOT_LOGGER_NAME.equals(name) ? LogManager.ROOT_LOGGER_NAME : name;
- return new Log4jLogger(markerFactory, context.getLogger(key), name);
+ return new Log4jLogger(markerFactory, validateContext(context).getLogger(key), name);
}
@Override
@@ -54,5 +56,11 @@
return markerFactory;
}
+ private LoggerContext validateContext(final LoggerContext context) {
+ if (TO_SLF4J_CONTEXT.equals(context.getClass().getName())) {
+ throw new LoggingException("log4j-slf4j-impl cannot be present with log4j-to-slf4j");
+ }
+ return context;
+ }
}
diff --git a/log4j-slf4j18-impl/src/test/java/org/apache/logging/slf4j/Log4j2_1482_Slf4jTest.java b/log4j-slf4j18-impl/src/test/java/org/apache/logging/slf4j/Log4j2_1482_Slf4jTest.java
index 8934f29..e04cbaf 100644
--- a/log4j-slf4j18-impl/src/test/java/org/apache/logging/slf4j/Log4j2_1482_Slf4jTest.java
+++ b/log4j-slf4j18-impl/src/test/java/org/apache/logging/slf4j/Log4j2_1482_Slf4jTest.java
@@ -38,4 +38,4 @@
logger.info("Info Message!", val1, val2, val3);
}
-}
\ No newline at end of file
+}
diff --git a/log4j-slf4j18-impl/src/test/java/org/apache/logging/slf4j/LoggerContextTest.java b/log4j-slf4j18-impl/src/test/java/org/apache/logging/slf4j/LoggerContextTest.java
new file mode 100644
index 0000000..3c6403c
--- /dev/null
+++ b/log4j-slf4j18-impl/src/test/java/org/apache/logging/slf4j/LoggerContextTest.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.slf4j;
+
+import org.apache.logging.log4j.core.LifeCycle;
+import org.apache.logging.log4j.spi.LoggerContext;
+import org.junit.Test;
+import org.slf4j.LoggerFactory;
+
+import java.util.Set;
+
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Tests cleanup of the LoggerContexts.
+ */
+public class LoggerContextTest {
+
+ @Test
+ public void testCleanup() throws Exception {
+ Log4jLoggerFactory factory = (Log4jLoggerFactory) LoggerFactory.getILoggerFactory();
+ factory.getLogger("test");
+ Set<LoggerContext> set = factory.getLoggerContexts();
+ LoggerContext ctx1 = set.toArray(new LoggerContext[0])[0];
+ assertTrue("LoggerContext is not enabled for shutdown", ctx1 instanceof LifeCycle);
+ ((LifeCycle) ctx1).stop();
+ set = factory.getLoggerContexts();
+ assertTrue("Expected no LoggerContexts", set.isEmpty());
+ }
+}
diff --git a/log4j-slf4j18-impl/src/test/java/org/apache/logging/slf4j/LoggerTest.java b/log4j-slf4j18-impl/src/test/java/org/apache/logging/slf4j/LoggerTest.java
index 0524074..c60f1ad 100644
--- a/log4j-slf4j18-impl/src/test/java/org/apache/logging/slf4j/LoggerTest.java
+++ b/log4j-slf4j18-impl/src/test/java/org/apache/logging/slf4j/LoggerTest.java
@@ -21,7 +21,6 @@
import static org.junit.Assert.assertTrue;
import java.util.List;
-import java.util.Locale;
import org.apache.logging.log4j.junit.LoggerContextRule;
import org.apache.logging.log4j.test.appender.ListAppender;
@@ -34,8 +33,6 @@
import org.slf4j.LoggerFactory;
import org.slf4j.MDC;
import org.slf4j.Marker;
-import org.slf4j.ext.EventData;
-import org.slf4j.ext.EventLogger;
import org.slf4j.ext.XLogger;
import org.slf4j.ext.XLoggerFactory;
import org.slf4j.spi.LocationAwareLogger;
@@ -145,23 +142,6 @@
verify("List", "o.a.l.s.LoggerTest Hello, Log4j Log4j {} MDC{}" + Strings.LINE_SEPARATOR);
}
- @Test
- public void testEventLogger() {
- MDC.put("loginId", "JohnDoe");
- MDC.put("ipAddress", "192.168.0.120");
- MDC.put("locale", Locale.US.getDisplayName());
- final EventData data = new EventData();
- data.setEventType("Transfer");
- data.setEventId("Audit@18060");
- data.setMessage("Transfer Complete");
- data.put("ToAccount", "123456");
- data.put("FromAccount", "123457");
- data.put("Amount", "200.00");
- EventLogger.logEvent(data);
- MDC.clear();
- verify("EventLogger", "o.a.l.s.LoggerTest Transfer [Audit@18060 Amount=\"200.00\" FromAccount=\"123457\" ToAccount=\"123456\"] Transfer Complete" + Strings.LINE_SEPARATOR);
- }
-
private void verify(final String name, final String expected) {
final ListAppender listApp = ctx.getListAppender(name);
assertNotNull("Missing Appender", listApp);
@@ -177,6 +157,5 @@
public void cleanup() {
MDC.clear();
ctx.getListAppender("List").clear();
- ctx.getListAppender("EventLogger").clear();
}
}
diff --git a/log4j-slf4j18-impl/src/test/java/org/apache/logging/slf4j/OptionalTest.java b/log4j-slf4j18-impl/src/test/java/org/apache/logging/slf4j/OptionalTest.java
deleted file mode 100644
index a6e9fd5..0000000
--- a/log4j-slf4j18-impl/src/test/java/org/apache/logging/slf4j/OptionalTest.java
+++ /dev/null
@@ -1,69 +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.slf4j;
-
-import java.util.List;
-
-import org.apache.logging.log4j.junit.LoggerContextRule;
-import org.apache.logging.log4j.test.appender.ListAppender;
-import org.apache.logging.log4j.util.Strings;
-import org.junit.Before;
-import org.junit.ClassRule;
-import org.junit.Test;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.slf4j.MDC;
-import org.slf4j.Marker;
-import org.slf4j.MarkerFactory;
-
-import static org.junit.Assert.*;
-
-/**
- *
- */
-public class OptionalTest {
-
- private static final String CONFIG = "log4j-test1.xml";
-
- @ClassRule
- public static final LoggerContextRule CTX = new LoggerContextRule(CONFIG);
-
- Logger logger = LoggerFactory.getLogger("EventLogger");
- Marker marker = MarkerFactory.getMarker("EVENT");
-
- @Test
- public void testEventLogger() {
- logger.info(marker, "This is a test");
- MDC.clear();
- verify("EventLogger", "o.a.l.s.OptionalTest This is a test" + Strings.LINE_SEPARATOR);
- }
-
- private void verify(final String name, final String expected) {
- final ListAppender listApp = CTX.getListAppender(name);
- final List<String> events = listApp.getMessages();
- assertTrue("Incorrect number of messages. Expected 1 Actual " + events.size(), events.size()== 1);
- final String actual = events.get(0);
- assertEquals("Incorrect message. Expected " + expected + ". Actual " + actual, expected, actual);
- listApp.clear();
- }
-
- @Before
- public void cleanup() {
- CTX.getListAppender("List").clear();
- CTX.getListAppender("EventLogger").clear();
- }
-}
diff --git a/log4j-slf4j18-impl/src/test/java/org/apache/logging/slf4j/OverflowTest.java b/log4j-slf4j18-impl/src/test/java/org/apache/logging/slf4j/OverflowTest.java
new file mode 100644
index 0000000..b12facb
--- /dev/null
+++ b/log4j-slf4j18-impl/src/test/java/org/apache/logging/slf4j/OverflowTest.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.slf4j;
+
+import org.apache.logging.log4j.LoggingException;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import static org.junit.Assert.fail;
+
+/**
+ * Tests StackOverflow when slf4j-impl and to-slf4j are both present.
+ */
+public class OverflowTest {
+
+ @Test
+ public void log() {
+ try {
+ final Logger logger = LoggerFactory.getLogger(OverflowTest.class);
+ fail("Failed to detect inclusion of log4j-to-slf4j");
+ } catch (LoggingException ex) {
+ // Expected exception.
+ } catch (StackOverflowError error) {
+ fail("Failed to detect inclusion of log4j-to-slf4j, caught StackOverflowError");
+ }
+ }
+}
diff --git a/log4j-slf4j18-impl/src/test/resources/log4j-test1.xml b/log4j-slf4j18-impl/src/test/resources/log4j-test1.xml
index a64bdfa..07a2be6 100644
--- a/log4j-slf4j18-impl/src/test/resources/log4j-test1.xml
+++ b/log4j-slf4j18-impl/src/test/resources/log4j-test1.xml
@@ -6,9 +6,6 @@
<ThresholdFilter level="trace"/>
<Appenders>
- <List name="EventLogger">
- <PatternLayout pattern="%C{1.} %m%n"/>
- </List>
<Console name="STDOUT">
<PatternLayout pattern="%C{1.} %m MDC%X%n"/>
</Console>
@@ -24,10 +21,6 @@
</Appenders>
<Loggers>
- <Logger name="EventLogger" level="info" additivity="false">
- <AppenderRef ref="EventLogger"/>
- </Logger>>
-
<Logger name="org.apache.logging.log4j.test2" level="debug" additivity="false">
<AppenderRef ref="File"/>
</Logger>
diff --git a/log4j-smtp/pom.xml b/log4j-smtp/pom.xml
index f53b662..33ed5c2 100644
--- a/log4j-smtp/pom.xml
+++ b/log4j-smtp/pom.xml
@@ -111,6 +111,7 @@
<!-- module link generation is completely broken in the javadoc plugin for a multi-module non-aggregating project -->
<detectOfflineLinks>false</detectOfflineLinks>
<linksource>true</linksource>
+ <source>8</source>
</configuration>
<reportSets>
<reportSet>
diff --git a/log4j-smtp/revapi.json b/log4j-smtp/revapi.json
new file mode 100644
index 0000000..9b24f5e
--- /dev/null
+++ b/log4j-smtp/revapi.json
@@ -0,0 +1,23 @@
+[
+ {
+ "extension": "revapi.java",
+ "configuration": {
+ "filter": {
+ "classes": {
+ "exclude": [
+ ]
+ }
+ }
+ }
+ },
+ {
+ "extension": "revapi.ignore",
+ "configuration": [
+ {
+ "code": "java.class.nonPublicPartOfAPI",
+ "new": "class org.apache.logging.log4j.smtp.appender.SmtpManager.FactoryData",
+ "justification": "Internal component"
+ }
+ ]
+ }
+]
\ No newline at end of file
diff --git a/log4j-smtp/src/main/java/org/apache/logging/log4j/smtp/MimeMessageBuilder.java b/log4j-smtp/src/main/java/org/apache/logging/log4j/smtp/MimeMessageBuilder.java
index 9c57d91..1656bad 100644
--- a/log4j-smtp/src/main/java/org/apache/logging/log4j/smtp/MimeMessageBuilder.java
+++ b/log4j-smtp/src/main/java/org/apache/logging/log4j/smtp/MimeMessageBuilder.java
@@ -25,7 +25,7 @@
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
-import org.apache.logging.log4j.core.util.Builder;
+import org.apache.logging.log4j.plugins.util.Builder;
/**
* Builder for {@link MimeMessage} instances.
@@ -78,14 +78,6 @@
return this;
}
- /**
- * @deprecated Use {@link #build()}.
- */
- @Deprecated
- public MimeMessage getMimeMessage() {
- return build();
- }
-
@Override
public MimeMessage build() {
return message;
diff --git a/log4j-smtp/src/main/java/org/apache/logging/log4j/smtp/appender/SmtpAppender.java b/log4j-smtp/src/main/java/org/apache/logging/log4j/smtp/appender/SmtpAppender.java
index 4d5b73b..af1f234 100644
--- a/log4j-smtp/src/main/java/org/apache/logging/log4j/smtp/appender/SmtpAppender.java
+++ b/log4j-smtp/src/main/java/org/apache/logging/log4j/smtp/appender/SmtpAppender.java
@@ -17,8 +17,6 @@
package org.apache.logging.log4j.smtp.appender;
-import java.io.Serializable;
-
import org.apache.logging.log4j.core.Appender;
import org.apache.logging.log4j.core.Core;
import org.apache.logging.log4j.core.Filter;
@@ -27,16 +25,20 @@
import org.apache.logging.log4j.core.appender.AbstractAppender;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.DefaultConfiguration;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
-import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
+import org.apache.logging.log4j.core.config.Property;
import org.apache.logging.log4j.core.config.plugins.PluginConfiguration;
-import org.apache.logging.log4j.core.config.plugins.PluginElement;
-import org.apache.logging.log4j.core.config.plugins.PluginFactory;
-import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required;
-import org.apache.logging.log4j.core.config.plugins.validation.constraints.ValidPort;
import org.apache.logging.log4j.core.filter.ThresholdFilter;
import org.apache.logging.log4j.core.layout.HtmlLayout;
+import org.apache.logging.log4j.core.net.ssl.SslConfiguration;
import org.apache.logging.log4j.core.util.Booleans;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginAttribute;
+import org.apache.logging.log4j.plugins.PluginElement;
+import org.apache.logging.log4j.plugins.PluginFactory;
+import org.apache.logging.log4j.plugins.validation.constraints.Required;
+import org.apache.logging.log4j.plugins.validation.constraints.ValidPort;
+
+import java.io.Serializable;
/**
* Send an e-mail when a specific logging event occurs, typically on errors or
@@ -64,71 +66,227 @@
/** The SMTP Manager */
private final SmtpManager manager;
- private SmtpAppender(final String name, final Filter filter, final Layout<? extends Serializable> layout, final SmtpManager manager,
- final boolean ignoreExceptions) {
- super(name, filter, layout, ignoreExceptions);
+ private SmtpAppender(final String name, final Filter filter, final Layout<? extends Serializable> layout, final boolean ignoreExceptions,
+ Property[] properties, final SmtpManager manager) {
+ super(name, filter, layout, ignoreExceptions, properties);
this.manager = manager;
}
/**
- * Create a SmtpAppender.
- *
- * @param name
- * The name of the Appender.
- * @param to
- * The comma-separated list of recipient email addresses.
- * @param cc
- * The comma-separated list of CC email addresses.
- * @param bcc
- * The comma-separated list of BCC email addresses.
- * @param from
- * The email address of the sender.
- * @param replyTo
- * The comma-separated list of reply-to email addresses.
- * @param subject The subject of the email message.
- * @param smtpProtocol The SMTP transport protocol (such as "smtps", defaults to "smtp").
- * @param smtpHost
- * The SMTP hostname to send to.
- * @param smtpPortStr
- * The SMTP port to send to.
- * @param smtpUsername
- * The username required to authenticate against the SMTP server.
- * @param smtpPassword
- * The password required to authenticate against the SMTP server.
- * @param smtpDebug
- * Enable mail session debuging on STDOUT.
- * @param bufferSizeStr
- * How many log events should be buffered for inclusion in the
- * message?
- * @param layout
- * The layout to use (defaults to HtmlLayout).
- * @param filter
- * The Filter or null (defaults to ThresholdFilter, level of
- * ERROR).
- * @param ignore If {@code "true"} (default) exceptions encountered when appending events are logged; otherwise
- * they are propagated to the caller.
- * @return The SmtpAppender.
+ * @since 2.13.2
+ */
+ public static class Builder extends AbstractAppender.Builder<Builder>
+ implements org.apache.logging.log4j.plugins.util.Builder<SmtpAppender> {
+ @PluginAttribute
+ private String to;
+
+ @PluginAttribute
+ private String cc;
+
+ @PluginAttribute
+ private String bcc;
+
+ @PluginAttribute
+ private String from;
+
+ @PluginAttribute
+ private String replyTo;
+
+ @PluginAttribute
+ private String subject;
+
+ @PluginAttribute
+ private String smtpProtocol = "smtp";
+
+ @PluginAttribute
+ private String smtpHost;
+
+ @PluginAttribute
+ @ValidPort
+ private int smtpPort;
+
+ @PluginAttribute
+ private String smtpUsername;
+
+ @PluginAttribute(sensitive = true)
+ private String smtpPassword;
+
+ @PluginAttribute
+ private boolean smtpDebug;
+
+ @PluginAttribute
+ 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<? extends Serializable> 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));
+ }
+ final SmtpManager smtpManager = SmtpManager.getSmtpManager(getConfiguration(), to, cc, bcc, from, replyTo,
+ subject, smtpProtocol, smtpHost, smtpPort, smtpUsername, smtpPassword, smtpDebug,
+ getFilter().toString(), bufferSize, sslConfiguration);
+ return new SmtpAppender(getName(), getFilter(), getLayout(), isIgnoreExceptions(), getPropertyArray(), smtpManager);
+ }
+ }
+
+ /**
+ * @since 2.13.2
*/
@PluginFactory
- public static SmtpAppender createAppender(
- @PluginConfiguration final Configuration config,
- @PluginAttribute("name") @Required final String name,
- @PluginAttribute("to") final String to,
- @PluginAttribute("cc") final String cc,
- @PluginAttribute("bcc") final String bcc,
- @PluginAttribute("from") final String from,
- @PluginAttribute("replyTo") final String replyTo,
- @PluginAttribute("subject") final String subject,
- @PluginAttribute("smtpProtocol") final String smtpProtocol,
- @PluginAttribute("smtpHost") final String smtpHost,
- @PluginAttribute(value = "smtpPort", defaultString = "0") @ValidPort final String smtpPortStr,
- @PluginAttribute("smtpUsername") final String smtpUsername,
- @PluginAttribute(value = "smtpPassword", sensitive = true) final String smtpPassword,
- @PluginAttribute("smtpDebug") final String smtpDebug,
- @PluginAttribute("bufferSize") final String bufferSizeStr,
- @PluginElement("Layout") Layout<? extends Serializable> layout,
- @PluginElement("Filter") Filter filter,
- @PluginAttribute("ignoreExceptions") final String ignore) {
+ public static Builder newBuilder() {
+ return new Builder();
+ }
+
+ /**
+ * Create a SmtpAppender.
+ * @deprecated Use {@link #newBuilder()} to create and configure a {@link Builder} instance.
+ * @see Builder
+ */
+ public static SmtpAppender createAppender(final Configuration config, final String name, final String to,
+ final String cc, final String bcc, final String from,
+ final String replyTo, final String subject, final String smtpProtocol,
+ final String smtpHost, final String smtpPortStr,
+ final String smtpUsername, final String smtpPassword,
+ final String smtpDebug, final String bufferSizeStr,
+ Layout<? extends Serializable> layout, Filter filter,
+ final String ignore) {
if (name == null) {
LOGGER.error("No name provided for SmtpAppender");
return null;
@@ -148,12 +306,12 @@
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);
+ smtpHost, smtpPort, smtpUsername, smtpPassword, isSmtpDebug, filter.toString(), bufferSize, null);
if (manager == null) {
return null;
}
- return new SmtpAppender(name, filter, layout, manager, ignoreExceptions);
+ return new SmtpAppender(name, filter, layout, ignoreExceptions, Property.EMPTY_ARRAY, manager);
}
/**
diff --git a/log4j-smtp/src/main/java/org/apache/logging/log4j/smtp/appender/SmtpManager.java b/log4j-smtp/src/main/java/org/apache/logging/log4j/smtp/appender/SmtpManager.java
index 4f1c4a1..d44a35d 100644
--- a/log4j-smtp/src/main/java/org/apache/logging/log4j/smtp/appender/SmtpManager.java
+++ b/log4j-smtp/src/main/java/org/apache/logging/log4j/smtp/appender/SmtpManager.java
@@ -35,6 +35,7 @@
import javax.mail.internet.MimeMultipart;
import javax.mail.internet.MimeUtility;
import javax.mail.util.ByteArrayDataSource;
+import javax.net.ssl.SSLSocketFactory;
import org.apache.logging.log4j.LoggingException;
import org.apache.logging.log4j.core.Layout;
@@ -46,8 +47,9 @@
import org.apache.logging.log4j.core.impl.MutableLogEvent;
import org.apache.logging.log4j.core.layout.AbstractStringLayout.Serializer;
import org.apache.logging.log4j.core.layout.PatternLayout;
+import org.apache.logging.log4j.core.net.ssl.SslConfiguration;
import org.apache.logging.log4j.core.util.CyclicBuffer;
-import org.apache.logging.log4j.core.util.NameUtil;
+import org.apache.logging.log4j.util.NameUtil;
import org.apache.logging.log4j.core.util.NetUtils;
import org.apache.logging.log4j.message.ReusableMessage;
import org.apache.logging.log4j.smtp.MimeMessageBuilder;
@@ -100,7 +102,8 @@
final String from, final String replyTo,
final String subject, String protocol, final String host,
final int port, final String username, final String password,
- final boolean isDebug, final String filterName, final int numElements) {
+ final boolean isDebug, final String filterName, final int numElements,
+ final SslConfiguration sslConfiguration) {
if (Strings.isEmpty(protocol)) {
protocol = "smtp";
}
@@ -145,7 +148,7 @@
final Serializer subjectSerializer = PatternLayout.newSerializerBuilder().setConfiguration(config).setPattern(subject).build();
return getManager(name, FACTORY, new FactoryData(to, cc, bcc, from, replyTo, subjectSerializer,
- protocol, host, port, username, password, isDebug, numElements));
+ protocol, host, port, username, password, isDebug, numElements, sslConfiguration));
}
/**
@@ -276,10 +279,12 @@
private final String password;
private final boolean isDebug;
private final int numElements;
+ private final SslConfiguration sslConfiguration;
public FactoryData(final String to, final String cc, final String bcc, final String from, final String replyTo,
final Serializer subjectSerializer, final String protocol, final String host, final int port,
- final String username, final String password, final boolean isDebug, final int numElements) {
+ final String username, final String password, final boolean isDebug, final int numElements,
+ final SslConfiguration sslConfiguration) {
this.to = to;
this.cc = cc;
this.bcc = bcc;
@@ -293,6 +298,7 @@
this.password = password;
this.isDebug = isDebug;
this.numElements = numElements;
+ this.sslConfiguration = sslConfiguration;
}
}
@@ -318,22 +324,31 @@
final String prefix = "mail." + data.protocol;
final Properties properties = PropertiesUtil.getSystemProperties();
- properties.put("mail.transport.protocol", data.protocol);
+ properties.setProperty("mail.transport.protocol", data.protocol);
if (properties.getProperty("mail.host") == null) {
// Prevent an UnknownHostException in Java 7
- properties.put("mail.host", NetUtils.getLocalHostname());
+ properties.setProperty("mail.host", NetUtils.getLocalHostname());
}
if (null != data.host) {
- properties.put(prefix + ".host", data.host);
+ properties.setProperty(prefix + ".host", data.host);
}
if (data.port > 0) {
- properties.put(prefix + ".port", String.valueOf(data.port));
+ properties.setProperty(prefix + ".port", String.valueOf(data.port));
}
final Authenticator authenticator = buildAuthenticator(data.username, data.password);
if (null != authenticator) {
- properties.put(prefix + ".auth", "true");
+ properties.setProperty(prefix + ".auth", "true");
+ }
+
+ if (data.protocol.equals("smtps")) {
+ final SslConfiguration sslConfiguration = data.sslConfiguration;
+ if (sslConfiguration != null) {
+ final SSLSocketFactory sslSocketFactory = sslConfiguration.getSslSocketFactory();
+ properties.put(prefix + ".ssl.socketFactory", sslSocketFactory);
+ properties.setProperty(prefix + ".ssl.checkserveridentity", Boolean.toString(sslConfiguration.isVerifyHostName()));
+ }
}
final Session session = Session.getInstance(properties, authenticator);
diff --git a/log4j-smtp/src/site/manual/index.md b/log4j-smtp/src/site/manual/index.md
index 318d365..6443298 100644
--- a/log4j-smtp/src/site/manual/index.md
+++ b/log4j-smtp/src/site/manual/index.md
@@ -18,7 +18,7 @@
# Apache Log4j Simple Mail Transfer Protocol module
-As of Log4j 2.11.0, Simple Mail Transfer Protocol (SMTP) support has moved from the existing module logj-core to the new module log4j-smtp.
+As of Log4j 2.11.0, Simple Mail Transfer Protocol (SMTP) support has moved from the existing module log4j-core to the new module log4j-smtp.
## Requirements
diff --git a/log4j-smtp/src/test/java/org/apache/logging/log4j/smtp/appender/SmtpAppenderAsyncTest.java b/log4j-smtp/src/test/java/org/apache/logging/log4j/smtp/appender/SmtpAppenderAsyncTest.java
index e0225cb..d086c73 100644
--- a/log4j-smtp/src/test/java/org/apache/logging/log4j/smtp/appender/SmtpAppenderAsyncTest.java
+++ b/log4j-smtp/src/test/java/org/apache/logging/log4j/smtp/appender/SmtpAppenderAsyncTest.java
@@ -95,4 +95,4 @@
public static void teardownClass() {
System.clearProperty("smtp.port");
}
-}
\ No newline at end of file
+}
diff --git a/log4j-smtp/src/test/java/org/apache/logging/log4j/smtp/appender/SmtpAppenderTest.java b/log4j-smtp/src/test/java/org/apache/logging/log4j/smtp/appender/SmtpAppenderTest.java
index 599093c..90f08c5 100644
--- a/log4j-smtp/src/test/java/org/apache/logging/log4j/smtp/appender/SmtpAppenderTest.java
+++ b/log4j-smtp/src/test/java/org/apache/logging/log4j/smtp/appender/SmtpAppenderTest.java
@@ -30,7 +30,6 @@
import org.apache.logging.log4j.core.Logger;
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.smtp.MimeMessageBuilder;
-import org.apache.logging.log4j.smtp.appender.SmtpAppender;
import org.apache.logging.log4j.test.AvailablePortFinder;
import org.junit.Test;
import org.junit.experimental.categories.Category;
@@ -41,8 +40,6 @@
public class SmtpAppenderTest {
private static final String HOST = "localhost";
- private static final int PORTNUM = AvailablePortFinder.getNextAvailable();
- private static final String PORT = String.valueOf(PORTNUM);
@Test
public void testMessageFactorySetFrom() throws MessagingException {
@@ -115,9 +112,19 @@
final String subjectKey = getClass().getName();
final String subjectValue = "SubjectValue1";
ThreadContext.put(subjectKey, subjectValue);
- final SmtpAppender appender = SmtpAppender.createAppender(null, "Test", "to@example.com", "cc@example.com",
- "bcc@example.com", "from@example.com", "replyTo@example.com", "Subject Pattern %X{" + subjectKey + "}",
- null, HOST, PORT, null, null, "false", "3", null, null, "true");
+ 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 + "}")
+ .setSmtpHost(HOST)
+ .setSmtpPort(smtpPort)
+ .setBufferSize(3)
+ .build();
appender.start();
final LoggerContext context = LoggerContext.getContext();
@@ -126,7 +133,7 @@
root.setAdditive(false);
root.setLevel(Level.DEBUG);
- final SimpleSmtpServer server = SimpleSmtpServer.start(PORTNUM);
+ final SimpleSmtpServer server = SimpleSmtpServer.start(smtpPort);
root.debug("Debug message #1");
root.debug("Debug message #2");
diff --git a/log4j-spring-cloud-config/log4j-spring-cloud-config-client/pom.xml b/log4j-spring-cloud-config/log4j-spring-cloud-config-client/pom.xml
new file mode 100644
index 0000000..f96df94
--- /dev/null
+++ b/log4j-spring-cloud-config/log4j-spring-cloud-config-client/pom.xml
@@ -0,0 +1,220 @@
+<?xml version="1.0" encoding="UTF-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 "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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 xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-spring-cloud-config</artifactId>
+ <version>3.0.0-SNAPSHOT</version>
+ <relativePath>../</relativePath>
+ </parent>
+ <artifactId>log4j-spring-cloud-config-client</artifactId>
+ <packaging>jar</packaging>
+ <name>Apache Log4j Spring Cloud Config Client Support</name>
+ <description>Apache Log4j Spring Cloud Config Client Support</description>
+ <properties>
+ <log4jParentDir>${basedir}/../..</log4jParentDir>
+ <docLabel>Log4j Spring Cloud Config Client Documentation</docLabel>
+ <projectDir>/log4j-spring-cloud-config-client</projectDir>
+ <module.name>org.apache.logging.log4j.spring.cloud.config.client</module.name>
+ </properties>
+ <dependencies>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.cloud</groupId>
+ <artifactId>spring-cloud-config-client</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.cloud</groupId>
+ <artifactId>spring-cloud-bus</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-context</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-context-support</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.hamcrest</groupId>
+ <artifactId>hamcrest-all</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>commons-logging</groupId>
+ <artifactId>commons-logging</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-core</artifactId>
+ <type>test-jar</type>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-test</artifactId>
+ <version>${spring-boot.version}</version>
+ <scope>test</scope>
+ <exclusions>
+ <exclusion>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-logging</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ </dependencies>
+ <build>
+ <plugins>
+ <!-- Include the standard NOTICE and LICENSE -->
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-remote-resources-plugin</artifactId>
+ <executions>
+ <execution>
+ <goals>
+ <goal>process</goal>
+ </goals>
+ <configuration>
+ <skip>false</skip>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <configuration>
+ <instructions>
+ <Export-Package>org.apache.logging.log4j.spring.cloud.config.controller</Export-Package>
+ </instructions>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ <reporting>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-changes-plugin</artifactId>
+ <version>${changes.plugin.version}</version>
+ <reportSets>
+ <reportSet>
+ <reports>
+ <report>changes-report</report>
+ </reports>
+ </reportSet>
+ </reportSets>
+ <configuration>
+ <issueLinkTemplate>%URL%/show_bug.cgi?id=%ISSUE%</issueLinkTemplate>
+ <useJql>true</useJql>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-checkstyle-plugin</artifactId>
+ <version>${checkstyle.plugin.version}</version>
+ <configuration>
+ <!--<propertiesLocation>${vfs.parent.dir}/checkstyle.properties</propertiesLocation> -->
+ <configLocation>${log4jParentDir}/checkstyle.xml</configLocation>
+ <suppressionsLocation>${log4jParentDir}/checkstyle-suppressions.xml</suppressionsLocation>
+ <enableRulesSummary>false</enableRulesSummary>
+ <propertyExpansion>basedir=${basedir}</propertyExpansion>
+ <propertyExpansion>licensedir=${log4jParentDir}/checkstyle-header.txt</propertyExpansion>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-javadoc-plugin</artifactId>
+ <version>${javadoc.plugin.version}</version>
+ <configuration>
+ <bottom><![CDATA[<p align="center">Copyright © {inceptionYear}-{currentYear} {organizationName}. All Rights Reserved.<br />
+ Apache Logging, Apache Log4j, Log4j, Apache, the Apache feather logo, the Apache Logging project logo,
+ and the Apache Log4j logo are trademarks of The Apache Software Foundation.</p>]]></bottom>
+ <!-- module link generation is completely broken in the javadoc plugin for a multi-module non-aggregating
+ project -->
+ <detectOfflineLinks>false</detectOfflineLinks>
+ <linksource>true</linksource>
+ <source>8</source>
+ </configuration>
+ <reportSets>
+ <reportSet>
+ <id>non-aggregate</id>
+ <reports>
+ <report>javadoc</report>
+ </reports>
+ </reportSet>
+ </reportSets>
+ </plugin>
+ <plugin>
+ <groupId>com.github.spotbugs</groupId>
+ <artifactId>spotbugs-maven-plugin</artifactId>
+ <configuration>
+ <fork>true</fork>
+ <jvmArgs>-Duser.language=en</jvmArgs>
+ <threshold>Normal</threshold>
+ <effort>Default</effort>
+ <excludeFilterFile>${log4jParentDir}/spotbugs-exclude-filter.xml</excludeFilterFile>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jxr-plugin</artifactId>
+ <version>${jxr.plugin.version}</version>
+ <reportSets>
+ <reportSet>
+ <id>non-aggregate</id>
+ <reports>
+ <report>jxr</report>
+ </reports>
+ </reportSet>
+ <reportSet>
+ <id>aggregate</id>
+ <reports>
+ <report>aggregate</report>
+ </reports>
+ </reportSet>
+ </reportSets>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-pmd-plugin</artifactId>
+ <version>${pmd.plugin.version}</version>
+ <configuration>
+ <targetJdk>${maven.compiler.target}</targetJdk>
+ </configuration>
+ </plugin>
+ </plugins>
+ </reporting>
+</project>
diff --git a/log4j-spring-cloud-config/log4j-spring-cloud-config-client/src/main/java/org/apache/logging/log4j/spring/cloud/config/client/Log4j2CloudConfigLoggingSystem.java b/log4j-spring-cloud-config/log4j-spring-cloud-config-client/src/main/java/org/apache/logging/log4j/spring/cloud/config/client/Log4j2CloudConfigLoggingSystem.java
new file mode 100644
index 0000000..a029922
--- /dev/null
+++ b/log4j-spring-cloud-config/log4j-spring-cloud-config-client/src/main/java/org/apache/logging/log4j/spring/cloud/config/client/Log4j2CloudConfigLoggingSystem.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.spring.cloud.config.client;
+
+import javax.net.ssl.HttpsURLConnection;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.MalformedURLException;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.net.URLDecoder;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Properties;
+
+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.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.composite.CompositeConfiguration;
+import org.apache.logging.log4j.core.net.ssl.LaxHostnameVerifier;
+import org.apache.logging.log4j.core.net.ssl.SslConfiguration;
+import org.apache.logging.log4j.core.net.ssl.SslConfigurationFactory;
+import org.apache.logging.log4j.core.util.AuthorizationProvider;
+import org.apache.logging.log4j.core.util.FileUtils;
+import org.apache.logging.log4j.status.StatusLogger;
+import org.apache.logging.log4j.util.PropertiesUtil;
+import org.springframework.boot.logging.LogFile;
+import org.springframework.boot.logging.LoggingInitializationContext;
+import org.springframework.boot.logging.log4j2.Log4J2LoggingSystem;
+import org.springframework.util.Assert;
+import org.springframework.util.ClassUtils;
+import org.springframework.util.ResourceUtils;
+
+/**
+ * Override Spring's implementation of the Log4j 2 Logging System to properly support Spring Cloud Config.
+ */
+public class Log4j2CloudConfigLoggingSystem extends Log4J2LoggingSystem {
+ private static final String HTTPS = "https";
+ public static final String ENVIRONMENT_KEY = "SpringEnvironment";
+ private static final String OVERRIDE_PARAM = "override";
+ private static Logger LOGGER = StatusLogger.getLogger();
+
+ public Log4j2CloudConfigLoggingSystem(ClassLoader loader) {
+ super(loader);
+ }
+
+ /**
+ * Set the environment into the ExternalContext field so that it can be obtained by SpringLookup when it
+ * is constructed. Spring will replace the ExternalContext field with a String once initialization is
+ * complete.
+ * @param initializationContext The initialization context.
+ * @param configLocation The configuration location.
+ * @param logFile the log file.
+ */
+ @Override
+ public void initialize(LoggingInitializationContext initializationContext, String configLocation, LogFile logFile) {
+ getLoggerContext().putObjectIfAbsent(ENVIRONMENT_KEY, initializationContext.getEnvironment());
+ super.initialize(initializationContext, configLocation, logFile);
+ }
+
+ @Override
+ protected String[] getStandardConfigLocations() {
+ String[] locations = super.getStandardConfigLocations();
+ PropertiesUtil props = new PropertiesUtil(new Properties());
+ String location = props.getStringProperty(ConfigurationFactory.CONFIGURATION_FILE_PROPERTY);
+ if (location != null) {
+ List<String> list = Arrays.asList(super.getStandardConfigLocations());
+ list.add(location);
+ locations = list.toArray(new String[0]);
+ }
+ return locations;
+ }
+
+ @Override
+ protected void loadDefaults(LoggingInitializationContext initializationContext, LogFile logFile) {
+ if (logFile != null) {
+ this.loadConfiguration(this.getBootPackagedConfigFile("log4j2-file.xml"), logFile);
+ } else {
+ this.loadConfiguration(this.getBootPackagedConfigFile("log4j2.xml"), logFile);
+ }
+ }
+
+ private String getBootPackagedConfigFile(String fileName) {
+ String defaultPath = ClassUtils.getPackageName(Log4J2LoggingSystem.class);
+ defaultPath = defaultPath.replace('.', '/');
+ defaultPath = defaultPath + "/" + fileName;
+ defaultPath = "classpath:" + defaultPath;
+ return defaultPath;
+ }
+
+ @Override
+ protected void loadConfiguration(String location, LogFile logFile) {
+ Assert.notNull(location, "Location must not be null");
+ try {
+ LoggerContext ctx = getLoggerContext();
+ String[] locations = parseConfigLocations(location);
+ if (locations.length == 1) {
+ final URL url = ResourceUtils.getURL(location);
+ final ConfigurationSource source = getConfigurationSource(url);
+ if (source != null) {
+ ctx.start(ConfigurationFactory.getInstance().getConfiguration(ctx, source));
+ }
+ } else {
+ final List<AbstractConfiguration> configs = new ArrayList<>();
+ for (final String sourceLocation : locations) {
+ final ConfigurationSource source = getConfigurationSource(ResourceUtils.getURL(sourceLocation));
+ if (source != null) {
+ final Configuration config = ConfigurationFactory.getInstance().getConfiguration(ctx, source);
+ if (config instanceof AbstractConfiguration) {
+ configs.add((AbstractConfiguration) config);
+ } else {
+ LOGGER.warn("Configuration at {} cannot be combined in a CompositeConfiguration", sourceLocation);
+ return;
+ }
+ }
+ }
+ if (configs.size() > 1) {
+ ctx.start(new CompositeConfiguration(configs));
+ } else {
+ ctx.start(configs.get(0));
+ }
+ }
+ }
+ catch (Exception ex) {
+ throw new IllegalStateException(
+ "Could not initialize Log4J2 logging from " + location, ex);
+ }
+ }
+
+ @Override
+ public void cleanUp() {
+ getLoggerContext().removeObject(ENVIRONMENT_KEY);
+ super.cleanUp();
+ }
+
+ private String[] parseConfigLocations(String configLocations) {
+ final String[] uris = configLocations.split("\\?");
+ final List<String> locations = new ArrayList<>();
+ if (uris.length > 1) {
+ locations.add(uris[0]);
+ try {
+ final URL url = new URL(configLocations);
+ final String[] pairs = url.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("Bad data in configuration string: {}", pair);
+ }
+ }
+ return locations.toArray(new String[0]);
+ } catch (MalformedURLException ex) {
+ LOGGER.warn("Unable to parse configuration URL {}", configLocations);
+ }
+ }
+ return new String[] {uris[0]};
+ }
+
+ private ConfigurationSource getConfigurationSource(URL url) throws IOException, URISyntaxException {
+ URLConnection urlConnection = url.openConnection();
+ AuthorizationProvider provider = ConfigurationFactory.authorizationProvider(PropertiesUtil.getProperties());
+ provider.addAuthorization(urlConnection);
+ if (url.getProtocol().equals(HTTPS)) {
+ SslConfiguration sslConfiguration = SslConfigurationFactory.getSslConfiguration();
+ if (sslConfiguration != null) {
+ ((HttpsURLConnection) urlConnection).setSSLSocketFactory(sslConfiguration.getSslSocketFactory());
+ if (!sslConfiguration.isVerifyHostName()) {
+ ((HttpsURLConnection) urlConnection).setHostnameVerifier(LaxHostnameVerifier.INSTANCE);
+ }
+ }
+ }
+ File file = FileUtils.fileFromUri(url.toURI());
+ try {
+ if (file != null) {
+ return new ConfigurationSource(urlConnection.getInputStream(), FileUtils.fileFromUri(url.toURI()));
+ } else {
+ return new ConfigurationSource(urlConnection.getInputStream(), url, urlConnection.getLastModified());
+ }
+ } catch (FileNotFoundException ex) {
+ LOGGER.info("Unable to locate file {}, ignoring.", url.toString());
+ return null;
+ }
+ }
+ private LoggerContext getLoggerContext() {
+ return (LoggerContext) LogManager.getContext(false);
+ }
+}
diff --git a/log4j-spring-cloud-config/log4j-spring-cloud-config-client/src/main/java/org/apache/logging/log4j/spring/cloud/config/client/Log4j2EventListener.java b/log4j-spring-cloud-config/log4j-spring-cloud-config-client/src/main/java/org/apache/logging/log4j/spring/cloud/config/client/Log4j2EventListener.java
new file mode 100644
index 0000000..98bfb44
--- /dev/null
+++ b/log4j-spring-cloud-config/log4j-spring-cloud-config-client/src/main/java/org/apache/logging/log4j/spring/cloud/config/client/Log4j2EventListener.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.spring.cloud.config.client;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
+import org.springframework.cloud.bus.ConditionalOnBusEnabled;
+import org.springframework.cloud.bus.SpringCloudBusClient;
+import org.springframework.cloud.bus.event.RemoteApplicationEvent;
+import org.springframework.cloud.stream.annotation.EnableBinding;
+import org.springframework.cloud.stream.annotation.StreamListener;
+import org.springframework.context.event.EventListener;
+import org.springframework.stereotype.Component;
+
+@Component
+@ConditionalOnBusEnabled
+@EnableBinding(SpringCloudBusClient.class)
+@ConditionalOnProperty(value = "spring.cloud.config.watch.enabled")
+public class Log4j2EventListener {
+ private static final Logger LOGGER = LogManager.getLogger(Log4j2EventListener.class);
+
+ @EventListener(classes = RemoteApplicationEvent.class)
+ public void acceptLocal(RemoteApplicationEvent event) {
+ LOGGER.debug("Refresh application event triggered");
+ WatchEventManager.publishEvent();
+ }
+
+ @StreamListener(SpringCloudBusClient.INPUT)
+ public void acceptRemote(RemoteApplicationEvent event) {
+ LOGGER.debug("Refresh application event triggered");
+ WatchEventManager.publishEvent();
+ }
+}
diff --git a/log4j-spring-cloud-config/log4j-spring-cloud-config-client/src/main/java/org/apache/logging/log4j/spring/cloud/config/client/SpringEnvironmentHolder.java b/log4j-spring-cloud-config/log4j-spring-cloud-config-client/src/main/java/org/apache/logging/log4j/spring/cloud/config/client/SpringEnvironmentHolder.java
new file mode 100644
index 0000000..4d38f0f
--- /dev/null
+++ b/log4j-spring-cloud-config/log4j-spring-cloud-config-client/src/main/java/org/apache/logging/log4j/spring/cloud/config/client/SpringEnvironmentHolder.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.spring.cloud.config.client;
+
+import org.apache.logging.log4j.LogManager;
+import org.springframework.core.env.Environment;
+
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+/**
+ * Provides access to the Spring Environment.
+ */
+public class SpringEnvironmentHolder {
+
+ private volatile Environment environment;
+ private final Lock lock = new ReentrantLock();
+
+
+ protected Environment getEnvironment() {
+ if (environment == null && LogManager.getFactory() != null && LogManager.getFactory().hasContext(SpringEnvironmentHolder.class.getName(), null, false)) {
+ lock.lock();
+ try {
+ if (environment == null) {
+ Object obj = LogManager.getContext(false).getObject(Log4j2CloudConfigLoggingSystem.ENVIRONMENT_KEY);
+ environment = obj instanceof Environment ? (Environment) obj : null;
+ }
+ } finally {
+ lock.unlock();
+ }
+ }
+ return environment;
+ }
+}
diff --git a/log4j-spring-cloud-config/log4j-spring-cloud-config-client/src/main/java/org/apache/logging/log4j/spring/cloud/config/client/SpringLookup.java b/log4j-spring-cloud-config/log4j-spring-cloud-config-client/src/main/java/org/apache/logging/log4j/spring/cloud/config/client/SpringLookup.java
new file mode 100644
index 0000000..36c90e7
--- /dev/null
+++ b/log4j-spring-cloud-config/log4j-spring-cloud-config-client/src/main/java/org/apache/logging/log4j/spring/cloud/config/client/SpringLookup.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.spring.cloud.config.client;
+
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.core.LogEvent;
+import org.apache.logging.log4j.core.lookup.StrLookup;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.status.StatusLogger;
+import org.springframework.core.env.Environment;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Lookup for Spring properties.
+ */
+@Plugin(name = "spring", category = StrLookup.CATEGORY)
+public class SpringLookup extends SpringEnvironmentHolder implements StrLookup {
+
+ private static final Logger LOGGER = StatusLogger.getLogger();
+ private static final String ACTIVE = "profiles.active";
+ private static final String DEFAULT = "profiles.default";
+ private static final String PATTERN = "\\[(\\d+?)\\]";
+ private static final Pattern ACTIVE_PATTERN = Pattern.compile(ACTIVE + PATTERN);
+ private static final Pattern DEFAULT_PATTERN = Pattern.compile(DEFAULT + PATTERN);
+
+ public SpringLookup() {
+ getEnvironment();
+ }
+
+ @Override
+ public String lookup(String key) {
+ Environment env = getEnvironment();
+ if (env != null) {
+ String lowerKey = key.toLowerCase();
+ if (lowerKey.startsWith(ACTIVE)) {
+ switch (env.getActiveProfiles().length) {
+ case 0: {
+ return null;
+ }
+ case 1: {
+ return env.getActiveProfiles()[0];
+ }
+ default: {
+ Matcher matcher = ACTIVE_PATTERN.matcher(key);
+ if (matcher.matches()) {
+ try {
+ int index = Integer.parseInt(matcher.group(1));
+ if (index < env.getActiveProfiles().length) {
+ return env.getActiveProfiles()[index];
+ } else {
+ LOGGER.warn("Index out of bounds for Spring active profiles: {}", index);
+ return null;
+ }
+ } catch (Exception ex) {
+ LOGGER.warn("Unable to parse {} as integer value", matcher.group(1));
+ return null;
+ }
+
+ } else {
+ StringBuilder sb = new StringBuilder();
+ for (String profile : env.getActiveProfiles()) {
+ if (sb.length() > 0) {
+ sb.append(",");
+ }
+ sb.append(profile);
+ }
+ return sb.toString();
+ }
+ }
+ }
+ } else if (lowerKey.startsWith(DEFAULT)) {
+ switch (env.getDefaultProfiles().length) {
+ case 0: {
+ return null;
+ }
+ case 1: {
+ return env.getDefaultProfiles()[0];
+ }
+ default: {
+ Matcher matcher = DEFAULT_PATTERN.matcher(key);
+ if (matcher.matches()) {
+ try {
+ int index = Integer.parseInt(matcher.group(1));
+ if (index < env.getDefaultProfiles().length) {
+ return env.getDefaultProfiles()[index];
+ } else {
+ LOGGER.warn("Index out of bounds for Spring default profiles: {}", index);
+ return null;
+ }
+ } catch (Exception ex) {
+ LOGGER.warn("Unable to parse {} as integer value", matcher.group(1));
+ return null;
+ }
+
+ } else {
+ StringBuilder sb = new StringBuilder();
+ for (String profile : env.getDefaultProfiles()) {
+ if (sb.length() > 0) {
+ sb.append(",");
+ }
+ sb.append(profile);
+ }
+ return sb.toString();
+ }
+ }
+ }
+ }
+
+ return env.getProperty(key);
+
+ }
+ return null;
+ }
+
+ @Override
+ public String lookup(LogEvent event, String key) {
+ return lookup((key));
+ }
+}
diff --git a/log4j-spring-cloud-config/log4j-spring-cloud-config-client/src/main/java/org/apache/logging/log4j/spring/cloud/config/client/SpringPropertySource.java b/log4j-spring-cloud-config/log4j-spring-cloud-config-client/src/main/java/org/apache/logging/log4j/spring/cloud/config/client/SpringPropertySource.java
new file mode 100644
index 0000000..8ffed65
--- /dev/null
+++ b/log4j-spring-cloud-config/log4j-spring-cloud-config-client/src/main/java/org/apache/logging/log4j/spring/cloud/config/client/SpringPropertySource.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.spring.cloud.config.client;
+
+import org.apache.logging.log4j.util.PropertySource;
+import org.springframework.core.env.Environment;
+
+/**
+ * Returns properties from Spring.
+ */
+public class SpringPropertySource extends SpringEnvironmentHolder implements PropertySource {
+
+ /**
+ * System properties take precendence followed by properties in Log4j properties files. Spring properties
+ * follow.
+ * @return This PropertySource's priority.
+ */
+ @Override
+ public int getPriority() {
+ return -50;
+ }
+
+ @Override
+ public String getProperty(String key) {
+ Environment environment = getEnvironment();
+ if (environment != null) {
+ return environment.getProperty(key);
+ }
+ return null;
+ }
+
+ @Override
+ public boolean containsProperty(String key) {
+ Environment environment = getEnvironment();
+ if (environment != null) {
+ return environment.containsProperty(key);
+ }
+ return false;
+ }
+}
diff --git a/log4j-spring-cloud-config/log4j-spring-cloud-config-client/src/main/java/org/apache/logging/log4j/spring/cloud/config/client/WatchEventManager.java b/log4j-spring-cloud-config/log4j-spring-cloud-config-client/src/main/java/org/apache/logging/log4j/spring/cloud/config/client/WatchEventManager.java
new file mode 100644
index 0000000..a49d5f2
--- /dev/null
+++ b/log4j-spring-cloud-config/log4j-spring-cloud-config-client/src/main/java/org/apache/logging/log4j/spring/cloud/config/client/WatchEventManager.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.spring.cloud.config.client;
+
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import org.apache.logging.log4j.core.util.WatchEventService;
+import org.apache.logging.log4j.core.util.WatchManager;
+
+/**
+ *
+ */
+public class WatchEventManager implements WatchEventService {
+ private static final ConcurrentMap<UUID, WatchManager> watchManagers = new ConcurrentHashMap<>();
+
+ public static void publishEvent() {
+ for (WatchManager manager : watchManagers.values()) {
+ manager.checkFiles();
+ }
+ }
+
+ @Override
+ public void subscribe(WatchManager manager) {
+ watchManagers.put(manager.getId(), manager);
+
+ }
+
+ @Override
+ public void unsubscribe(WatchManager manager) {
+ watchManagers.remove(manager.getId());
+ }
+}
diff --git a/log4j-spring-cloud-config/log4j-spring-cloud-config-client/src/main/resources/META-INF/services/org.apache.logging.log4j.core.util.WatchEventService b/log4j-spring-cloud-config/log4j-spring-cloud-config-client/src/main/resources/META-INF/services/org.apache.logging.log4j.core.util.WatchEventService
new file mode 100644
index 0000000..701bfd3
--- /dev/null
+++ b/log4j-spring-cloud-config/log4j-spring-cloud-config-client/src/main/resources/META-INF/services/org.apache.logging.log4j.core.util.WatchEventService
@@ -0,0 +1 @@
+org.apache.logging.log4j.spring.cloud.config.client.WatchEventManager
\ No newline at end of file
diff --git a/log4j-spring-cloud-config/log4j-spring-cloud-config-client/src/main/resources/META-INF/spring.factories b/log4j-spring-cloud-config/log4j-spring-cloud-config-client/src/main/resources/META-INF/spring.factories
new file mode 100644
index 0000000..95a972f
--- /dev/null
+++ b/log4j-spring-cloud-config/log4j-spring-cloud-config-client/src/main/resources/META-INF/spring.factories
@@ -0,0 +1 @@
+org.springframework.boot.autoconfigure.EnableAutoConfiguration=org.apache.logging.log4j.spring.cloud.config.client.Log4j2EventListener
\ No newline at end of file
diff --git a/log4j-spring-cloud-config/log4j-spring-cloud-config-client/src/main/resources/log4j2.component.properties b/log4j-spring-cloud-config/log4j-spring-cloud-config-client/src/main/resources/log4j2.component.properties
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/log4j-spring-cloud-config/log4j-spring-cloud-config-client/src/main/resources/log4j2.component.properties
diff --git a/log4j-spring-cloud-config/log4j-spring-cloud-config-client/src/main/resources/log4j2.system.properties b/log4j-spring-cloud-config/log4j-spring-cloud-config-client/src/main/resources/log4j2.system.properties
new file mode 100644
index 0000000..918a074
--- /dev/null
+++ b/log4j-spring-cloud-config/log4j-spring-cloud-config-client/src/main/resources/log4j2.system.properties
@@ -0,0 +1 @@
+org.springframework.boot.logging.LoggingSystem=org.apache.logging.log4j.spring.cloud.config.client.Log4j2CloudConfigLoggingSystem
\ No newline at end of file
diff --git a/log4j-spring-cloud-config/log4j-spring-cloud-config-client/src/site/markdown/index.md b/log4j-spring-cloud-config/log4j-spring-cloud-config-client/src/site/markdown/index.md
new file mode 100644
index 0000000..bb8892f
--- /dev/null
+++ b/log4j-spring-cloud-config/log4j-spring-cloud-config-client/src/site/markdown/index.md
@@ -0,0 +1,139 @@
+<!-- vim: set syn=markdown : -->
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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 Spring Cloud Configuration
+
+This module allows logging configuration files to be dynamically updated when new versions are available in
+Spring Cloud Configuration.
+
+## Overview
+
+Spring Boot applications initialize logging 3 times.
+1. SpringApplication declares a Logger. This Logger will be initialized using Log4j's "normal" mechanisms. Thus
+a system property named log4j2.configurationFile will be checked to see if a specific configuration file has been
+provided, otherwise it will search for a configuration file on the classpath. The property may also be declare
+in log4j2.component.properties.
+
+## Usage
+
+Log4j configuration files that specify a monitor interval of greater than zero will use polling to determine
+whether the configuration has been updated. If the monitor interval is zero then Log4j will listen for notifications
+from Spring Cloud Config and will check for configuration changes each time an event is generated. If the
+monitor interval is less than zero Log4j will not check for changes to the logging configuration.
+
+When referencing a configuration located in Spring Cloud Config the configuration should be referenced similar to
+
+```
+log4j.configurationFile=http://host.docker.internal:8888/ConfigService/sampleapp/default/master/log4j2.xml
+```
+
+Log4j also supports Composite Configurations. The standard way to do that is to concatentate the paths to the files in
+a comma separated string. Unfortunately, Spring validates the URL being provided and commas are not allowed.
+Therefore, additional configurations must be supplied as "override" query parametes.
+
+```
+log4j.configurationFile=http://host.docker.internal:8888/ConfigService/sampleapp/default/master/log4j2.xml
+?override=http://host.docker.internal:8888/ConfigService/sampleapp/default/master/log4j2-sampleapp.xml
+```
+Note that the location within the directory structure and how configuration files are located is completely
+dependent on the searchPaths setting in the Spring Cloud Config server.
+
+When running in a docker container host.docker.internal may be used as the domain name to access an application
+running on the same hose outside of the docker container. Note that in accordance with Spring Cloud Config
+practices but the application, profile, and label should be specified in the url.
+
+The Spring Cloud Config support also allows connections using TLS and/or basic authentication. When using basic
+authentication the userid and password may be specified as system properties, log4j2.component.properties or Spring
+Boot's bootstrap.yml. The table below shows the alternate names that may be used to specify the properties. Any of
+the alternatives may be used in any configuration location.
+
+| Property | Alias | Spring-like alias | Purpose |
+|----------|---------|---------|---------|
+| log4j2.configurationUserName | log4j2.config.username | logging.auth.username | User name for basic authentication |
+| log4j2.configurationPassword | log4j2.config.password | logging.auth.password | Password for basic authentication |
+| log4j2.authorizationProvider | log4j2.config.authorizationProvider | logging.auth.authorizationProvider | Class used to create HTTP Authorization header |
+
+```
+log4j2.configurationUserName=guest
+log4j2.configurationPassword=guest
+```
+As noted above, Log4j supports accessing logging configuration from bootstrap.yml. As an example, to configure reading
+from a Spring Cloud Configuration service using basic authoriztion you can do:
+```
+spring:
+ application:
+ name: myApp
+ cloud:
+ config:
+ uri: https://spring-configuration-server.mycorp.com
+ username: appuser
+ password: changeme
+
+logging:
+ config: classpath:log4j2.xml
+ label: ${spring.cloud.config.label}
+
+---
+spring:
+ profiles: dev
+
+logging:
+ config: https://spring-configuration-server.mycorp.com/myApp/default/${logging.label}/log4j2-dev.xml
+ auth:
+ username: appuser
+ password: changeme
+```
+
+Note that Log4j currently does not directly support encrypting the password. However, Log4j does use Spring's
+standard APIs to access properties in the Spring configuration so any customizations made to Spring's property
+handling would apply to the properties Log4j uses as well.
+
+If more extensive authentication is required an ```AuthorizationProvider``` can be implemented and the fully
+qualified class name in
+the ```log4j2.authorizationProvider``` system property, in log4j2.component.properties or in Spring's bootstrap.yml
+using either the ```log4j2.authorizationProvider``` key or with the key ```logging.auth.authorizationProvider```.
+
+TLS can be enabled by adding the following system properties or defining them in log4j2.component.properties
+
+| Property | Optional or Default Value | Description |
+| ------------- |-------|:-------------|
+| log4j2.trustStoreLocation | Optional | The location of the trust store. If not provided the default trust store will be used.|
+| log4j2.trustStorePassword | Optional | Password needed to access the trust store. |
+| log4j2.trustStorePasswordFile | Optional | The location of a file that contains the password for the trust store. |
+| log4j2.trustStorePasswordEnvironmentVariable | Optional | The name of the environment variable that contains the trust store password. |
+| log4j2.trustStoreKeyStoreType | Required if keystore location provided | The type of key store. |
+| log4j2.trustStoreKeyManagerFactoryAlgorithm | Optional | Java cryptographic algorithm. |
+| log4j2.keyStoreLocation | Optional | The location of the key store. If not provided the default key store will be used.|
+| log4j2.keyStorePassword | Optional | Password needed to access the key store. |
+| log4j2.keyStorePasswordFile | Optional | The location of a file that contains the password for the key store. |
+| log4j2.keyStorePasswordEnvironmentVariable | Optional | The name of the environment variable that contains the key store password.|
+| log4j2.keyStoreType | Required if trust store location provided. | The type of key store. |
+| log4j2.keyStoreKeyManagerFactoryAlgorithm | Optional | Java cryptographic algorithm. |
+| log4j2.sslVerifyHostName | false | true or false |
+
+
+
+## Requirements
+
+The Log4j 2 Spring Cloud Configuration integration has a dependency on Log4j 2 API, Log4j 2 Core, and
+Spring Cloud Configuration versions 2.0.3.RELEASE or 2.1.1.RELEASE or later versions it either release series.
+For more information, see [Runtime Dependencies](../../runtime-dependencies.html).
+
+
+
+
diff --git a/log4j-spring-cloud-config/log4j-spring-cloud-config-client/src/site/site.xml b/log4j-spring-cloud-config/log4j-spring-cloud-config-client/src/site/site.xml
new file mode 100644
index 0000000..5abfffd
--- /dev/null
+++ b/log4j-spring-cloud-config/log4j-spring-cloud-config-client/src/site/site.xml
@@ -0,0 +1,52 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+-->
+<project name="Log4j Spring Cloud Config Integration"
+ xmlns="http://maven.apache.org/DECORATION/1.4.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/DECORATION/1.4.0 http://maven.apache.org/xsd/decoration-1.4.0.xsd">
+ <body>
+ <links>
+ <item name="Apache" href="http://www.apache.org/" />
+ <item name="Logging Services" href="http://logging.apache.org/"/>
+ <item name="Log4j" href="../index.html"/>
+ </links>
+
+ <!-- Component-specific reports -->
+ <menu ref="reports"/>
+
+ <!-- Overall Project Info -->
+ <menu name="Log4j Project Information" img="icon-info-sign">
+ <item name="Dependencies" href="../dependencies.html" />
+ <item name="Dependency Convergence" href="../dependency-convergence.html" />
+ <item name="Dependency Management" href="../dependency-management.html" />
+ <item name="Project Team" href="../team-list.html" />
+ <item name="Mailing Lists" href="../mail-lists.html" />
+ <item name="Issue Tracking" href="../issue-tracking.html" />
+ <item name="Project License" href="../license.html" />
+ <item name="Source Repository" href="../source-repository.html" />
+ <item name="Project Summary" href="../project-summary.html" />
+ </menu>
+
+ <menu name="Log4j Project Reports" img="icon-cog">
+ <item name="Changes Report" href="../changes-report.html" />
+ <item name="JIRA Report" href="../jira-report.html" />
+ <item name="Surefire Report" href="../surefire-report.html" />
+ <item name="RAT Report" href="../rat-report.html" />
+ </menu>
+ </body>
+</project>
diff --git a/log4j-spring-cloud-config/log4j-spring-cloud-config-client/src/test/java/org/apache/logging/log4j/spring/cloud/config/client/SpringLookupTest.java b/log4j-spring-cloud-config/log4j-spring-cloud-config-client/src/test/java/org/apache/logging/log4j/spring/cloud/config/client/SpringLookupTest.java
new file mode 100644
index 0000000..9fa8a21
--- /dev/null
+++ b/log4j-spring-cloud-config/log4j-spring-cloud-config-client/src/test/java/org/apache/logging/log4j/spring/cloud/config/client/SpringLookupTest.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.spring.cloud.config.client;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.core.LoggerContext;
+import org.junit.Test;
+import org.springframework.mock.env.MockEnvironment;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+/**
+ * Test SpringLookup.
+ */
+public class SpringLookupTest {
+
+ @Test
+ public void testLookup() {
+ MockEnvironment env = new MockEnvironment();
+ env.setActiveProfiles("test");
+ env.setDefaultProfiles("one", "two");
+ env.setProperty("app.property", "test");
+ LoggerContext context = (LoggerContext) LogManager.getContext(false);
+ context.putObject(Log4j2CloudConfigLoggingSystem.ENVIRONMENT_KEY, env);
+ SpringLookup lookup = new SpringLookup();
+ String result = lookup.lookup("profiles.active");
+ assertNotNull("No active profiles", result);
+ assertEquals("Incorrect active profile", "test", result);
+ result = lookup.lookup("profiles.active[0]");
+ assertNotNull("No active profiles", result);
+ assertEquals("Incorrect active profile", "test", result);
+ result = lookup.lookup("profiles.default");
+ assertNotNull("No default profiles", result);
+ assertEquals("Incorrect default profiles", "one,two", result);
+ result = lookup.lookup("profiles.default[0]");
+ assertNotNull("No default profiles", result);
+ assertEquals("Incorrect default profiles", "one", result);
+ result = lookup.lookup("profiles.default[2]");
+ assertNull("Did not get index out of bounds", result);
+ result = lookup.lookup("app.property");
+ assertNotNull("Did not find property", result);
+ assertEquals("Incorrect property value", "test", result);
+ }
+}
diff --git a/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/Dockerfile b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/Dockerfile
new file mode 100644
index 0000000..d4402e8
--- /dev/null
+++ b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/Dockerfile
@@ -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.
+#
+# Alpine Linux with OpenJDK
+#FROM openjdk:8-jdk-alpine
+FROM openjdk:11-jdk-slim
+
+ARG build_version
+ENV BUILD_VERSION=${build_version}
+RUN mkdir /service
+
+ADD ./target/sampleapp.jar /service/
+WORKDIR /service
+
+#EXPOSE 8080 5005
+EXPOSE 8080
+
+#CMD java "-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=*:5005" -jar sampleapp.jar
+ENTRYPOINT ["sh", "-c", "java $JAVA_OPTS -jar -Xmx1G sampleapp.jar"]
+
diff --git a/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/README.md b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/README.md
new file mode 100644
index 0000000..59311e3
--- /dev/null
+++ b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/README.md
@@ -0,0 +1,55 @@
+#Log4j Spring Cloud Sample Application
+
+This application uses Spring Boot and reads the logging configuration from the companion Spring Cloud Config Server
+project. The log4j2.xml file is located in the config-repo directory in that project.
+
+## Running With Docker
+This sample packages the application in a docker container that is packaged with rabbit-mq (to allow dynamic updates
+from Spring Cloud Config), fluent-bit (to test as a log forwarder), Apache Flume (to test as a log forwarder), and
+Apache Kafka also as a log forwarder. It also installs Socat, a proxy to allow access to the Docker REST API.
+###Prerequisites
+Note: This guide assumes you already have docker installed. If you do not you may either use homebrew to install
+it or follow the instructions at https://docs.docker.com/docker-for-mac/install/.
+
+Like Log4j, the sample app uses the Maven toolchains plugin. The sample app may be built with Java 8 but is
+configured to run in a docker container with Java 11.
+
+The KafkaAppender requires a Kafka instance to write to. On MacOS a Kafka instance can be created by
+```
+brew install kafka
+zookeeper-server-start /usr/local/etc/kafka/zookeeper.properties & kafka-server-start /usr/local/etc/kafka/server.properties
+```
+
+###Starting the Application
+* Start the companion rabbit-mq, fluent-bit and flume images `./docker/up.sh`
+* Compile and start local application image `./docker/restartApp.sh`
+* The application exposes two endpoints.
+ 1. http://localhost:8080/sample/log?threads=1&count=100000 This endpoint will log up to 100,000 events using
+ 1 or more threads.
+ 1. http://localhost:8080/sample/exception This endpoint generates an exception that can be used to verify whether
+ multiline logging works with the chosen set of components.
+
+###Viewing the logs
+
+Accessing the log files varies depending on the appending being used. When logging to the console "docker logs" may
+be used. As configured, Flume will write to files in /var/log/flume, fluent-bit to the standard output of its container.
+Kafka output may be viewed using a tool like [Kafka Tool](http://www.kafkatool.com/).
+
+## Running with Kubernetes
+
+This sample has been verified to run in a Docker Desktop for Mac environment with Kubernetes enabled and may run in
+other Kubernetes environments.
+
+### Prerequisites
+Note: This guide assumes you already have Docker and Kubernetes installed. Since the same container is used for
+Kubernetes as with Docker, Java 11 is also required. This implmentation uses an ELK stack which is expected to
+be installed. They can be downloaded individually and started as local applications on the development
+machine for testing. Logstash should be configured as shown in
+[Logging in the Cloud](http://logging.apache.org/log4j/2.x/manual/cloud.html).
+
+### Starting the Application
+Run the ```docker/deploy.sh``` command from the base directory of the log4j-spring-cloud-config-sample-application
+project. This will build the application and then deploy it to Kubernetes. You should see the start-up logs in Kibana.
+To stop, run the ```docker/undeploy.sh``` script, then run ```docker images``` and perform
+```docker rmi --force {image id}``` where image id is the id of the image for the sample application.
+
diff --git a/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/docker/app-compose.yml b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/docker/app-compose.yml
new file mode 100755
index 0000000..9864e8e
--- /dev/null
+++ b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/docker/app-compose.yml
@@ -0,0 +1,21 @@
+version: "3"
+services:
+ sampleapp:
+ container_name: sampleapp
+ image: sampleapp
+ environment:
+ DOCKER_URI: http://socat:1234
+ SERVICE_PARAMS: --spring.config.location=classpath:/,classpath:/application-local-docker.yml
+ ports:
+ - "5005:5005"
+ - "8080:8080"
+ networks:
+ sample_network:
+ aliases:
+ - sampleapp
+
+networks:
+ sample_network:
+
+volumes:
+ pgdata:
\ No newline at end of file
diff --git a/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/docker/combined-compose.yml b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/docker/combined-compose.yml
new file mode 100755
index 0000000..b2f5abb
--- /dev/null
+++ b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/docker/combined-compose.yml
@@ -0,0 +1,86 @@
+version: "3"
+services:
+ socat:
+ container_name: socat
+ image: bobrik/socat
+ command: TCP-LISTEN:1234,fork UNIX-CONNECT:/var/run/docker.sock
+ expose:
+ - "1234"
+ volumes:
+ - /var/run/docker.sock:/var/run/docker.sock
+ networks:
+ sample_network:
+ aliases:
+ - socat
+
+ rabbitmq:
+ container_name: rabbit
+ image: rabbitmq:3-management-alpine
+ expose:
+ - "5672"
+ - "15672"
+ ports:
+ - "5672:5672"
+ - "15672:15672"
+ volumes:
+ - ./init/rabbit/rabbitmq.config:/etc/rabbitmq/rabbitmq.config:ro
+ - ./init/rabbit/definitions.json:/etc/rabbitmq/definitions.json:ro
+ networks:
+ sample_network:
+ aliases:
+ - rabbitmq
+
+ fluent-bit:
+ container_name: fluent-bit
+ image: fluent/fluent-bit:latest
+ expose:
+ - "2020"
+ - "24221"
+ - "24224"
+ ports:
+ - "24224:24224"
+ volumes:
+ - ./init/fluent-bit/fluent-bit.conf:/fluent-bit/etc/fluent-bit.conf
+ - ./target/logs:/var/logs
+ networks:
+ sample_network:
+ aliases:
+ - fluent-bit
+
+ flume:
+ container_name: flume
+ image: probablyfine/flume:latest
+ expose:
+ - "5050"
+ environment:
+ FLUME_AGENT_NAME: forwarder
+ FLUME_JAVA_OPTS: -Dlog4j.configuration=file:///opt/flume-config/log4j.properties
+ volumes:
+ - ./init/flume/start-flume.sh:/opt/flume/bin/start-flume
+ - ./init/flume/flume.conf:/opt/flume-config/flume.conf
+ - ./init/flume/flume-env.sh:/opt/flume-config/flume-env.sh
+ - ./init/flume/log4j.properties:/opt/flume-config/log4j.properties
+ - ~/flume-logs:/var/log/flume
+ networks:
+ sample_network:
+ aliases:
+ - flume
+
+ sampleapp:
+ container_name: sampleapp
+ image: sampleapp
+ environment:
+ DOCKER_URI: http://socat:1234
+ SERVICE_PARAMS: --spring.config.location=classpath:/,classpath:/application-local-docker.yml
+ ports:
+ - "5005:5005"
+ - "8080:8080"
+ networks:
+ sample_network:
+ aliases:
+ - sampleapp
+networks:
+ sample_network:
+
+volumes:
+ pgdata:
\ No newline at end of file
diff --git a/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/docker/docker-compose.yml b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/docker/docker-compose.yml
new file mode 100755
index 0000000..0d76f52
--- /dev/null
+++ b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/docker/docker-compose.yml
@@ -0,0 +1,74 @@
+version: "3"
+services:
+ socat:
+ container_name: socat
+ image: bobrik/socat
+ command: TCP-LISTEN:1234,fork UNIX-CONNECT:/var/run/docker.sock
+ expose:
+ - "1234"
+ volumes:
+ - /var/run/docker.sock:/var/run/docker.sock
+ networks:
+ sample_network:
+ aliases:
+ - socat
+
+ rabbitmq:
+ container_name: rabbit
+ image: rabbitmq:3-management-alpine
+ expose:
+ - "5672"
+ - "15672"
+ ports:
+ - "5672:5672"
+ - "15672:15672"
+ volumes:
+ - ./init/rabbit/rabbitmq.config:/etc/rabbitmq/rabbitmq.config:ro
+ - ./init/rabbit/definitions.json:/etc/rabbitmq/definitions.json:ro
+ networks:
+ sample_network:
+ aliases:
+ - rabbitmq
+
+ fluent-bit:
+ container_name: fluent-bit
+ image: fluent/fluent-bit:latest
+ expose:
+ - "2020"
+ - "24221"
+ - "24224"
+ ports:
+ - "24224:24224"
+ volumes:
+ - ./init/fluent-bit/fluent-bit.conf:/fluent-bit/etc/fluent-bit.conf
+ - ./target/logs:/var/logs
+ networks:
+ sample_network:
+ aliases:
+ - fluent-bit
+
+ flume:
+ container_name: flume
+ image: probablyfine/flume:latest
+ expose:
+ - "5050"
+ environment:
+ FLUME_AGENT_NAME: forwarder
+ FLUME_JAVA_OPTS: -Dlog4j.configuration=file:///opt/flume-config/log4j.properties
+ volumes:
+ - ./init/flume/start-flume.sh:/opt/flume/bin/start-flume
+ - ./init/flume/flume.conf:/opt/flume-config/flume.conf
+ - ./init/flume/flume-env.sh:/opt/flume-config/flume-env.sh
+ - ./init/flume/log4j.properties:/opt/flume-config/log4j.properties
+ - ~/flume-logs:/var/log/flume
+ networks:
+ sample_network:
+ aliases:
+ - flume
+
+
+networks:
+ sample_network:
+
+volumes:
+ pgdata:
\ No newline at end of file
diff --git a/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/docker/down.sh b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/docker/down.sh
new file mode 100755
index 0000000..79ff27e
--- /dev/null
+++ b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/docker/down.sh
@@ -0,0 +1,5 @@
+#!/usr/bin/env bash
+echo Stopping containers and removing containers, networks, volumes, and images created by up.
+DIR=`dirname "$0"`
+docker-compose --file ${DIR}/docker-compose.yml down --rmi local -v
+docker-compose --file ${DIR}/docker-compose.yml rm --force -v
diff --git a/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/docker/init/fluent-bit/fluent-bit.conf b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/docker/init/fluent-bit/fluent-bit.conf
new file mode 100644
index 0000000..170c7bd
--- /dev/null
+++ b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/docker/init/fluent-bit/fluent-bit.conf
@@ -0,0 +1,28 @@
+[SERVICE]
+ Log_Level debug
+
+ HTTP_Server On
+ HTTP_Listen 0.0.0.0
+ HTTP_Port 2020
+ Parsers_File parsers.conf
+
+[INPUT]
+ Name tcp
+ Listen 0.0.0.0
+ Port 24221
+ Chunk_Size 32
+ Buffer_Size 64
+
+[INPUT]
+ Name tail
+ Tag kube.*
+ Path /var/log/containers/*.log
+ Parser docker
+ DB /var/log/flb_kube.db
+ Mem_Buf_Limit 5MB
+ Skip_Long_Lines On
+ Refresh_Interval 10
+
+[OUTPUT]
+ Name stdout
+ Match *
\ No newline at end of file
diff --git a/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/docker/init/flume/flume-env.sh b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/docker/init/flume/flume-env.sh
new file mode 100644
index 0000000..d56d932
--- /dev/null
+++ b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/docker/init/flume/flume-env.sh
@@ -0,0 +1 @@
+JAVA_OPTS="-Xmx200m"
\ No newline at end of file
diff --git a/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/docker/init/flume/flume.conf b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/docker/init/flume/flume.conf
new file mode 100644
index 0000000..d625a76
--- /dev/null
+++ b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/docker/init/flume/flume.conf
@@ -0,0 +1,32 @@
+# Accepts incoming requests and forwards them
+
+# Name the components on this agent
+forwarder.sources = avro
+forwarder.sinks = sink1
+forwarder.channels = channel1
+
+# Describe/configure the source
+forwarder.sources.avro.type = avro
+forwarder.sources.avro.bind = 0.0.0.0
+forwarder.sources.avro.port = 5050
+forwarder.sources.avro.compression-type = none
+
+
+# Describe the sink
+forwarder.sinks.sink1.type = logger
+
+forwarder.sinks.sink1.type = file_roll
+forwarder.sinks.sink1.sink.directory = /var/log/flume/
+forwarder.sinks.sink1.sink.pathManager.extension=log
+forwarder.sinks.sink1.channel = channel1
+forwarder.sinks.sink1.sink.rollInterval = 300
+forwarder.sinks.sink1.sink.serializer = text
+
+# Use a channel which buffers events in memory
+forwarder.channels.channel1.type = memory
+forwarder.channels.channel1.capacity = 100000
+forwarder.channels.channel1.transactionCapacity = 10000
+
+# Bind the source and sink to the channel
+forwarder.sources.avro.channels = channel1
+forwarder.sinks.sink1.channel = channel1
\ No newline at end of file
diff --git a/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/docker/init/flume/log4j.properties b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/docker/init/flume/log4j.properties
new file mode 100644
index 0000000..05928f6
--- /dev/null
+++ b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/docker/init/flume/log4j.properties
@@ -0,0 +1,10 @@
+
+# Set root logger level to DEBUG and its only appender to A1.
+log4j.rootLogger=INFO, A1
+
+# A1 is set to be a ConsoleAppender.
+log4j.appender.A1=org.apache.log4j.ConsoleAppender
+
+# A1 uses PatternLayout.
+log4j.appender.A1.layout=org.apache.log4j.PatternLayout
+log4j.appender.A1.layout.ConversionPattern=%-4r [%t] %-5p %c %x - %m%n
\ No newline at end of file
diff --git a/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/docker/init/flume/start-flume.sh b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/docker/init/flume/start-flume.sh
new file mode 100755
index 0000000..071d62c
--- /dev/null
+++ b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/docker/init/flume/start-flume.sh
@@ -0,0 +1,14 @@
+#!/bin/bash
+
+FLUME_CONF_DIR=/opt/flume-config
+
+[[ -d "${FLUME_CONF_DIR}" ]] || { echo "Flume config file not mounted in /opt/flume-config"; exit 1; }
+[[ -z "${FLUME_AGENT_NAME}" ]] && { echo "FLUME_AGENT_NAME required"; exit 1; }
+
+echo "Starting flume agent : ${FLUME_AGENT_NAME}"
+
+flume-ng agent \
+ -c ${FLUME_CONF_DIR} \
+ -f ${FLUME_CONF_DIR}/flume.conf \
+ -n ${FLUME_AGENT_NAME} \
+ $*
\ No newline at end of file
diff --git a/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/docker/init/rabbit/definitions.json b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/docker/init/rabbit/definitions.json
new file mode 100644
index 0000000..835f10e
--- /dev/null
+++ b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/docker/init/rabbit/definitions.json
@@ -0,0 +1,36 @@
+{
+ "rabbit_version": "3.6.14",
+ "users": [
+ {
+ "name": "guest",
+ "password_hash": "8NWJazmxFqF0nohjVsE6rRP9wvwzEq/fpUel2in5Y3YAE3KZ",
+ "hashing_algorithm": "rabbit_password_hashing_sha256",
+ "tags": "administrator"
+ }
+ ],
+ "vhosts": [
+ {
+ "name": "/"
+ }
+ ],
+ "permissions": [
+ {
+ "user": "guest",
+ "vhost": "/",
+ "configure": ".*",
+ "write": ".*",
+ "read": ".*"
+ }
+ ],
+ "parameters": [],
+ "global_parameters": [
+ {
+ "name": "cluster_name",
+ "value": "rabbit@c85301775966"
+ }
+ ],
+ "policies": [],
+ "queues": [],
+ "exchanges": [],
+ "bindings": []
+}
\ No newline at end of file
diff --git a/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/docker/init/rabbit/rabbitmq.config b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/docker/init/rabbit/rabbitmq.config
new file mode 100644
index 0000000..b55f508
--- /dev/null
+++ b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/docker/init/rabbit/rabbitmq.config
@@ -0,0 +1,14 @@
+[
+ {
+ rabbit,
+ [
+ { loopback_users, [] }
+ ]
+ },
+ {
+ rabbitmq_management,
+ [
+ { load_definitions, "/etc/rabbitmq/definitions.json" }
+ ]
+ }
+].
\ No newline at end of file
diff --git a/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/docker/logs.sh b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/docker/logs.sh
new file mode 100755
index 0000000..8cf865d
--- /dev/null
+++ b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/docker/logs.sh
@@ -0,0 +1,7 @@
+#!/usr/bin/env bash
+
+DIR=`dirname "$0"`
+# dump existing logs and start tailing them
+# https://docs.docker.com/compose/reference/logs
+docker-compose --file ${DIR}/../docker/docker-compose.yml logs --follow --tail=all
+
diff --git a/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/docker/restartApp.sh b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/docker/restartApp.sh
new file mode 100755
index 0000000..af20bf4
--- /dev/null
+++ b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/docker/restartApp.sh
@@ -0,0 +1,20 @@
+#!/usr/bin/env bash
+imageName=sampleapp
+containerName=app-container
+networkName=docker_sample_network
+debug_port=5005
+#debug_expose="-p $debug_port:$debug_port"
+exposed_ports="-p 8080:8080 $debug_expose"
+
+mvn clean package -DskipTests=true
+
+docker build -t $imageName -f Dockerfile .
+
+echo Delete old container...
+docker rm -f $containerName
+
+echo Run new container...
+docker run -e "SERVICE_PARAMS=--spring.config.location=classpath:/,classpath:/application-local-docker.yml" \
+ -e "DOCKER_URI=http://socat:1234" -e "JAVA_OPTS=-Dlogstash.search.host=host.docker.internal" \
+ --network=$networkName -d $exposed_ports --name $containerName -h sample $imageName
+# --log-driver=fluentd --log-opt fluentd-address=host.docker.internal:24224 \
\ No newline at end of file
diff --git a/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/docker/stop.sh b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/docker/stop.sh
new file mode 100755
index 0000000..b7f83c6
--- /dev/null
+++ b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/docker/stop.sh
@@ -0,0 +1,6 @@
+#!/usr/bin/env bash
+
+echo Stopping running containers without removing them.
+
+DIR=`dirname "$0"`
+docker-compose --file ${DIR}/../docker/docker-compose.yml stop
\ No newline at end of file
diff --git a/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/docker/stopApp.sh b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/docker/stopApp.sh
new file mode 100755
index 0000000..471d6e1
--- /dev/null
+++ b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/docker/stopApp.sh
@@ -0,0 +1,3 @@
+#!/usr/bin/env bash
+containerName=app-container
+docker rm -f $containerName
\ No newline at end of file
diff --git a/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/docker/up.sh b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/docker/up.sh
new file mode 100755
index 0000000..6f6602b
--- /dev/null
+++ b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/docker/up.sh
@@ -0,0 +1,6 @@
+#!/usr/bin/env bash
+
+echo "Building, (re)creating, starting, and attaching to containers for a service."
+
+DIR=`dirname "$0"`
+docker-compose --file ${DIR}/docker-compose.yml up --detach --build
\ No newline at end of file
diff --git a/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/k8s/deploy.sh b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/k8s/deploy.sh
new file mode 100755
index 0000000..9f67de2
--- /dev/null
+++ b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/k8s/deploy.sh
@@ -0,0 +1,36 @@
+#!/usr/bin/env 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.
+#
+
+echo "Building, (re)creating, starting, and attaching to containers for a service."
+
+imageName=sampleapp
+containerName=sampleapp-container
+networkName=docker_sampleapp
+debug_port=5005
+#debug_expose="-p $debug_port:$debug_port"
+exposed_ports="-p 8080:8090 $debug_expose"
+
+mvn clean package -DskipTests=true
+docker build --no-cache -t $imageName -f Dockerfile .
+
+docker tag $imageName localhost:5000/$imageName
+docker push localhost:5000/$imageName
+kubectl apply -f k8s/sampleapp-deployment.yaml
\ No newline at end of file
diff --git a/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/k8s/sampleapp-deployment.yaml b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/k8s/sampleapp-deployment.yaml
new file mode 100644
index 0000000..ffd2851
--- /dev/null
+++ b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/k8s/sampleapp-deployment.yaml
@@ -0,0 +1,53 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+ name: sampleapp
+ labels:
+ app: sampleapp
+spec:
+ replicas: 1
+ selector:
+ matchLabels:
+ app: sampleapp
+ template:
+ metadata:
+ labels:
+ app: sampleapp
+ spec:
+ containers:
+ - name: sampleapp
+ image: localhost:5000/sampleapp:latest
+ imagePullPolicy: Always
+ ports:
+ - containerPort: 8080
+ - containerPort: 5005
+ env:
+ - name: JAVA_OPTS
+ value: "-Delastic.search.host=host.docker.internal"
+ - name: key-value-store
+ image: redis
+ ports:
+ - containerPort: 6379
+
+
+---
+apiVersion: v1
+kind: Service
+metadata:
+ name: sampleapp
+spec:
+ selector:
+ app: sampleapp
+ ports:
+ - protocol: TCP
+ port: 8080
+ targetPort: 8080
+ name: http
+ - protocol: TCP
+ port: 6379
+ targetPort: 6379
+ name: redis
+ - protocol: TCP
+ port: 5005
+ targetPort: 5005
+ name: debug
diff --git a/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/k8s/undeploy.sh b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/k8s/undeploy.sh
new file mode 100755
index 0000000..c5255c9
--- /dev/null
+++ b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/k8s/undeploy.sh
@@ -0,0 +1,29 @@
+#!/usr/bin/env 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.
+#
+
+echo "Building, (re)creating, starting, and attaching to containers for a service."
+
+imageName=sampleapp
+containerName=sampleapp-container
+networkName=docker_sampleapp
+
+kubectl delete deploy/$imageName svc/$imageName
+
diff --git a/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/pom.xml b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/pom.xml
new file mode 100644
index 0000000..95a02fc
--- /dev/null
+++ b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/pom.xml
@@ -0,0 +1,287 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.apache.logging.log4j.samples</groupId>
+ <artifactId>log4j-spring-cloud-config-samples</artifactId>
+ <version>3.0.0-SNAPSHOT</version>
+ <relativePath>..</relativePath>
+ </parent>
+
+ <artifactId>sample-app</artifactId>
+ <packaging>jar</packaging>
+
+ <name>Spring Cloud Config Sample Application</name>
+ <url>http://maven.apache.org</url>
+ <properties>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ <maven.compiler.source>1.8</maven.compiler.source>
+ <maven.compiler.target>1.8</maven.compiler.target>
+ <!--<manifestfile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestfile>-->
+ </properties>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-jcl</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <!-- Spring Boot dependencies -->
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-web</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-actuator</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.fasterxml.jackson.datatype</groupId>
+ <artifactId>jackson-datatype-jsr310</artifactId>
+ <version>${jackson2Version}</version>
+ </dependency>
+ <!-- Spring Cloud dependencies -->
+ <dependency>
+ <groupId>org.springframework.cloud</groupId>
+ <artifactId>spring-cloud-starter-bus-amqp</artifactId>
+ </dependency>
+
+ <!-- Spring Tests -->
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-test</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-test</artifactId>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-tomcat</artifactId>
+ </dependency>
+
+ <!-- log dependencies -->
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-log4j2</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-spring-cloud-config-client</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter</artifactId>
+ <exclusions>
+ <exclusion>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-logging</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-docker</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-layout-json-template</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-kubernetes</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <!-- Required for Async Loggers -->
+ <dependency>
+ <groupId>com.lmax</groupId>
+ <artifactId>disruptor</artifactId>
+ <optional>true</optional>
+ </dependency>
+ <!-- Required for Flume -->
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-flume-ng</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.flume</groupId>
+ <artifactId>flume-ng-sdk</artifactId>
+ <version>1.9.0</version>
+ </dependency>
+ <!-- Required for Flume embedded -->
+ <dependency>
+ <groupId>org.apache.flume</groupId>
+ <artifactId>flume-ng-embedded-agent</artifactId>
+ <version>1.9.0</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.kafka</groupId>
+ <artifactId>kafka-clients</artifactId>
+ <version>2.2.0</version>
+ </dependency>
+ <dependency>
+ <groupId>javax.servlet</groupId>
+ <artifactId>javax.servlet-api</artifactId>
+ <version>3.1.0</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-api</artifactId>
+ <classifier>tests</classifier>
+ <version>${project.version}</version>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <finalName>sampleapp</finalName>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-site-plugin</artifactId>
+ <version>${site.plugin.version}</version>
+ <configuration>
+ <skip>true</skip>
+ <skipDeploy>true</skipDeploy>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-deploy-plugin</artifactId>
+ <version>${deploy.plugin.version}</version>
+ <configuration>
+ <skip>true</skip>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-toolchains-plugin</artifactId>
+ <version>1.1</version>
+ <executions>
+ <execution>
+ <goals>
+ <goal>toolchain</goal>
+ </goals>
+ </execution>
+ </executions>
+ <configuration>
+ <toolchains>
+ <jdk>
+ <version>1.8</version>
+ </jdk>
+ </toolchains>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>default-compile</id>
+ <phase>compile</phase>
+ <goals>
+ <goal>compile</goal>
+ </goals>
+ </execution>
+ <execution>
+ <id>default-test-compile</id>
+ <phase>test-compile</phase>
+ <goals>
+ <goal>testCompile</goal>
+ </goals>
+ </execution>
+ </executions>
+ <configuration>
+ <source>1.8</source>
+ <target>1.8</target>
+ <proc>none</proc>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <version>2.18.1</version>
+ <executions>
+ <execution>
+ <id>default-test</id>
+ <goals>
+ <goal>test</goal>
+ </goals>
+ </execution>
+ </executions>
+ <configuration>
+ <includes>
+ <include>**/Test*.java</include>
+ <include>**/*Test.java</include>
+ <include>**/IT*.java</include>
+ <include>**/*IT.java</include>
+ </includes>
+ <excludes>
+ <exclude>**/*FuncTest.java</exclude>
+ </excludes>
+ <forkCount>1</forkCount>
+ <systemPropertyVariables>
+ <environment>${environment}</environment>
+ <site>${site}</site>
+ </systemPropertyVariables>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-jar-plugin</artifactId>
+ <version>3.1.0</version>
+ <executions>
+ <execution>
+ <id>default-jar</id>
+ <goals>
+ <goal>jar</goal>
+ </goals>
+ <configuration>
+ <archive>
+ <manifestFile>${manifestfile}</manifestFile>
+ <manifestEntries>
+ <Specification-Title>${project.name}</Specification-Title>
+ <Specification-Version>${project.version}</Specification-Version>
+ <Specification-Vendor>${project.organization.name}</Specification-Vendor>
+ <Implementation-Title>${project.name}</Implementation-Title>
+ <Implementation-Version>${project.version}</Implementation-Version>
+ <Implementation-Vendor>${project.organization.name}</Implementation-Vendor>
+ <Implementation-Vendor-Id>org.apache</Implementation-Vendor-Id>
+ <X-Compile-Source-JDK>${maven.compiler.source}</X-Compile-Source-JDK>
+ <X-Compile-Target-JDK>${maven.compiler.target}</X-Compile-Target-JDK>
+ <Multi-Release>true</Multi-Release>
+ </manifestEntries>
+ </archive>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+
+ <plugin>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-maven-plugin</artifactId>
+ <version>${spring-boot.version}</version>
+ <executions>
+ <execution>
+ <goals>
+ <goal>repackage</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+ <profiles>
+
+ </profiles>
+</project>
diff --git a/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/src/main/java/org/apache/logging/log4j/spring/cloud/config/sample/SampleApplication.java b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/src/main/java/org/apache/logging/log4j/spring/cloud/config/sample/SampleApplication.java
new file mode 100644
index 0000000..a1ac420
--- /dev/null
+++ b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/src/main/java/org/apache/logging/log4j/spring/cloud/config/sample/SampleApplication.java
@@ -0,0 +1,17 @@
+/*
+ * Copyright (c) 2019 Nextiva, Inc. to Present.
+ * All rights reserved.
+ */
+package org.apache.logging.log4j.spring.cloud.config.sample;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
+
+@SpringBootApplication
+public class SampleApplication extends SpringBootServletInitializer {
+ public static void main(String[] args) {
+ SpringApplication.run(SampleApplication.class, args);
+ }
+
+}
diff --git a/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/src/main/java/org/apache/logging/log4j/spring/cloud/config/sample/config/GlobalExceptionHandler.java b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/src/main/java/org/apache/logging/log4j/spring/cloud/config/sample/config/GlobalExceptionHandler.java
new file mode 100644
index 0000000..8efa0a8
--- /dev/null
+++ b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/src/main/java/org/apache/logging/log4j/spring/cloud/config/sample/config/GlobalExceptionHandler.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.spring.cloud.config.sample.config;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
+
+
+@ControllerAdvice
+public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
+
+ private static final Logger LOGGER = LogManager.getLogger(GlobalExceptionHandler.class);
+
+
+ private ResponseEntity<Object> getResponseEntity(Object responseMessage, HttpStatus httpStatus) {
+ return new ResponseEntity<>(responseMessage, httpStatus);
+ }
+}
diff --git a/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/src/main/java/org/apache/logging/log4j/spring/cloud/config/sample/config/WebMvcConfig.java b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/src/main/java/org/apache/logging/log4j/spring/cloud/config/sample/config/WebMvcConfig.java
new file mode 100644
index 0000000..1576f88
--- /dev/null
+++ b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/src/main/java/org/apache/logging/log4j/spring/cloud/config/sample/config/WebMvcConfig.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.spring.cloud.config.sample.config;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.spring.cloud.config.sample.utils.spring.SampleResponseErrorHandler;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.client.RestTemplate;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
+
+@Configuration
+public class WebMvcConfig extends WebMvcConfigurerAdapter {
+ private Logger LOGGER = LogManager.getLogger(WebMvcConfig.class);
+
+ @Bean
+ public RestTemplate restTemplate() {
+ RestTemplate restTemplate = new RestTemplate();
+ restTemplate.setErrorHandler(new SampleResponseErrorHandler());
+ return restTemplate;
+ }
+
+
+}
diff --git a/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/src/main/java/org/apache/logging/log4j/spring/cloud/config/sample/controller/K8SController.java b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/src/main/java/org/apache/logging/log4j/spring/cloud/config/sample/controller/K8SController.java
new file mode 100644
index 0000000..5059a49
--- /dev/null
+++ b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/src/main/java/org/apache/logging/log4j/spring/cloud/config/sample/controller/K8SController.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2020 Nextiva, Inc. to Present.
+ * All rights reserved.
+ */
+
+package org.apache.logging.log4j.spring.cloud.config.sample.controller;
+
+import java.nio.file.Paths;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.kubernetes.KubernetesClientBuilder;
+import org.apache.logging.log4j.util.Strings;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import io.fabric8.kubernetes.api.model.Pod;
+import io.fabric8.kubernetes.client.Config;
+import io.fabric8.kubernetes.client.KubernetesClient;
+
+/**
+ * Test class
+ */
+@RestController
+public class K8SController {
+
+ private static final Logger LOGGER = LogManager.getLogger(K8SController.class);
+ private static final String HOSTNAME = "HOSTNAME";
+ @Autowired
+ private ObjectMapper objectMapper;
+
+ @GetMapping("/k8s/pod")
+ public ResponseEntity<Pod> getPod() {
+ try {
+ KubernetesClient client = new KubernetesClientBuilder().createClient();
+ if (client != null) {
+ Pod pod = getCurrentPod(client);
+ if (pod != null) {
+ LOGGER.info("Pod: {}", objectMapper.writeValueAsString(pod));
+ return new ResponseEntity<>(pod, HttpStatus.OK);
+ }
+ }
+ } catch (Exception ex) {
+ LOGGER.error("Unable to obtain or print Pod information", ex);
+ }
+ return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
+ }
+
+ private Pod getCurrentPod(KubernetesClient kubernetesClient) {
+ String hostName = System.getenv(HOSTNAME);
+ try {
+ if (isServiceAccount() && Strings.isNotBlank(hostName)) {
+ return kubernetesClient.pods().withName(hostName).get();
+ }
+ } catch (Throwable t) {
+ LOGGER.debug("Unable to locate pod with name {}.", hostName);
+ }
+ return null;
+ }
+
+ private boolean isServiceAccount() {
+ return Paths.get(Config.KUBERNETES_SERVICE_ACCOUNT_TOKEN_PATH).toFile().exists()
+ && Paths.get(Config.KUBERNETES_SERVICE_ACCOUNT_CA_CRT_PATH).toFile().exists();
+ }
+
+}
diff --git a/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/src/main/java/org/apache/logging/log4j/spring/cloud/config/sample/controller/SampleController.java b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/src/main/java/org/apache/logging/log4j/spring/cloud/config/sample/controller/SampleController.java
new file mode 100644
index 0000000..7657503
--- /dev/null
+++ b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/src/main/java/org/apache/logging/log4j/spring/cloud/config/sample/controller/SampleController.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.spring.cloud.config.sample.controller;
+
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.ThreadContext;
+import org.apache.logging.log4j.core.util.UuidUtil;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+import org.apache.logging.log4j.util.Timer;
+
+
+
+@RestController
+public class SampleController {
+
+ private static final Logger LOGGER = LogManager.getLogger(SampleController.class);
+ private static int MAX_MESSAGES = 100000;
+
+ @GetMapping("/log")
+ public ResponseEntity<String> get(@RequestParam(name="threads", defaultValue="1") int threads,
+ @RequestParam(name="messages", defaultValue="100000") int count) {
+ if (threads < 1) {
+ threads = 1;
+ }
+ if (count < threads) {
+ count = threads * 10000;
+ }
+ if ((count * threads) > MAX_MESSAGES) {
+ count = MAX_MESSAGES / threads;
+ }
+ String msg = "";
+ if (threads == 1) {
+ ThreadContext.put("requestId", UuidUtil.getTimeBasedUuid().toString());
+ Timer timer = new Timer("sample");
+ timer.start();
+ for (int n = 0; n < count; ++n) {
+ LOGGER.info("Log record " + n);
+ }
+ timer.stop();
+ StringBuilder sb = new StringBuilder("Elapsed time = ");
+ timer.formatTo(sb);
+ msg = sb.toString();
+ ThreadContext.clearMap();
+ } else {
+ ExecutorService service = Executors.newFixedThreadPool(threads);
+ Timer timer = new Timer("sample");
+ timer.start();
+ for (int i = 0; i < threads; ++i) {
+ service.submit(new Worker(i, count));
+ }
+ service.shutdown();
+ try {
+ service.awaitTermination(2, TimeUnit.MINUTES);
+ timer.stop();
+ StringBuilder sb = new StringBuilder("Elapsed time = ");
+ timer.formatTo(sb);
+ msg = sb.toString();
+ } catch (InterruptedException ex) {
+ msg = "Max time exceeded";
+ }
+ }
+
+ return ResponseEntity.ok(msg);
+ }
+
+ @GetMapping("/exception")
+ public ResponseEntity<String> exception() {
+ ThreadContext.put("requestId", UuidUtil.getTimeBasedUuid().toString());
+ Throwable t = new Throwable("This is a test");
+ LOGGER.info("This is a test", t);
+ ByteArrayOutputStream os = new ByteArrayOutputStream();
+ PrintStream ps = new PrintStream(os);
+ t.printStackTrace(ps);
+ String stackTrace = os.toString();
+ stackTrace = stackTrace.replaceAll("\n", "<br>");
+ ThreadContext.clearMap();
+
+ //LOGGER.info("Hello, World");
+ return ResponseEntity.ok(stackTrace);
+ }
+
+ private static class Worker implements Runnable {
+
+ private final int id;
+ private final int count;
+
+ public Worker(int id, int count) {
+ this.id = id;
+ this.count = count;
+ }
+
+ public void run() {
+ String prefix = "Thread " + id + " record ";
+ ThreadContext.put("requestId", UuidUtil.getTimeBasedUuid().toString());
+ for (int i = 0; i < count; ++i) {
+ LOGGER.info(prefix + i);
+ }
+ ThreadContext.clearMap();
+ }
+ }
+}
diff --git a/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/src/main/java/org/apache/logging/log4j/spring/cloud/config/sample/utils/spring/SampleResponseErrorHandler.java b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/src/main/java/org/apache/logging/log4j/spring/cloud/config/sample/utils/spring/SampleResponseErrorHandler.java
new file mode 100644
index 0000000..77b4d1f
--- /dev/null
+++ b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/src/main/java/org/apache/logging/log4j/spring/cloud/config/sample/utils/spring/SampleResponseErrorHandler.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.spring.cloud.config.sample.utils.spring;
+
+import java.io.IOException;
+import org.springframework.http.client.ClientHttpResponse;
+import org.springframework.web.client.ResponseErrorHandler;
+
+/**
+ * Custom http client error handler which doesn't throw exception in case http code is not 2xx.
+ */
+public class SampleResponseErrorHandler implements org.springframework.web.client.ResponseErrorHandler {
+ @Override
+ public boolean hasError(ClientHttpResponse clientHttpResponse) throws IOException {
+ return false;
+ }
+
+ @Override
+ public void handleError(ClientHttpResponse clientHttpResponse) throws IOException {
+
+ }
+}
diff --git a/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/src/main/resources/EnhancedGelf.json b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/src/main/resources/EnhancedGelf.json
new file mode 100644
index 0000000..994b0c7
--- /dev/null
+++ b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/src/main/resources/EnhancedGelf.json
@@ -0,0 +1,41 @@
+{
+ "version": "1.1",
+ "host": "${hostName}",
+ "short_message": {
+ "$resolver": "message",
+ "stringified": true
+ },
+ "full_message": {
+ "$resolver": "message",
+ "pattern": "[%t] %-5p %X{requestId, sessionId, loginId, userId, ipAddress, corpAcctNumber} %C{1.}.%M:%L - %m",
+ "stringified": true
+ },
+ "timestamp": {
+ "$resolver": "timestamp",
+ "epoch": {
+ "unit": "secs"
+ }
+ },
+ "level": {
+ "$resolver": "level",
+ "field": "severity",
+ "severity": {
+ "field": "code"
+ }
+ },
+ "_logger": {
+ "$resolver": "logger",
+ "field": "name"
+ },
+ "_thread": {
+ "$resolver": "thread",
+ "field": "name"
+ },
+ "_mdc": {
+ "$resolver": "mdc",
+ "flatten": {
+ "prefix": "_"
+ },
+ "stringified": true
+ }
+}
diff --git a/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/src/main/resources/META-INF/MANIFEST.MF b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/src/main/resources/META-INF/MANIFEST.MF
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/src/main/resources/META-INF/MANIFEST.MF
diff --git a/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/src/main/resources/application.yml b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/src/main/resources/application.yml
new file mode 100644
index 0000000..4c6bb12
--- /dev/null
+++ b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/src/main/resources/application.yml
@@ -0,0 +1,33 @@
+server:
+ port: 8080
+ servlet:
+ context-path: /sample
+
+security:
+ basic:
+ enabled: false
+
+management:
+ security:
+ enabled: false
+
+spring:
+ application:
+ name: sampleapp
+
+ cloud:
+ bus:
+ trace:
+ enabled: true
+ config:
+ watch:
+ enabled: true
+
+ rabbitmq:
+ addresses: rabbit
+ port: 5672
+ username: guest
+ password: guest
+
+log4j2:
+ debug: true
\ No newline at end of file
diff --git a/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/src/main/resources/bootstrap.yml b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/src/main/resources/bootstrap.yml
new file mode 100644
index 0000000..102e941
--- /dev/null
+++ b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/src/main/resources/bootstrap.yml
@@ -0,0 +1,5 @@
+#Spring Cloud (Hystrix) config goes here.
+hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds: 30000
+logging:
+ config: http://host.docker.internal:8888/ConfigService/samppleapp/default/master/log4j2.xml
+
diff --git a/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/src/main/resources/log4j2.component.properties b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/src/main/resources/log4j2.component.properties
new file mode 100644
index 0000000..33e4766
--- /dev/null
+++ b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-application/src/main/resources/log4j2.component.properties
@@ -0,0 +1,3 @@
+log4j.configurationFile=http://host.docker.internal:8888/ConfigService/sampleapp/default/master/log4j2.xml
+log4j2.configurationUserName=guest
+log4j2.configurationPassword=guest
\ No newline at end of file
diff --git a/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-server/pom.xml b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-server/pom.xml
new file mode 100644
index 0000000..9a69e3b
--- /dev/null
+++ b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-server/pom.xml
@@ -0,0 +1,227 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <groupId>org.apache.logging.log4j.samples</groupId>
+ <artifactId>log4j-spring-cloud-config-sample-server</artifactId>
+ <version>3.0.0-SNAPSHOT</version>
+ <packaging>jar</packaging>
+
+ <name>Log4j Sample Configuration Service</name>
+ <description>Sample Cloud Config Server</description>
+
+ <parent>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-parent</artifactId>
+ <version>2.1.2.RELEASE</version>
+ <relativePath/> <!-- lookup parent from repository -->
+ </parent>
+
+ <properties>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+ <java.version>1.8</java.version>
+ <spring-cloud.version>Greenwich.SR1</spring-cloud.version>
+
+
+ <!-- paths -->
+ <sonar.dependencyCheck.reportPath>${project.build.directory}/dependency-check-report.xml
+ </sonar.dependencyCheck.reportPath>
+
+ <!-- maven plugin versions -->
+ <deploy.plugin.version>2.8.2</deploy.plugin.version>
+ <maven.checkstyle.version>3.0.0</maven.checkstyle.version>
+ <maven.findbugs.version>3.0.5</maven.findbugs.version>
+ <maven.google.code.findbugs.version>3.0.2</maven.google.code.findbugs.version>
+ <maven.google.code.findbugs.findbugs.version>3.0.1</maven.google.code.findbugs.findbugs.version>
+ <maven.jacoco.version>0.8.1</maven.jacoco.version>
+ <maven.pmd.version>3.9.0</maven.pmd.version>
+ <site.plugin.version>3.4</site.plugin.version>
+ <!-- maven plugin config -->
+ <pmd.failurePriority>2</pmd.failurePriority>
+
+ </properties>
+
+ <dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>org.springframework.cloud</groupId>
+ <artifactId>spring-cloud-dependencies</artifactId>
+ <version>${spring-cloud.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
+
+ <dependencies>
+ <!-- log dependencies -->
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-log4j2</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.cloud</groupId>
+ <artifactId>spring-cloud-starter-config</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.cloud</groupId>
+ <artifactId>spring-cloud-config-server</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.cloud</groupId>
+ <artifactId>spring-cloud-config-monitor</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-actuator</artifactId>
+ <exclusions>
+ <exclusion>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-logging</artifactId>
+ </exclusion>
+ </exclusions>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.cloud</groupId>
+ <artifactId>spring-cloud-starter-bus-amqp</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-web</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-security</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-api</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-core</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-slf4j-impl</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-test</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-site-plugin</artifactId>
+ <version>${site.plugin.version}</version>
+ <configuration>
+ <skip>true</skip>
+ <skipDeploy>true</skipDeploy>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-deploy-plugin</artifactId>
+ <version>${deploy.plugin.version}</version>
+ <configuration>
+ <skip>true</skip>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-maven-plugin</artifactId>
+ </plugin>
+ <plugin>
+ <artifactId>maven-resources-plugin</artifactId>
+ <version>3.1.0</version>
+ <executions>
+ <execution>
+ <id>copy-resources</id>
+ <!-- here the phase you need -->
+ <phase>validate</phase>
+ <goals>
+ <goal>copy-resources</goal>
+ </goals>
+ <configuration>
+ <outputDirectory>${basedir}/target/config-repo</outputDirectory>
+ <resources>
+ <resource>
+ <directory>src/main/config-repo</directory>
+ <filtering>true</filtering>
+ </resource>
+ </resources>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+
+ <plugin>
+ <groupId>org.jacoco</groupId>
+ <artifactId>jacoco-maven-plugin</artifactId>
+ <version>${maven.jacoco.version}</version>
+ <configuration>
+ <excludes>
+ <exclude>com/oracle/brm/**/*.class</exclude>
+ <exclude>org/w3/**/*.class</exclude>
+ <exclude>org/xmlsoap/**/*.class</exclude>
+ <exclude>oracle/security/pki/*.class</exclude>
+ </excludes>
+ </configuration>
+ <executions>
+ <execution>
+ <goals>
+ <goal>prepare-agent</goal>
+ </goals>
+ </execution>
+ <execution>
+ <id>report</id>
+ <phase>prepare-package</phase>
+ <goals>
+ <goal>report</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+
+ <distributionManagement>
+ <downloadUrl>https://logging.apache.org/log4j/2.x/download.html</downloadUrl>
+ <!-- site is only included to make maven-site-plugin stop complaining -->
+ <site>
+ <id>www.example.com</id>
+ <url>scp://www.example.com/www/docs/project/</url>
+ </site>
+ </distributionManagement>
+
+ <repositories>
+ <repository>
+ <id>spring-snapshots</id>
+ <name>Spring Snapshots</name>
+ <url>https://repo.spring.io/snapshot</url>
+ <snapshots>
+ <enabled>true</enabled>
+ </snapshots>
+ </repository>
+ <repository>
+ <id>spring-milestones</id>
+ <name>Spring Milestones</name>
+ <url>https://repo.spring.io/libs-milestone</url>
+ <snapshots>
+ <enabled>false</enabled>
+ </snapshots>
+ </repository>
+ </repositories>
+
+
+</project>
diff --git a/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-server/readme.txt b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-server/readme.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-server/readme.txt
diff --git a/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-server/src/main/config-repo/log4j2.xml b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-server/src/main/config-repo/log4j2.xml
new file mode 100644
index 0000000..a2afa70
--- /dev/null
+++ b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-server/src/main/config-repo/log4j2.xml
@@ -0,0 +1,178 @@
+<?xml version="1.0" encoding="UTF-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 "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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="DEBUG" monitorInterval="0">
+ <Properties>
+ <Property name="spring.application.name">sampleapp</Property>
+ </Properties>
+ <Appenders>
+ <!--<Flume name="flume" ignoreExceptions="false" type="Embedded" compress="false">
+ <Property name="channel.type">memory</Property>
+ <Property name="channel.capacity">100000</Property>
+ <Property name="channel.transactionCapacity">5000</Property>
+ <Property name="sinks">agent1</Property>
+ <Property name="agent1.type">avro</Property>
+ <Property name="agent1.hostname">flume</Property>
+ <Property name="agent1.port">5050</Property>
+ <Property name="agent1.batch-size">1000</Property>
+ <Property name="processor.type">default</Property>
+ <JsonLayout properties="true" compact="true" eventEol="true">
+ <KeyValuePair key="containerId" value="\${docker:containerId}"/>
+ <KeyValuePair key="containerName" value="\${docker:containerName}"/>
+ <KeyValuePair key="imageName" value="\${docker:imageName}"/>
+ <KeyValuePair key="application" value="\$\${lower:\${spring:spring.application.name}}"/>
+ </JsonLayout>
+ </Flume>
+ <Flume name="avro" ignoreExceptions="false" batchSize="1" compress="false">
+ <Agent host="flume" port="5050"/>
+ <JsonLayout properties="true" compact="true" eventEol="true" stackTraceAsString="true">
+ <KeyValuePair key="containerId" value="\${docker:containerId}"/>
+ <KeyValuePair key="containerName" value="\${docker:containerName}"/>
+ <KeyValuePair key="imageName" value="\${docker:imageName}"/>
+ <KeyValuePair key="application" value="\$\${lower:\${spring:spring.application.name}}"/>
+ </JsonLayout>
+ </Flume>
+ <Flume name="avroSyslog" ignoreExceptions="false" batchSize="100" compress="false">
+ <Agent host="flume" port="5050"/>
+ <RFC5424Layout enterpriseNumber="18060" includeMDC="true" mdcId="RequestContext" appName="Sample"
+ mdcPrefix="">
+ <LoggerFields>
+ <KeyValuePair key="containerId" value="\${docker:containerId}"/>
+ <KeyValuePair key="containerName" value="\${docker:containerName}"/>
+ <KeyValuePair key="imageName" value="\${docker:imageName}"/>
+ <KeyValuePair key="application" value="\$\${lower:\${spring:spring.application.name}}"/>
+ </LoggerFields>
+ </RFC5424Layout>
+ </Flume>
+ <Kafka name="Kafka" topic="log-test" syncSend="false">
+ <JsonLayout properties="true" compact="true" eventEol="true">
+ <KeyValuePair key="containerId" value="\${docker:containerId}"/>
+ <KeyValuePair key="containerName" value="\${docker:containerName}"/>
+ <KeyValuePair key="imageName" value="\${docker:imageName}"/>
+ <KeyValuePair key="application" value="\$\${lower:\${spring:spring.application.name}}"/>
+ </JsonLayout>
+ <Property name="bootstrap.servers">host.docker.internal:9092</Property>
+ </Kafka>
+ <Socket name="fluent-bit" host="fluent-bit" port="24221">
+ <JsonLayout properties="true" compact="true" eventEol="true" stackTraceAsString="true">
+ <KeyValuePair key="containerId" value="\${docker:containerId}"/>
+ <KeyValuePair key="containerName" value="\${docker:containerName}"/>
+ <KeyValuePair key="imageName" value="\${docker:imageName}"/>
+ <KeyValuePair key="application" value="\$\${lower:\${spring:spring.application.name}}"/>
+ </JsonLayout>
+ </Socket>
+ <RollingFile name="RollingFile" fileName="/var/log/sampleapp/app.log"
+ filePattern="/var/log/sampleapp/archive/app.log.%i">
+ <JsonLayout properties="true" compact="true" eventEol="true" stackTraceAsString="true">
+ <KeyValuePair key="containerId" value="\${docker:containerId}"/>
+ <KeyValuePair key="containerName" value="\${docker:containerName}"/>
+ <KeyValuePair key="imageName" value="\${docker:imageName}"/>
+ <KeyValuePair key="application" value="\$\${lower:\${spring:spring.application.name}}"/>
+ </JsonLayout>
+ <SizeBasedTriggeringPolicy size="10MB" />
+ <DefaultRolloverStrategy max="5"/>
+ </RollingFile>
+ <RollingFile name="RollingSyslog" fileName="/var/log/sampleapp/syslog.log"
+ filePattern="/var/log/sampleapp/archive/syslog.log.%i">
+ <RFC5424Layout enterpriseNumber="18060" includeMDC="true" mdcId="RequestContext" appName="Sample"
+ mdcPrefix="">
+ <LoggerFields>
+ <KeyValuePair key="containerId" value="\${docker:containerId}"/>
+ <KeyValuePair key="containerName" value="\${docker:containerName}"/>
+ <KeyValuePair key="imageName" value="\${docker:imageName}"/>
+ <KeyValuePair key="application" value="\$\${lower:\${spring:spring.application.name}}"/>
+ </LoggerFields>
+ </RFC5424Layout>
+ <SizeBasedTriggeringPolicy size="10MB" />
+ <DefaultRolloverStrategy max="5"/>
+ </RollingFile>-->
+ <Socket name="Elastic"
+ host="\${sys:logstash.search.host}"
+ port="12222"
+ protocol="tcp"
+ bufferedIo="true">
+ <JsonTemplateLayout eventTemplateUri="classpath:EnhancedGelf.json" nullEventDelimiterEnabled="true">
+ <EventTemplateAdditionalFields>
+ <EventTemplateAdditionalField key="containerId" value="\${docker:containerId:-}"/>
+ <EventTemplateAdditionalField key="application" value="\${lower:${spring:spring.application.name:-spring}}"/>
+ <EventTemplateAdditionalField key="kubernetes.serviceAccountName" value="\${k8s:accountName:-}"/>
+ <EventTemplateAdditionalField key="kubernetes.containerId" value="\${k8s:containerId:-}"/>
+ <EventTemplateAdditionalField key="kubernetes.containerName" value="\${k8s:containerName:-}"/>
+ <EventTemplateAdditionalField key="kubernetes.host" value="\${k8s:host:-}"/>
+ <EventTemplateAdditionalField key="kubernetes.labels.app" value="\${k8s:labels.app:-}"/>
+ <EventTemplateAdditionalField key="kubernetes.labels.pod-template-hash" value="\${k8s:labels.podTemplateHash:-}"/>
+ <EventTemplateAdditionalField key="kubernetes.master_url" value="\${k8s:masterUrl:-}"/>
+ <EventTemplateAdditionalField key="kubernetes.namespaceId" value="\${k8s:namespaceId:-}"/>
+ <EventTemplateAdditionalField key="kubernetes.namespaceName" value="\${k8s:namespaceName:-}"/>
+ <EventTemplateAdditionalField key="kubernetes.podID" value="\${k8s:podId:-}"/>
+ <EventTemplateAdditionalField key="kubernetes.podIP" value="\${k8s:podIp:-}"/>
+ <EventTemplateAdditionalField key="kubernetes.podName" value="\${k8s:podName:-}"/>
+ <EventTemplateAdditionalField key="kubernetes.imageId" value="\${k8s:imageId:-}"/>
+ <EventTemplateAdditionalField key="kubernetes.imageName" value="\${k8s:imageName:-}"/>
+ </EventTemplateAdditionalFields>
+ </JsonTemplateLayout>
+ </Socket>
+ <!--<Socket name="Elastic" host="\${sys:logstash.search.host:-localhost}" port="12222" protocol="tcp" bufferedIo="true" ignoreExceptions="false">
+ <GelfLayout includeStackTrace="true" host="${hostName}" includeThreadContext="true" includeNullDelimiter="true"
+ compressionType="OFF">
+ <ThreadContextIncludes>requestId,sessionId,loginId,userId,ipAddress,corpAcctNumber,callingHost,ohBehalfOf,onBehalfOfAccount</ThreadContextIncludes>
+ <MessagePattern>[%t] %-5p %X{requestId, sessionId, loginId, userId, ipAddress, corpAcctNumber} %C{1.}.%M:%L - %m%n</MessagePattern>
+ <KeyValuePair key="docker.containerId" value="\${docker:containerId:-}"/>
+ <KeyValuePair key="application" value="$\${lower:\${spring:spring.application.name}}"/>
+ <KeyValuePair key="kubernetes.serviceAccountName" value="\${k8s:accountName:-}"/>
+ <KeyValuePair key="kubernetes.containerId" value="\${k8s:containerId:-}"/>
+ <KeyValuePair key="kubernetes.containerName" value="\${k8s:containerName:-}"/>
+ <KeyValuePair key="kubernetes.host" value="\${k8s:host:-}"/>
+ <KeyValuePair key="kubernetes.labels.app" value="\${k8s:labels.app:-}"/>
+ <KeyValuePair key="kubernetes.labels.pod-template-hash" value="\${k8s:labels.podTemplateHash:-}"/>
+ <KeyValuePair key="kubernetes.master_url" value="\${k8s:masterUrl:-}"/>
+ <KeyValuePair key="kubernetes.namespaceId" value="\${k8s:namespaceId:-}"/>
+ <KeyValuePair key="kubernetes.namespaceName" value="\${k8s:namespaceName:-}"/>
+ <KeyValuePair key="kubernetes.podID" value="\${k8s:podId:-}"/>
+ <KeyValuePair key="kubernetes.podIP" value="\${k8s:podIp:-}"/>
+ <KeyValuePair key="kubernetes.podName" value="\${k8s:podName:-}"/>
+ <KeyValuePair key="kubernetes.imageId" value="\${k8s:imageId:-}"/>
+ <KeyValuePair key="kubernetes.imageName" value="\${k8s:imageName:-}"/>
+ </GelfLayout>
+ </Socket>-->
+ <Console name="Console" target="SYSTEM_OUT">
+ <RFC5424Layout enterpriseNumber="50177" includeMDC="true" mdcId="RequestContext" appName="SalesforceGateway"
+ mdcPrefix="" newLine="true" mdcIncludes="requestId,sessionId,loginId,userId,ipAddress,corpAcctNumber"/>
+ </Console>
+ <Failover name="log4j" primary="Elastic">
+ <Failovers>
+ <AppenderRef ref="Console"/>
+ </Failovers>
+ </Failover>
+ </Appenders>
+
+ <Loggers>
+ <Logger name="org.apache.kafka" level="warn" additivity="false">
+ <AppenderRef ref="log4j"/>
+ </Logger>
+ <Logger name="org.apache.flume" level="warn" additivity="false">
+ <AppenderRef ref="log4j"/>
+ </Logger>
+ <Logger name="org.apache.avro" level="warn" additivity="false">
+ <AppenderRef ref="log4j"/>
+ </Logger>
+ <Root level="DEBUG">
+ <AppenderRef ref="log4j"/>
+ </Root>
+ </Loggers>
+</Configuration>
\ No newline at end of file
diff --git a/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-server/src/main/java/org/apache/logging/log4j/spring/cloud/config/service/ConfigServiceApplication.java b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-server/src/main/java/org/apache/logging/log4j/spring/cloud/config/service/ConfigServiceApplication.java
new file mode 100644
index 0000000..7fdbb99
--- /dev/null
+++ b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-server/src/main/java/org/apache/logging/log4j/spring/cloud/config/service/ConfigServiceApplication.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.spring.cloud.config.service;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.cloud.config.server.EnableConfigServer;
+
+@EnableConfigServer
+@SpringBootApplication
+public class ConfigServiceApplication {
+ public static void main(String[] args) {
+ SpringApplication.run(ConfigServiceApplication.class, args);
+ }
+}
diff --git a/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-server/src/main/java/org/apache/logging/log4j/spring/cloud/config/service/config/SecurityConfiguration.java b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-server/src/main/java/org/apache/logging/log4j/spring/cloud/config/service/config/SecurityConfiguration.java
new file mode 100644
index 0000000..1abf6d7
--- /dev/null
+++ b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-server/src/main/java/org/apache/logging/log4j/spring/cloud/config/service/config/SecurityConfiguration.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.spring.cloud.config.service.config;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.config.annotation.web.builders.WebSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+
+/**
+ *
+ */
+@Configuration
+@EnableWebSecurity
+public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
+ @Override
+ public void configure(WebSecurity web) throws Exception {
+ web.ignoring().antMatchers("/health")
+ .antMatchers("/metrics")
+ .antMatchers("/info");
+ getHttp().csrf().disable();
+ }
+}
diff --git a/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-server/src/main/resources/application.yaml b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-server/src/main/resources/application.yaml
new file mode 100644
index 0000000..b559127
--- /dev/null
+++ b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-server/src/main/resources/application.yaml
@@ -0,0 +1,38 @@
+spring:
+ cloud:
+ bus:
+ enabled: true
+ config:
+ server:
+# git:
+# uri:
+# searchPaths: "{application},{application}/{profile}"
+ native:
+ searchLocations: file:///${user.dir}/config-repo,file:///${user.dir}/config-repo/{application},file:///${user.dir}/config-repo/{application}/{profile}
+
+ security:
+ user:
+ name: guest
+ password: guest
+
+ rabbitmq:
+ addresses: localhost
+ port: 5672
+ username: guest
+ password: guest
+
+ profiles:
+ active: native
+
+server:
+ port: 8888
+
+ servlet:
+ context-path: /ConfigService
+
+management:
+ endpoints:
+ web:
+ base-path: /
+ exposure:
+ include: health, info, refresh
diff --git a/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-server/src/main/resources/log4j2.xml b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-server/src/main/resources/log4j2.xml
new file mode 100644
index 0000000..3a01958
--- /dev/null
+++ b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-server/src/main/resources/log4j2.xml
@@ -0,0 +1,29 @@
+<?xml version="1.0" encoding="UTF-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 "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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="ERROR" name="ConfigService">
+ <Appenders>
+ <Console name="STDOUT">
+ <PatternLayout pattern="%d %-5level [%t][%logger]%notEmpty{[%markerSimpleName]} %msg%n%xThrowable" />
+ </Console>
+ </Appenders>
+ <Loggers>
+ <Root level="ERROR">
+ <AppenderRef ref="STDOUT" />
+ </Root>
+ </Loggers>
+</Configuration>
\ No newline at end of file
diff --git a/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-server/src/test/java/org/apache/logging/log4j/spring/cloud/config/service/ConfigServiceApplicationTest.java b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-server/src/test/java/org/apache/logging/log4j/spring/cloud/config/service/ConfigServiceApplicationTest.java
new file mode 100644
index 0000000..011366f
--- /dev/null
+++ b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/log4j-spring-cloud-config-sample-server/src/test/java/org/apache/logging/log4j/spring/cloud/config/service/ConfigServiceApplicationTest.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.spring.cloud.config.service;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.test.context.junit4.SpringRunner;
+
+@RunWith(SpringRunner.class)
+@SpringBootTest
+public class ConfigServiceApplicationTest {
+
+ @Test
+ public void contextLoads() {
+ }
+
+}
diff --git a/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/pom.xml b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/pom.xml
new file mode 100644
index 0000000..68dd994
--- /dev/null
+++ b/log4j-spring-cloud-config/log4j-spring-cloud-config-samples/pom.xml
@@ -0,0 +1,108 @@
+<?xml version="1.0" encoding="UTF-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 "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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 xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-spring-cloud-config</artifactId>
+ <version>3.0.0-SNAPSHOT</version>
+ <relativePath>../</relativePath>
+ </parent>
+ <groupId>org.apache.logging.log4j.samples</groupId>
+ <artifactId>log4j-spring-cloud-config-samples</artifactId>
+ <packaging>pom</packaging>
+ <name>Apache Log4j Spring Cloud Config Samples</name>
+ <url>http://maven.apache.org</url>
+ <properties>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ <revapi.skip>true</revapi.skip>
+ </properties>
+ <dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-beans</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-core</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-webmvc</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.ws</groupId>
+ <artifactId>spring-ws-core</artifactId>
+ <version>2.1.4.RELEASE</version>
+ </dependency>
+ <dependency>
+ <groupId>javax.servlet</groupId>
+ <artifactId>servlet-api</artifactId>
+ <version>2.5</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>4.12</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
+ <modules>
+ <module>log4j-spring-cloud-config-sample-server</module>
+ <module>log4j-spring-cloud-config-sample-application</module>
+ </modules>
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-site-plugin</artifactId>
+ <version>${site.plugin.version}</version>
+ <configuration>
+ <skip>true</skip>
+ <skipDeploy>true</skipDeploy>
+ </configuration>
+ </plugin>
+ <!-- Include the standard NOTICE and LICENSE -->
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-remote-resources-plugin</artifactId>
+ <executions>
+ <execution>
+ <goals>
+ <goal>process</goal>
+ </goals>
+ <configuration>
+ <skip>false</skip>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-deploy-plugin</artifactId>
+ <version>${deploy.plugin.version}</version>
+ <configuration>
+ <skip>true</skip>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/log4j-spring-cloud-config/pom.xml b/log4j-spring-cloud-config/pom.xml
new file mode 100644
index 0000000..09a1c83
--- /dev/null
+++ b/log4j-spring-cloud-config/pom.xml
@@ -0,0 +1,93 @@
+<?xml version="1.0" encoding="UTF-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 "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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 xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j</artifactId>
+ <version>3.0.0-SNAPSHOT</version>
+ <relativePath>../</relativePath>
+ </parent>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-spring-cloud-config</artifactId>
+ <packaging>pom</packaging>
+ <name>Apache Log4j Spring Cloud Config Support</name>
+ <url>http://maven.apache.org</url>
+ <properties>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ <revapi.skip>true</revapi.skip>
+ <spring-cloud-version>Finchley.SR3</spring-cloud-version>
+ <spring-boot.version>2.0.8.RELEASE</spring-boot.version>
+ <springVersion>5.0.12.RELEASE</springVersion>
+ <log4jParentDir>${basedir}/..</log4jParentDir>
+ </properties>
+ <dependencyManagement>
+ <dependencies>
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-framework-bom</artifactId>
+ <version>${springVersion}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot</artifactId>
+ <version>${spring-boot.version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.cloud</groupId>
+ <artifactId>spring-cloud-dependencies</artifactId>
+ <version>${spring-cloud-version}</version>
+ <type>pom</type>
+ <scope>import</scope>
+ </dependency>
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>4.12</version>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+ </dependencyManagement>
+ <modules>
+ <module>log4j-spring-cloud-config-client</module>
+ <module>log4j-spring-cloud-config-samples</module>
+ </modules>
+ <build>
+ <plugins>
+ <!-- Include the standard NOTICE and LICENSE -->
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-remote-resources-plugin</artifactId>
+ <executions>
+ <execution>
+ <goals>
+ <goal>process</goal>
+ </goals>
+ <configuration>
+ <skip>false</skip>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/log4j-spring-cloud-config/src/site/markdown/index.md b/log4j-spring-cloud-config/src/site/markdown/index.md
new file mode 100644
index 0000000..7f51be4
--- /dev/null
+++ b/log4j-spring-cloud-config/src/site/markdown/index.md
@@ -0,0 +1,21 @@
+<!-- vim: set syn=markdown : -->
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT 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 Spring Cloud Config
+
+
diff --git a/log4j-spring-cloud-config/src/site/site.xml b/log4j-spring-cloud-config/src/site/site.xml
new file mode 100644
index 0000000..14bbb87
--- /dev/null
+++ b/log4j-spring-cloud-config/src/site/site.xml
@@ -0,0 +1,52 @@
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+-->
+<project name="Log4j Spring Cloud Config"
+ xmlns="http://maven.apache.org/DECORATION/1.4.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/DECORATION/1.4.0 http://maven.apache.org/xsd/decoration-1.4.0.xsd">
+ <body>
+ <links>
+ <item name="Apache" href="http://www.apache.org/" />
+ <item name="Logging Services" href="http://logging.apache.org/"/>
+ <item name="Log4j" href="../index.html"/>
+ </links>
+
+ <!-- Component-specific reports -->
+ <menu ref="reports"/>
+
+ <!-- Overall Project Info -->
+ <menu name="Log4j Project Information" img="icon-info-sign">
+ <item name="Dependencies" href="../dependencies.html" />
+ <item name="Dependency Convergence" href="../dependency-convergence.html" />
+ <item name="Dependency Management" href="../dependency-management.html" />
+ <item name="Project Team" href="../team-list.html" />
+ <item name="Mailing Lists" href="../mail-lists.html" />
+ <item name="Issue Tracking" href="../issue-tracking.html" />
+ <item name="Project License" href="../license.html" />
+ <item name="Source Repository" href="../source-repository.html" />
+ <item name="Project Summary" href="../project-summary.html" />
+ </menu>
+
+ <menu name="Log4j Project Reports" img="icon-cog">
+ <item name="Changes Report" href="../changes-report.html" />
+ <item name="JIRA Report" href="../jira-report.html" />
+ <item name="Surefire Report" href="../surefire-report.html" />
+ <item name="RAT Report" href="../rat-report.html" />
+ </menu>
+ </body>
+</project>
diff --git a/log4j-taglib/pom.xml b/log4j-taglib/pom.xml
index ba309b7..1e1e36e 100644
--- a/log4j-taglib/pom.xml
+++ b/log4j-taglib/pom.xml
@@ -150,6 +150,7 @@
project -->
<detectOfflineLinks>false</detectOfflineLinks>
<linksource>true</linksource>
+ <source>8</source>
</configuration>
<reportSets>
<reportSet>
diff --git a/log4j-taglib/revapi.json b/log4j-taglib/revapi.json
new file mode 100644
index 0000000..9a74788
--- /dev/null
+++ b/log4j-taglib/revapi.json
@@ -0,0 +1,14 @@
+[
+ {
+ "extension": "revapi.java",
+ "configuration": {
+ "filter": {
+ "classes": {
+ "exclude": [
+ "org\\.apache\\.logging\\.log4j\\.taglib\\.Log4jTaglibLogger"
+ ]
+ }
+ }
+ }
+ }
+]
\ No newline at end of file
diff --git a/log4j-taglib/src/site/site.xml b/log4j-taglib/src/site/site.xml
index 9ca359b..b201bcc 100644
--- a/log4j-taglib/src/site/site.xml
+++ b/log4j-taglib/src/site/site.xml
@@ -20,14 +20,6 @@
xmlns="http://maven.apache.org/DECORATION/1.4.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/DECORATION/1.4.0 http://maven.apache.org/xsd/decoration-1.4.0.xsd">
- <bannerLeft>
- <name>Logging Services</name>
- <src>../images/ls-logo.jpg</src>
- <href>../index.html</href>
- </bannerLeft>
- <bannerRight>
- <src>../images/logo.jpg</src>
- </bannerRight>
<body>
<links>
<item name="Apache" href="http://www.apache.org/" />
@@ -57,8 +49,5 @@
<item name="Surefire Report" href="../surefire-report.html" />
<item name="RAT Report" href="../rat-report.html" />
</menu>
- <footer><div class="row span16">Apache Logging, Apache Log4j, Log4j, Apache, the Apache feather logo, and the
- Apache Logging project logo are trademarks of The Apache Software Foundation.</div>
- </footer>
</body>
</project>
\ No newline at end of file
diff --git a/log4j-taglib/src/test/java/org/apache/logging/log4j/taglib/SetLoggerTagTest.java b/log4j-taglib/src/test/java/org/apache/logging/log4j/taglib/SetLoggerTagTest.java
index 022ea58..bc6450d 100644
--- a/log4j-taglib/src/test/java/org/apache/logging/log4j/taglib/SetLoggerTagTest.java
+++ b/log4j-taglib/src/test/java/org/apache/logging/log4j/taglib/SetLoggerTagTest.java
@@ -24,7 +24,6 @@
import org.apache.logging.log4j.message.MessageFactory;
import org.apache.logging.log4j.message.StringFormatterMessageFactory;
import org.apache.logging.log4j.spi.AbstractLogger;
-import org.apache.logging.log4j.spi.MessageFactory2Adapter;
import org.junit.Before;
import org.junit.Test;
import org.springframework.mock.web.MockPageContext;
@@ -165,17 +164,14 @@
checkMessageFactory("The message factory is not correct.", factory, logger);
}
- private static void checkMessageFactory(final String msg, final MessageFactory messageFactory1,
- final Logger testLogger1) {
- if (messageFactory1 == null) {
+ private static void checkMessageFactory(final String msg, final MessageFactory messageFactory,
+ final Logger testLogger) {
+ if (messageFactory == null) {
assertEquals(msg, AbstractLogger.DEFAULT_MESSAGE_FACTORY_CLASS,
- testLogger1.getMessageFactory().getClass());
+ testLogger.getMessageFactory().getClass());
} else {
- MessageFactory actual = testLogger1.getMessageFactory();
- if (actual instanceof MessageFactory2Adapter) {
- actual = ((MessageFactory2Adapter) actual).getOriginal();
- }
- assertEquals(msg, messageFactory1, actual);
+ MessageFactory actual = testLogger.getMessageFactory();
+ assertEquals(msg, messageFactory, actual);
}
}
diff --git a/log4j-to-slf4j/pom.xml b/log4j-to-slf4j/pom.xml
index e123b91..8db8853 100644
--- a/log4j-to-slf4j/pom.xml
+++ b/log4j-to-slf4j/pom.xml
@@ -140,6 +140,7 @@
project -->
<detectOfflineLinks>false</detectOfflineLinks>
<linksource>true</linksource>
+ <source>8</source>
</configuration>
<reportSets>
<reportSet>
diff --git a/log4j-to-slf4j/src/main/java/org/apache/logging/slf4j/SLF4JLoggerContextFactory.java b/log4j-to-slf4j/src/main/java/org/apache/logging/slf4j/SLF4JLoggerContextFactory.java
index 2f20685..2b90008 100644
--- a/log4j-to-slf4j/src/main/java/org/apache/logging/slf4j/SLF4JLoggerContextFactory.java
+++ b/log4j-to-slf4j/src/main/java/org/apache/logging/slf4j/SLF4JLoggerContextFactory.java
@@ -28,7 +28,7 @@
*/
public class SLF4JLoggerContextFactory implements LoggerContextFactory {
private static final StatusLogger LOGGER = StatusLogger.getLogger();
- private static LoggerContext context = new SLF4JLoggerContext();
+ private static final LoggerContext context = new SLF4JLoggerContext();
public SLF4JLoggerContextFactory() {
// LOG4J2-230, LOG4J2-204 (improve error reporting when misconfigured)
diff --git a/log4j-to-slf4j/src/test/java/org/apache/logging/slf4j/LoggerTest.java b/log4j-to-slf4j/src/test/java/org/apache/logging/slf4j/LoggerTest.java
index 77b9c4e..e994d19 100644
--- a/log4j-to-slf4j/src/test/java/org/apache/logging/slf4j/LoggerTest.java
+++ b/log4j-to-slf4j/src/test/java/org/apache/logging/slf4j/LoggerTest.java
@@ -29,7 +29,6 @@
import org.apache.logging.log4j.message.ParameterizedMessageFactory;
import org.apache.logging.log4j.message.StringFormatterMessageFactory;
import org.apache.logging.log4j.spi.AbstractLogger;
-import org.apache.logging.log4j.spi.MessageFactory2Adapter;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Test;
@@ -71,22 +70,8 @@
}
@Test
- public void basicFlowDepreacted() {
- logger.entry();
- logger.exit();
- assertThat(list.strList, hasSize(2));
- }
-
- @Test
- public void simpleFlowDeprecated() {
- logger.entry(CONFIG);
- logger.exit(0);
- assertThat(list.strList, hasSize(2));
- }
-
- @Test
public void simpleFlow() {
- logger.entry(CONFIG);
+ logger.traceEntry(CONFIG);
logger.traceExit(0);
assertThat(list.strList, hasSize(2));
}
@@ -140,15 +125,12 @@
return testLogger;
}
- private static void checkMessageFactory(final MessageFactory messageFactory1, final Logger testLogger1) {
- if (messageFactory1 == null) {
- assertEquals(AbstractLogger.DEFAULT_MESSAGE_FACTORY_CLASS, testLogger1.getMessageFactory().getClass());
+ private static void checkMessageFactory(final MessageFactory messageFactory, final Logger testLogger) {
+ if (messageFactory == null) {
+ assertEquals(AbstractLogger.DEFAULT_MESSAGE_FACTORY_CLASS, testLogger.getMessageFactory().getClass());
} else {
- MessageFactory actual = testLogger1.getMessageFactory();
- if (actual instanceof MessageFactory2Adapter) {
- actual = ((MessageFactory2Adapter) actual).getOriginal();
- }
- assertEquals(messageFactory1, actual);
+ MessageFactory actual = testLogger.getMessageFactory();
+ assertEquals(messageFactory, actual);
}
}
diff --git a/log4j-web/pom.xml b/log4j-web/pom.xml
index 69bc40a..361accb 100644
--- a/log4j-web/pom.xml
+++ b/log4j-web/pom.xml
@@ -135,6 +135,7 @@
project -->
<detectOfflineLinks>false</detectOfflineLinks>
<linksource>true</linksource>
+ <source>8</source>
<links>
<link>http://docs.oracle.com/javaee/6/api/</link>
</links>
diff --git a/log4j-web/revapi.json b/log4j-web/revapi.json
new file mode 100644
index 0000000..9ba8949
--- /dev/null
+++ b/log4j-web/revapi.json
@@ -0,0 +1,24 @@
+[
+ {
+ "extension": "revapi.java",
+ "configuration": {
+ "filter": {
+ "classes": {
+ "exclude": [
+ "org\\.apache\\.logging\\.log4j\\.web\\.Log4jWebLifeCycle"
+ ]
+ }
+ }
+ }
+ },
+ {
+ "extension": "revapi.ignore",
+ "configuration": [
+ {
+ "code": "java.method.removed",
+ "old": "method org.apache.logging.log4j.web.appender.ServletAppender org.apache.logging.log4j.web.appender.ServletAppender::createAppender(org.apache.logging.log4j.core.Layout<? extends java.io.Serializable>, org.apache.logging.log4j.core.Filter, java.lang.String, boolean)",
+ "justification": "Remove deprecated code"
+ }
+ ]
+ }
+]
\ No newline at end of file
diff --git a/log4j-web/src/main/java/org/apache/logging/log4j/web/Log4jServletContainerInitializer.java b/log4j-web/src/main/java/org/apache/logging/log4j/web/Log4jServletContainerInitializer.java
index aad1b63..19ac246 100644
--- a/log4j-web/src/main/java/org/apache/logging/log4j/web/Log4jServletContainerInitializer.java
+++ b/log4j-web/src/main/java/org/apache/logging/log4j/web/Log4jServletContainerInitializer.java
@@ -34,14 +34,14 @@
*/
public class Log4jServletContainerInitializer implements ServletContainerInitializer {
- private static final Logger LOGGER = StatusLogger.getLogger();
-
@Override
public void onStartup(final Set<Class<?>> classes, final ServletContext servletContext) throws ServletException {
if (servletContext.getMajorVersion() > 2 && servletContext.getEffectiveMajorVersion() > 2 &&
!"true".equalsIgnoreCase(servletContext.getInitParameter(
Log4jWebSupport.IS_LOG4J_AUTO_INITIALIZATION_DISABLED
))) {
+ final Logger LOGGER = StatusLogger.getLogger();
+
LOGGER.debug("Log4jServletContainerInitializer starting up Log4j in Servlet 3.0+ environment.");
final FilterRegistration.Dynamic filter =
diff --git a/log4j-web/src/main/java/org/apache/logging/log4j/web/Log4jServletContextListener.java b/log4j-web/src/main/java/org/apache/logging/log4j/web/Log4jServletContextListener.java
index 4a65d92..a121ccf 100644
--- a/log4j-web/src/main/java/org/apache/logging/log4j/web/Log4jServletContextListener.java
+++ b/log4j-web/src/main/java/org/apache/logging/log4j/web/Log4jServletContextListener.java
@@ -24,6 +24,7 @@
import javax.servlet.ServletContextListener;
import org.apache.logging.log4j.Logger;
+import org.apache.logging.log4j.core.LifeCycle;
import org.apache.logging.log4j.core.LifeCycle2;
import org.apache.logging.log4j.status.StatusLogger;
import org.apache.logging.log4j.util.Strings;
@@ -70,16 +71,12 @@
this.initializer.clearLoggerContext(); // the application is finished
// shutting down now
- if (initializer instanceof LifeCycle2) {
- final String stopTimeoutStr = servletContext.getInitParameter(KEY_STOP_TIMEOUT);
- final long stopTimeout = Strings.isEmpty(stopTimeoutStr) ? DEFAULT_STOP_TIMEOUT
- : Long.parseLong(stopTimeoutStr);
- final String timeoutTimeUnitStr = servletContext.getInitParameter(KEY_STOP_TIMEOUT_TIMEUNIT);
- final TimeUnit timeoutTimeUnit = Strings.isEmpty(timeoutTimeUnitStr) ? DEFAULT_STOP_TIMEOUT_TIMEUNIT
- : TimeUnit.valueOf(timeoutTimeUnitStr.toUpperCase(Locale.ROOT));
- ((LifeCycle2) this.initializer).stop(stopTimeout, timeoutTimeUnit);
- } else {
- this.initializer.stop();
- }
+ final String stopTimeoutStr = servletContext.getInitParameter(KEY_STOP_TIMEOUT);
+ final long stopTimeout = Strings.isEmpty(stopTimeoutStr) ? DEFAULT_STOP_TIMEOUT
+ : Long.parseLong(stopTimeoutStr);
+ final String timeoutTimeUnitStr = servletContext.getInitParameter(KEY_STOP_TIMEOUT_TIMEUNIT);
+ final TimeUnit timeoutTimeUnit = Strings.isEmpty(timeoutTimeUnitStr) ? DEFAULT_STOP_TIMEOUT_TIMEUNIT
+ : TimeUnit.valueOf(timeoutTimeUnitStr.toUpperCase(Locale.ROOT));
+ ((LifeCycle) this.initializer).stop(stopTimeout, timeoutTimeUnit);
}
}
diff --git a/log4j-web/src/main/java/org/apache/logging/log4j/web/Log4jServletFilter.java b/log4j-web/src/main/java/org/apache/logging/log4j/web/Log4jServletFilter.java
index e95f334..efcb0ba 100644
--- a/log4j-web/src/main/java/org/apache/logging/log4j/web/Log4jServletFilter.java
+++ b/log4j-web/src/main/java/org/apache/logging/log4j/web/Log4jServletFilter.java
@@ -17,6 +17,10 @@
package org.apache.logging.log4j.web;
import java.io.IOException;
+import java.util.concurrent.Executor;
+import java.util.function.BiConsumer;
+import java.util.function.Function;
+
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
@@ -44,6 +48,7 @@
private static final Logger LOGGER = StatusLogger.getLogger();
static final String ALREADY_FILTERED_ATTRIBUTE = Log4jServletFilter.class.getName() + ".FILTERED";
+ static final ThreadLocal<ServletRequest> CURRENT_REQUEST = new ThreadLocal<>();
private ServletContext servletContext;
private Log4jWebLifeCycle initializer;
@@ -55,6 +60,18 @@
this.initializer = WebLoggerContextUtils.getWebLifeCycle(this.servletContext);
this.initializer.clearLoggerContext(); // the application is mostly finished starting up now
+
+ filterConfig.getServletContext().setAttribute("log4j.requestExecutor",
+ (BiConsumer<ServletRequest, Runnable>) (request, command) -> {
+ try {
+ Log4jServletFilter.this.initializer.setLoggerContext();
+ CURRENT_REQUEST.set(request);
+ command.run();
+ } finally {
+ Log4jServletFilter.this.initializer.clearLoggerContext();
+ CURRENT_REQUEST.remove();
+ }
+ });
}
@Override
@@ -67,10 +84,11 @@
try {
this.initializer.setLoggerContext();
-
+ CURRENT_REQUEST.set(request);
chain.doFilter(request, response);
} finally {
this.initializer.clearLoggerContext();
+ CURRENT_REQUEST.remove();
}
}
}
diff --git a/log4j-web/src/main/java/org/apache/logging/log4j/web/ServletRequestThreadContext.java b/log4j-web/src/main/java/org/apache/logging/log4j/web/ServletRequestThreadContext.java
index d7820b2..986fa2d 100644
--- a/log4j-web/src/main/java/org/apache/logging/log4j/web/ServletRequestThreadContext.java
+++ b/log4j-web/src/main/java/org/apache/logging/log4j/web/ServletRequestThreadContext.java
@@ -23,6 +23,7 @@
import org.apache.logging.log4j.ThreadContext;
+@Deprecated // use WebLookup
public class ServletRequestThreadContext {
public static void put(final String key, final ServletRequest servletRequest) {
diff --git a/log4j-web/src/main/java/org/apache/logging/log4j/web/WebLookup.java b/log4j-web/src/main/java/org/apache/logging/log4j/web/WebLookup.java
index aa6d141..c58ceec 100644
--- a/log4j-web/src/main/java/org/apache/logging/log4j/web/WebLookup.java
+++ b/log4j-web/src/main/java/org/apache/logging/log4j/web/WebLookup.java
@@ -18,15 +18,28 @@
// Please note that if you move this class, make sure to update the Interpolator class (if still applicable) or remove
// this comment if no longer relevant
+import java.security.Principal;
+import java.util.Objects;
+import java.util.stream.Stream;
+
import javax.servlet.ServletContext;
+import javax.servlet.ServletRequest;
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpSession;
import org.apache.logging.log4j.core.LogEvent;
-import org.apache.logging.log4j.core.config.plugins.Plugin;
+import org.apache.logging.log4j.plugins.Plugin;
import org.apache.logging.log4j.core.lookup.AbstractLookup;
import org.apache.logging.log4j.util.Strings;
@Plugin(name = "web", category = "Lookup")
public class WebLookup extends AbstractLookup {
+ private static final String SESSION_ATTR_PREFIX = "session.attr.";
+ private static final String REQUEST_ATTR_PREFIX = "request.attr.";
+ private static final String REQUEST_HEADER_PREFIX = "header.";
+ private static final String REQUEST_COOKIE_PREFIX = "cookie.";
+ private static final String REQUEST_PARAMETER_PREFIX = "request.parameter.";
private static final String ATTR_PREFIX = "attr.";
private static final String INIT_PARAM_PREFIX = "initParam.";
@@ -48,6 +61,98 @@
return ctx.getInitParameter(paramName);
}
+ if (key.startsWith(REQUEST_ATTR_PREFIX)) {
+ final String name = key.substring(REQUEST_ATTR_PREFIX.length());
+ final ServletRequest req = getRequest();
+ final Object value = req == null ? null : req.getAttribute(name);
+ return value == null ? null : String.valueOf(value);
+ }
+
+ if (key.startsWith(REQUEST_HEADER_PREFIX)) {
+ final String name = key.substring(REQUEST_HEADER_PREFIX.length());
+ final ServletRequest req = getRequest();
+ return HttpServletRequest.class.isInstance(req) ? HttpServletRequest.class.cast(req).getHeader(name) : null;
+ }
+
+ if (key.startsWith(REQUEST_COOKIE_PREFIX)) {
+ final String name = key.substring(REQUEST_COOKIE_PREFIX.length());
+ final ServletRequest req = getRequest();
+ return HttpServletRequest.class.isInstance(req) ?
+ Stream.of(HttpServletRequest.class.cast(req).getCookies())
+ .filter(c -> name.equals(c.getName()))
+ .findFirst()
+ .map(Cookie::getValue)
+ .orElse(null) :
+ null;
+ }
+
+ if (key.startsWith(REQUEST_PARAMETER_PREFIX)) {
+ final String name = key.substring(REQUEST_PARAMETER_PREFIX.length());
+ final ServletRequest req = getRequest();
+ return HttpServletRequest.class.isInstance(req) ?
+ HttpServletRequest.class.cast(req).getParameter(name) : null;
+ }
+
+ if (key.startsWith(SESSION_ATTR_PREFIX)) {
+ final ServletRequest req = getRequest();
+ if (HttpServletRequest.class.isInstance(req)) {
+ final HttpSession session = HttpServletRequest.class.cast(req).getSession(false);
+ if (session != null) {
+ final String name = key.substring(SESSION_ATTR_PREFIX.length());
+ return Objects.toString(session.getAttribute(name), null);
+ }
+ }
+ return null;
+ }
+
+ if ("request.method".equals(key)) {
+ final ServletRequest req = getRequest();
+ return HttpServletRequest.class.isInstance(req) ? HttpServletRequest.class.cast(req).getMethod() : null;
+ }
+
+ if ("request.uri".equals(key)) {
+ final ServletRequest req = getRequest();
+ return HttpServletRequest.class.isInstance(req) ? HttpServletRequest.class.cast(req).getRequestURI() : null;
+ }
+
+ if ("request.url".equals(key)) {
+ final ServletRequest req = getRequest();
+ return HttpServletRequest.class.isInstance(req) ?
+ HttpServletRequest.class.cast(req).getRequestURL().toString() : null;
+ }
+
+ if ("request.remoteAddress".equals(key)) {
+ final ServletRequest req = getRequest();
+ return HttpServletRequest.class.isInstance(req) ?
+ HttpServletRequest.class.cast(req).getRemoteAddr() : null;
+ }
+
+ if ("request.remoteHost".equals(key)) {
+ final ServletRequest req = getRequest();
+ return HttpServletRequest.class.isInstance(req) ?
+ HttpServletRequest.class.cast(req).getRemoteHost() : null;
+ }
+
+ if ("request.remotePort".equals(key)) {
+ final ServletRequest req = getRequest();
+ return HttpServletRequest.class.isInstance(req) ?
+ Integer.toString(HttpServletRequest.class.cast(req).getRemotePort()) : null;
+ }
+
+ if ("request.principal".equals(key)) {
+ final ServletRequest req = getRequest();
+ final Principal pcp = HttpServletRequest.class.isInstance(req) ?
+ HttpServletRequest.class.cast(req).getUserPrincipal() : null;
+ return pcp == null ? null : pcp.getName();
+ }
+
+ if ("session.id".equals(key)) {
+ final ServletRequest req = getRequest();
+ final HttpSession session = HttpServletRequest.class.isInstance(req) ?
+ HttpServletRequest.class.cast(req).getSession(false) : null;
+ return session == null ? null : session.getId();
+ }
+
if ("rootDir".equals(key)) {
final String root = ctx.getRealPath("/");
if (root == null) {
@@ -59,6 +164,21 @@
return root;
}
+ if ("contextPathName".equals(key)) {
+ String path = ctx.getContextPath();
+ if (path.trim().contains("/")) {
+ String[] fields = path.split("/");
+ for (String field : fields) {
+ if (field.length() > 0) {
+ return field;
+ }
+ }
+ return null;
+
+ }
+ return ctx.getContextPath();
+ }
+
if ("contextPath".equals(key)) {
return ctx.getContextPath();
}
@@ -98,4 +218,12 @@
ctx.log(getClass().getName() + " unable to resolve key " + Strings.quote(key));
return null;
}
+
+ private ServletRequest getRequest() {
+ final ServletRequest servletRequest = Log4jServletFilter.CURRENT_REQUEST.get();
+ if (servletRequest == null) { // don't leak the thread map
+ Log4jServletFilter.CURRENT_REQUEST.remove();
+ }
+ return servletRequest;
+ }
}
diff --git a/log4j-web/src/main/java/org/apache/logging/log4j/web/appender/ServletAppender.java b/log4j-web/src/main/java/org/apache/logging/log4j/web/appender/ServletAppender.java
index b7e30c2..e54004c 100644
--- a/log4j-web/src/main/java/org/apache/logging/log4j/web/appender/ServletAppender.java
+++ b/log4j-web/src/main/java/org/apache/logging/log4j/web/appender/ServletAppender.java
@@ -16,21 +16,21 @@
*/
package org.apache.logging.log4j.web.appender;
-import java.io.Serializable;
-
-import javax.servlet.ServletContext;
-
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.PluginBuilderAttribute;
-import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
+import org.apache.logging.log4j.core.config.Property;
import org.apache.logging.log4j.core.layout.AbstractStringLayout;
import org.apache.logging.log4j.core.layout.PatternLayout;
+import org.apache.logging.log4j.plugins.Plugin;
+import org.apache.logging.log4j.plugins.PluginBuilderAttribute;
+import org.apache.logging.log4j.plugins.PluginFactory;
import org.apache.logging.log4j.web.WebLoggerContextUtils;
+import javax.servlet.ServletContext;
+import java.io.Serializable;
+
/**
* Logs using the ServletContext's log method
*/
@@ -38,7 +38,7 @@
public class ServletAppender extends AbstractAppender {
public static class Builder<B extends Builder<B>> extends AbstractAppender.Builder<B>
- implements org.apache.logging.log4j.core.util.Builder<ServletAppender> {
+ implements org.apache.logging.log4j.plugins.util.Builder<ServletAppender> {
@PluginBuilderAttribute
private boolean logThrowables;
@@ -61,7 +61,8 @@
LOGGER.error("Layout must be a StringLayout to log to ServletContext");
return null;
}
- return new ServletAppender(name, layout, getFilter(), servletContext, isIgnoreExceptions(), logThrowables);
+ return new ServletAppender(name, layout, getFilter(), servletContext, isIgnoreExceptions(), logThrowables,
+ getPropertyArray());
}
/**
@@ -82,7 +83,7 @@
}
- @PluginBuilderFactory
+ @PluginFactory
public static <B extends Builder<B>> B newBuilder() {
return new Builder<B>().asBuilder();
}
@@ -91,8 +92,9 @@
private final boolean logThrowables;
private ServletAppender(final String name, final Layout<? extends Serializable> layout, final Filter filter,
- final ServletContext servletContext, final boolean ignoreExceptions, final boolean logThrowables) {
- super(name, filter, layout, ignoreExceptions);
+ final ServletContext servletContext, final boolean ignoreExceptions, final boolean logThrowables,
+ Property[] properties) {
+ super(name, filter, layout, ignoreExceptions, properties);
this.servletContext = servletContext;
this.logThrowables = logThrowables;
}
@@ -107,27 +109,4 @@
}
}
- /**
- * Creates a Servlet Appender.
- * @param layout The layout to use (required). Must extend {@link AbstractStringLayout}.
- * @param filter The Filter or null.
- * @param name The name of the Appender (required).
- * @param ignoreExceptions If {@code true} (default) exceptions encountered when appending events are logged;
- * otherwise they are propagated to the caller.
- * @return The ServletAppender.
- * @deprecated Use {@link #newBuilder()}.
- */
- @Deprecated
- public static ServletAppender createAppender(final Layout<? extends Serializable> layout, final Filter filter,
- final String name, final boolean ignoreExceptions) {
- // @formatter:off
- return newBuilder()
- .withFilter(filter)
- .withIgnoreExceptions(ignoreExceptions)
- .withLayout(layout)
- .withName(name)
- .build();
- // @formatter:on
- }
-
}
diff --git a/log4j-web/src/test/java/org/apache/logging/log4j/web/Log4jServletContextListenerTest.java b/log4j-web/src/test/java/org/apache/logging/log4j/web/Log4jServletContextListenerTest.java
index 7be7b9c..1dbc75d 100644
--- a/log4j-web/src/test/java/org/apache/logging/log4j/web/Log4jServletContextListenerTest.java
+++ b/log4j-web/src/test/java/org/apache/logging/log4j/web/Log4jServletContextListenerTest.java
@@ -31,6 +31,8 @@
import static org.mockito.BDDMockito.then;
import static org.mockito.BDDMockito.willThrow;
+import java.util.concurrent.TimeUnit;
+
@RunWith(MockitoJUnitRunner.class)
public class Log4jServletContextListenerTest {
@Mock
@@ -59,7 +61,7 @@
this.listener.contextDestroyed(this.event);
then(initializer).should().clearLoggerContext();
- then(initializer).should().stop();
+ then(initializer).should().stop(30L, TimeUnit.SECONDS);
}
@Test
diff --git a/log4j-web/src/test/java/org/apache/logging/log4j/web/WebLookupTest.java b/log4j-web/src/test/java/org/apache/logging/log4j/web/WebLookupTest.java
index a93262e..a95e01d 100644
--- a/log4j-web/src/test/java/org/apache/logging/log4j/web/WebLookupTest.java
+++ b/log4j-web/src/test/java/org/apache/logging/log4j/web/WebLookupTest.java
@@ -27,6 +27,7 @@
import org.apache.logging.log4j.core.impl.ContextAnchor;
import org.apache.logging.log4j.core.lookup.StrSubstitutor;
import org.junit.Test;
+import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockServletContext;
import static org.junit.Assert.*;
@@ -37,6 +38,7 @@
public void testLookup() throws Exception {
ContextAnchor.THREAD_CONTEXT.remove();
final ServletContext servletContext = new MockServletContext();
+ ((MockServletContext) servletContext).setContextPath("/WebApp");
servletContext.setAttribute("TestAttr", "AttrValue");
servletContext.setInitParameter("TestParam", "ParamValue");
servletContext.setAttribute("Name1", "Ben");
@@ -64,6 +66,9 @@
value = substitutor.replace("${web:Name2}");
assertNotNull("No value for Name2", value);
assertEquals("Incorrect value for Name2: " + value, "Jerry", value);
+ value = substitutor.replace("${web:contextPathName}");
+ assertNotNull("No value for context name", value);
+ assertEquals("Incorrect value for context name", "WebApp", value);
} catch (final IllegalStateException e) {
fail("Failed to initialize Log4j properly." + e.getMessage());
}
@@ -75,6 +80,7 @@
public void testLookup2() throws Exception {
ContextAnchor.THREAD_CONTEXT.remove();
final ServletContext servletContext = new MockServletContext();
+ ((MockServletContext) servletContext).setContextPath("/");
servletContext.setAttribute("TestAttr", "AttrValue");
servletContext.setInitParameter("myapp.logdir", "target");
servletContext.setAttribute("Name1", "Ben");
@@ -95,8 +101,81 @@
assertEquals("target/myapp.log", fa.getFileName());
}
}
+ final StrSubstitutor substitutor = config.getStrSubstitutor();
+ String value = substitutor.replace("${web:contextPathName:-default}");
+ assertNotNull("No value for context name", value);
+ assertEquals("Incorrect value for context name", "default", value);
initializer.stop();
ContextAnchor.THREAD_CONTEXT.remove();
}
+ @Test
+ public void testRequestAttributeLookups() {
+ final Log4jWebLifeCycle initializer = startInitializer();
+ final MockHttpServletRequest request = new MockHttpServletRequest();
+ request.setAttribute("foo", "bar");
+ Log4jServletFilter.CURRENT_REQUEST.set(request);
+ final WebLookup lookup = new WebLookup();
+ assertEquals("bar", lookup.lookup(null, "request.attr.foo"));
+ assertNull(lookup.lookup(null, "request.attr.missing"));
+ Log4jServletFilter.CURRENT_REQUEST.remove();
+ assertNull(lookup.lookup(null, "request.attr.missing"));
+ assertNull(lookup.lookup(null, "request.attr.foo"));
+ initializer.stop();
+ ContextAnchor.THREAD_CONTEXT.remove();
+ }
+
+ @Test
+ public void testRequestHeaderLookups() {
+ final Log4jWebLifeCycle initializer = startInitializer();
+ final MockHttpServletRequest request = new MockHttpServletRequest();
+ request.addHeader("foo", "bar");
+ Log4jServletFilter.CURRENT_REQUEST.set(request);
+ final WebLookup lookup = new WebLookup();
+ assertEquals("bar", lookup.lookup(null, "header.foo"));
+ assertNull(lookup.lookup(null, "header.missing"));
+ Log4jServletFilter.CURRENT_REQUEST.remove();
+ assertNull(lookup.lookup(null, "header.foo"));
+ assertNull(lookup.lookup(null, "header.missing"));
+ initializer.stop();
+ ContextAnchor.THREAD_CONTEXT.remove();
+ }
+
+ @Test
+ public void testSessionId() {
+ final Log4jWebLifeCycle initializer = startInitializer();
+ final MockHttpServletRequest request = new MockHttpServletRequest();
+ request.getSession();
+ Log4jServletFilter.CURRENT_REQUEST.set(request);
+ final WebLookup lookup = new WebLookup();
+ assertNotNull(lookup.lookup(null, "session.id"));
+ Log4jServletFilter.CURRENT_REQUEST.remove();
+ assertNull(lookup.lookup(null, "session.id"));
+ initializer.stop();
+ ContextAnchor.THREAD_CONTEXT.remove();
+ }
+
+ @Test
+ public void testSessionAttribute() {
+ final Log4jWebLifeCycle initializer = startInitializer();
+ final MockHttpServletRequest request = new MockHttpServletRequest();
+ request.getSession().setAttribute("foo", "bar");
+ Log4jServletFilter.CURRENT_REQUEST.set(request);
+ final WebLookup lookup = new WebLookup();
+ assertEquals("bar", lookup.lookup(null, "session.attr.foo"));
+ Log4jServletFilter.CURRENT_REQUEST.remove();
+ assertNull(lookup.lookup(null, "session.attr.foo"));
+ initializer.stop();
+ ContextAnchor.THREAD_CONTEXT.remove();
+ }
+
+ private Log4jWebLifeCycle startInitializer() {
+ ContextAnchor.THREAD_CONTEXT.remove();
+ final ServletContext servletContext = new MockServletContext();
+ final Log4jWebLifeCycle initializer = WebLoggerContextUtils.getWebLifeCycle(servletContext);
+ initializer.start();
+ initializer.setLoggerContext();
+ return initializer;
+ }
+
}
diff --git a/mvnw b/mvnw
index f5ef4d1..41c0f0c 100755
--- a/mvnw
+++ b/mvnw
@@ -1,4 +1,4 @@
-#!/bin/bash
+#!/bin/sh
# ----------------------------------------------------------------------------
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
@@ -19,7 +19,7 @@
# ----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
-# Maven2 Start Up Batch script
+# Maven Start Up Batch script
#
# Required ENV vars:
# ------------------
@@ -54,38 +54,16 @@
CYGWIN*) cygwin=true ;;
MINGW*) mingw=true;;
Darwin*) darwin=true
- #
- # Look for the Apple JDKs first to preserve the existing behaviour, and then look
- # for the new JDKs provided by Oracle.
- #
- if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK ] ; then
- #
- # Apple JDKs
- #
- export JAVA_HOME=/System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK/Home
- fi
-
- if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Java/JavaVirtualMachines/CurrentJDK ] ; then
- #
- # Apple JDKs
- #
- export JAVA_HOME=/System/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home
- fi
-
- if [ -z "$JAVA_HOME" ] && [ -L "/Library/Java/JavaVirtualMachines/CurrentJDK" ] ; then
- #
- # Oracle JDKs
- #
- export JAVA_HOME=/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home
- fi
-
- if [ -z "$JAVA_HOME" ] && [ -x "/usr/libexec/java_home" ]; then
- #
- # Apple JDKs
- #
- export JAVA_HOME=`/usr/libexec/java_home`
- fi
- ;;
+ # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
+ # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
+ if [ -z "$JAVA_HOME" ]; then
+ if [ -x "/usr/libexec/java_home" ]; then
+ export JAVA_HOME="`/usr/libexec/java_home`"
+ else
+ export JAVA_HOME="/Library/Java/Home"
+ fi
+ fi
+ ;;
esac
if [ -z "$JAVA_HOME" ] ; then
@@ -130,13 +108,12 @@
CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
fi
-# For Migwn, ensure paths are in UNIX format before anything is touched
+# For Mingw, ensure paths are in UNIX format before anything is touched
if $mingw ; then
[ -n "$M2_HOME" ] &&
M2_HOME="`(cd "$M2_HOME"; pwd)`"
[ -n "$JAVA_HOME" ] &&
JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
- # TODO classpath?
fi
if [ -z "$JAVA_HOME" ]; then
@@ -187,14 +164,25 @@
# traverses directory structure from process work directory to filesystem root
# first directory with .mvn subdirectory is considered project base directory
find_maven_basedir() {
- local basedir=$(pwd)
- local wdir=$(pwd)
+
+ if [ -z "$1" ]
+ then
+ echo "Path not specified to find_maven_basedir"
+ return 1
+ fi
+
+ basedir="$1"
+ wdir="$1"
while [ "$wdir" != '/' ] ; do
if [ -d "$wdir"/.mvn ] ; then
basedir=$wdir
break
fi
- wdir=$(cd "$wdir/.."; pwd)
+ # workaround for JBEAP-8937 (on Solaris 10/Sparc)
+ if [ -d "${wdir}" ]; then
+ wdir=`cd "$wdir/.."; pwd`
+ fi
+ # end of workaround
done
echo "${basedir}"
}
@@ -206,7 +194,94 @@
fi
}
-export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-$(find_maven_basedir)}
+BASE_DIR=`find_maven_basedir "$(pwd)"`
+if [ -z "$BASE_DIR" ]; then
+ exit 1;
+fi
+
+##########################################################################################
+# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+# This allows using the maven wrapper in projects that prohibit checking in binary data.
+##########################################################################################
+if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found .mvn/wrapper/maven-wrapper.jar"
+ fi
+else
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
+ fi
+ if [ -n "$MVNW_REPOURL" ]; then
+ jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+ else
+ jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+ fi
+ while IFS="=" read key value; do
+ case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
+ esac
+ done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Downloading from: $jarUrl"
+ fi
+ wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
+ if $cygwin; then
+ wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"`
+ fi
+
+ if command -v wget > /dev/null; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found wget ... using wget"
+ fi
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ wget "$jarUrl" -O "$wrapperJarPath"
+ else
+ wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath"
+ fi
+ elif command -v curl > /dev/null; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found curl ... using curl"
+ fi
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ curl -o "$wrapperJarPath" "$jarUrl" -f
+ else
+ curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
+ fi
+
+ else
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Falling back to using Java to download"
+ fi
+ javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
+ # For Cygwin, switch paths to Windows format before running javac
+ if $cygwin; then
+ javaClass=`cygpath --path --windows "$javaClass"`
+ fi
+ if [ -e "$javaClass" ]; then
+ if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo " - Compiling MavenWrapperDownloader.java ..."
+ fi
+ # Compiling the Java class
+ ("$JAVA_HOME/bin/javac" "$javaClass")
+ fi
+ if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+ # Running the downloader
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo " - Running MavenWrapperDownloader.java ..."
+ fi
+ ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
+ fi
+ fi
+ fi
+fi
+##########################################################################################
+# End of extension
+##########################################################################################
+
+export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
+if [ "$MVNW_VERBOSE" = true ]; then
+ echo $MAVEN_PROJECTBASEDIR
+fi
MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
# For Cygwin, switch paths to Windows format before running java
@@ -228,7 +303,6 @@
WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
-# avoid using MAVEN_CMD_LINE_ARGS below since that would loose parameter escaping in $@
exec "$JAVACMD" \
$MAVEN_OPTS \
-classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
diff --git a/mvnw.cmd b/mvnw.cmd
index dcb298e..8611571 100644
--- a/mvnw.cmd
+++ b/mvnw.cmd
@@ -18,7 +18,7 @@
@REM ----------------------------------------------------------------------------
@REM ----------------------------------------------------------------------------
-@REM Maven2 Start Up Batch script
+@REM Maven Start Up Batch script
@REM
@REM Required ENV vars:
@REM JAVA_HOME - location of a JDK home dir
@@ -26,7 +26,7 @@
@REM Optional ENV vars
@REM M2_HOME - location of maven2's installed home dir
@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
-@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending
+@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
@REM e.g. to debug Maven itself, use
@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
@@ -35,7 +35,9 @@
@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
@echo off
-@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on'
+@REM set title of command window
+title %0
+@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
@REM set %HOME% to equivalent of $HOME
@@ -80,8 +82,6 @@
:init
-set MAVEN_CMD_LINE_ARGS=%MAVEN_CONFIG% %*
-
@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
@REM Fallback to current working directory if not found.
@@ -117,12 +117,48 @@
:endReadAdditionalConfig
SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
-
-set WRAPPER_JAR=""%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar""
+set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
-@REM avoid using MAVEN_CMD_LINE_ARGS below since that would lose parameter escaping in %*
-"%MAVEN_JAVA_EXE%" %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath "%WRAPPER_JAR%" "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
+set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+
+FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
+ IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
+)
+
+@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
+if exist %WRAPPER_JAR% (
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Found %WRAPPER_JAR%
+ )
+) else (
+ if not "%MVNW_REPOURL%" == "" (
+ SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+ )
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Couldn't find %WRAPPER_JAR%, downloading it ...
+ echo Downloading from: %DOWNLOAD_URL%
+ )
+
+ powershell -Command "&{"^
+ "$webclient = new-object System.Net.WebClient;"^
+ "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
+ "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
+ "}"^
+ "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^
+ "}"
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Finished downloading %WRAPPER_JAR%
+ )
+)
+@REM End of extension
+
+@REM Provide a "standardized" way to retrieve the CLI args that will
+@REM work with both Windows and non-Windows executions.
+set MAVEN_CMD_LINE_ARGS=%*
+
+%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
if ERRORLEVEL 1 goto error
goto end
diff --git a/pom.xml b/pom.xml
index d3b31ab..55b372e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -24,11 +24,9 @@
<parent>
<groupId>org.apache.logging</groupId>
<artifactId>logging-parent</artifactId>
- <version>1</version>
+ <version>2</version>
+ <relativePath/>
</parent>
- <prerequisites>
- <maven>3.0.5</maven>
- </prerequisites>
<description>Apache Log4j 2</description>
<url>https://logging.apache.org/log4j/2.x/</url>
<issueManagement>
@@ -173,9 +171,9 @@
</mailingList>
</mailingLists>
<scm>
- <connection>scm:git:https://git-wip-us.apache.org/repos/asf/logging-log4j2.git</connection>
- <developerConnection>scm:git:https://git-wip-us.apache.org/repos/asf/logging-log4j2.git</developerConnection>
- <url>https://git-wip-us.apache.org/repos/asf?p=logging-log4j2.git;a=summary</url>
+ <connection>scm:git:https://gitbox.apache.org/repos/asf/logging-log4j2.git</connection>
+ <developerConnection>scm:git:https://gitbox.apache.org/repos/asf/logging-log4j2.git</developerConnection>
+ <url>https://gitbox.apache.org/repos/asf?p=logging-log4j2.git</url>
<tag>log4j-${Log4jReleaseVersion}</tag>
</scm>
<properties>
@@ -190,19 +188,22 @@
<slf4jVersion>1.7.25</slf4jVersion>
<logbackVersion>1.2.3</logbackVersion>
<jackson1Version>1.9.13</jackson1Version>
- <jackson2Version>2.9.6</jackson2Version>
+ <jackson2Version>2.11.1</jackson2Version>
<springVersion>3.2.18.RELEASE</springVersion>
- <flumeVersion>1.8.0</flumeVersion>
+ <kubernetes-client.version>4.6.1</kubernetes-client.version>
+ <flumeVersion>1.9.0</flumeVersion>
<disruptorVersion>3.4.2</disruptorVersion>
- <conversantDisruptorVersion>1.2.13</conversantDisruptorVersion>
- <mongodb2.version>2.14.3</mongodb2.version>
- <mongodb3.version>3.8.0</mongodb3.version>
- <compiler.plugin.version>3.7.0</compiler.plugin.version>
+ <conversantDisruptorVersion>1.2.15</conversantDisruptorVersion>
+ <elastic.version>7.6.2</elastic.version>
+ <mongodb3.version>3.12.4</mongodb3.version>
+ <mongodb4.version>4.0.3</mongodb4.version>
+ <groovy.version>2.5.6</groovy.version>
+ <compiler.plugin.version>3.8.1</compiler.plugin.version>
<pmd.plugin.version>3.10.0</pmd.plugin.version>
<spotbugs.plugin.version>3.1.5</spotbugs.plugin.version>
- <spotbugs.version>3.1.6</spotbugs.version>
+ <spotbugs.version>3.1.7</spotbugs.version>
<changes.plugin.version>2.12.1</changes.plugin.version>
- <javadoc.plugin.version>3.0.1</javadoc.plugin.version>
+ <javadoc.plugin.version>3.2.0</javadoc.plugin.version>
<!-- surefire.plugin.version 2.18 yields http://jira.codehaus.org/browse/SUREFIRE-1121, which is fixed in 2.18.1 -->
<!-- surefire.plugin.version 2.19 yields https://issues.apache.org/jira/browse/SUREFIRE-1193. -->
<!-- all versions after 2.13 yield https://issues.apache.org/jira/browse/SUREFIRE-720 -->
@@ -210,38 +211,39 @@
<failsafe.plugin.version>2.21.0</failsafe.plugin.version>
<checkstyle.plugin.version>3.0.0</checkstyle.plugin.version>
<deploy.plugin.version>2.8.2</deploy.plugin.version>
- <rat.plugin.version>0.12</rat.plugin.version>
+ <rat.plugin.version>0.13</rat.plugin.version>
<pdf.plugin.version>1.2</pdf.plugin.version>
<cobertura.plugin.version>2.7</cobertura.plugin.version>
- <jacoco.plugin.version>0.8.1</jacoco.plugin.version>
+ <jacoco.plugin.version>0.8.5</jacoco.plugin.version>
<release.plugin.version>2.5.3</release.plugin.version>
<scm.plugin.version>1.9.5</scm.plugin.version>
<jxr.plugin.version>2.5</jxr.plugin.version>
- <clirr.plugin.version>2.8</clirr.plugin.version>
- <!-- Maven site 3.7 uses the wrong stylesheet? -->
- <site.plugin.version>3.4</site.plugin.version>
+ <revapi.plugin.version>0.10.5</revapi.plugin.version>
+ <revapi.skip>true</revapi.skip>
+ <site.plugin.version>3.8.2</site.plugin.version>
<!-- Maven site depends on Velocity and the escaping rules are different in newer versions. -->
<!-- See https://maven.apache.org/plugins/maven-site-plugin/migrate.html -->
<velocity.plugin.version>1.5</velocity.plugin.version>
<asciidoc.plugin.version>1.5.6</asciidoc.plugin.version>
- <errorprone.version>2.3.0</errorprone.version>
- <plexus.errorprone.version>2.8.4</plexus.errorprone.version>
+ <errorprone.version>2.3.2</errorprone.version>
+ <plexus.errorprone.version>2.8.5</plexus.errorprone.version>
<remote.resources.plugin.version>1.5</remote.resources.plugin.version>
<manifestfile>${project.build.outputDirectory}/META-INF/MANIFEST.MF</manifestfile>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<docLabel>Site Documentation</docLabel>
<projectDir />
<commonsLoggingVersion>1.2</commonsLoggingVersion>
- <javax.persistence>2.2.0</javax.persistence>
+ <javax.persistence>2.2.1</javax.persistence>
<osgi.api.version>6.0.0</osgi.api.version>
- <activemq.version>5.15.4</activemq.version>
+ <activemq.version>5.15.9</activemq.version>
<!-- Allow Clirr severity to be overriden by the command-line option -DminSeverity=level -->
<minSeverity>info</minSeverity>
<jctoolsVersion>1.2.1</jctoolsVersion>
- <mockitoVersion>2.13.0</mockitoVersion>
+ <mockitoVersion>2.25.1</mockitoVersion>
<argLine>-Xms256m -Xmx1024m</argLine>
- <javaTargetVersion>1.7</javaTargetVersion>
+ <javaTargetVersion>1.8</javaTargetVersion>
<module.name />
</properties>
<pluginRepositories>
@@ -296,17 +298,17 @@
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-core</artifactId>
- <version>3.5.4</version>
+ <version>3.6.0</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
- <version>1.11</version>
+ <version>1.12</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
- <version>3.7</version>
+ <version>3.9</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
@@ -334,6 +336,29 @@
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-plugins-java9</artifactId>
+ <version>${project.version}</version>
+ <type>zip</type>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-plugins</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-1.2-api</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-plugins</artifactId>
+ <version>${project.version}</version>
+ <type>test-jar</type>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>${project.version}</version>
<type>test-jar</type>
@@ -359,6 +384,12 @@
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-layout-json-template</artifactId>
+ <version>${project.version}</version>
+ <type>test-jar</type>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-slf4j-impl</artifactId>
<version>${project.version}</version>
</dependency>
@@ -385,11 +416,6 @@
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
- <artifactId>log4j-1.2-api</artifactId>
- <version>${project.version}</version>
- </dependency>
- <dependency>
- <groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-flume-ng</artifactId>
<version>${project.version}</version>
</dependency>
@@ -405,6 +431,11 @@
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-jpl</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-taglib</artifactId>
<version>${project.version}</version>
</dependency>
@@ -611,7 +642,7 @@
<dependency>
<groupId>com.sun.mail</groupId>
<artifactId>javax.mail</artifactId>
- <version>1.6.1</version>
+ <version>1.6.2</version>
</dependency>
<dependency>
<groupId>org.jboss.spec.javax.jms</groupId>
@@ -662,15 +693,27 @@
<scope>test</scope>
</dependency>
<dependency>
+ <groupId>org.assertj</groupId>
+ <artifactId>assertj-core</artifactId>
+ <version>3.14.0</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-all</artifactId>
<version>1.3</version>
<scope>test</scope>
</dependency>
<dependency>
+ <groupId>org.awaitility</groupId>
+ <artifactId>awaitility</artifactId>
+ <version>4.0.2</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
<groupId>org.codehaus.plexus</groupId>
<artifactId>plexus-utils</artifactId>
- <version>3.1.0</version>
+ <version>3.2.0</version>
<scope>test</scope>
</dependency>
<dependency>
@@ -725,6 +768,11 @@
<version>${springVersion}</version>
</dependency>
<dependency>
+ <groupId>io.fabric8</groupId>
+ <artifactId>kubernetes-client</artifactId>
+ <version>${kubernetes-client.version}</version>
+ </dependency>
+ <dependency>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<version>2.4.1</version>
@@ -732,12 +780,12 @@
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
- <version>1.4.197</version>
+ <version>1.4.199</version>
</dependency>
<dependency>
<groupId>org.eclipse.persistence</groupId>
<artifactId>org.eclipse.persistence.jpa</artifactId>
- <version>2.7.2</version>
+ <version>2.7.4</version>
</dependency>
<dependency>
<groupId>org.eclipse.persistence</groupId>
@@ -746,21 +794,6 @@
<scope>provided</scope>
</dependency>
<dependency>
- <groupId>org.mongodb</groupId>
- <artifactId>mongo-java-driver</artifactId>
- <version>${mongodb2.version}</version>
- </dependency>
- <dependency>
- <groupId>org.mongodb</groupId>
- <artifactId>mongodb-driver</artifactId>
- <version>${mongodb3.version}</version>
- </dependency>
- <dependency>
- <groupId>org.mongodb</groupId>
- <artifactId>bson</artifactId>
- <version>${mongodb3.version}</version>
- </dependency>
- <dependency>
<groupId>org.lightcouch</groupId>
<artifactId>lightcouch</artifactId>
<version>0.0.6</version>
@@ -784,13 +817,13 @@
<dependency>
<groupId>org.xmlunit</groupId>
<artifactId>xmlunit-core</artifactId>
- <version>2.6.0</version>
+ <version>2.6.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.xmlunit</groupId>
<artifactId>xmlunit-matchers</artifactId>
- <version>2.6.0</version>
+ <version>2.6.2</version>
<scope>test</scope>
</dependency>
<dependency>
@@ -799,18 +832,29 @@
<version>2.6</version>
<scope>test</scope>
</dependency>
+ <!-- Used for testing JsonTemplateLayout -->
+ <dependency>
+ <groupId>co.elastic.logging</groupId>
+ <artifactId>log4j2-ecs-layout</artifactId>
+ <version>0.4.0</version>
+ </dependency>
+ <dependency>
+ <groupId>org.elasticsearch.client</groupId>
+ <artifactId>elasticsearch-rest-high-level-client</artifactId>
+ <version>${elastic.version}</version>
+ </dependency>
<!-- Used for testing HttpAppender -->
<dependency>
<groupId>com.github.tomakehurst</groupId>
<artifactId>wiremock</artifactId>
<scope>test</scope>
- <version>2.18.0</version>
+ <version>2.19.0</version>
</dependency>
<!-- Used for compressing to formats other than zip and gz -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-compress</artifactId>
- <version>1.17</version>
+ <version>1.18</version>
</dependency>
<dependency>
<groupId>org.tukaani</groupId>
@@ -822,7 +866,7 @@
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-csv</artifactId>
- <version>1.5</version>
+ <version>1.6</version>
</dependency>
<!-- GC-free -->
<dependency>
@@ -843,18 +887,18 @@
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-jsr223</artifactId>
- <version>2.5.1</version>
+ <version>${groovy.version}</version>
</dependency>
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-dateutil</artifactId>
- <version>2.5.1</version>
+ <version>${groovy.version}</version>
</dependency>
<dependency>
<!-- Testing MongoDB -->
<groupId>de.flapdoodle.embed</groupId>
<artifactId>de.flapdoodle.embed.mongo</artifactId>
- <version>2.1.1</version>
+ <version>2.2.0</version>
<scope>test</scope>
</dependency>
</dependencies>
@@ -933,6 +977,7 @@
</compilerArguments>
<compilerId>javac-with-errorprone</compilerId>
<forceJavacCompilerUse>true</forceJavacCompilerUse>
+ <parameters>true</parameters>
</configuration>
<dependencies>
<dependency>
@@ -978,14 +1023,6 @@
<version>${jxr.plugin.version}</version>
</plugin>
<plugin>
- <groupId>org.codehaus.mojo</groupId>
- <artifactId>clirr-maven-plugin</artifactId>
- <version>${clirr.plugin.version}</version>
- <configuration>
- <minSeverity>${minSeverity}</minSeverity>
- </configuration>
- </plugin>
- <plugin>
<groupId>org.eluder.coveralls</groupId>
<artifactId>coveralls-maven-plugin</artifactId>
<version>4.3.0</version>
@@ -1058,6 +1095,11 @@
</execution>
</executions>
</plugin>
+ <plugin>
+ <groupId>io.fabric8</groupId>
+ <artifactId>docker-maven-plugin</artifactId>
+ <version>0.33.0</version>
+ </plugin>
</plugins>
</pluginManagement>
<plugins>
@@ -1133,16 +1175,29 @@
</configuration>
</plugin>
<plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>build-helper-maven-plugin</artifactId>
+ <version>1.12</version>
+ <executions>
+ <execution>
+ <id>timestamp-property</id>
+ <goals>
+ <goal>timestamp-property</goal>
+ </goals>
+ <phase>pre-site</phase>
+ <configuration>
+ <name>currentYear</name>
+ <pattern>yyyy</pattern>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ <plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-site-plugin</artifactId>
<version>${site.plugin.version}</version>
<dependencies>
<dependency>
- <groupId>org.apache.velocity</groupId>
- <artifactId>velocity</artifactId>
- <version>${velocity.plugin.version}</version>
- </dependency>
- <dependency>
<groupId>org.apache.maven.wagon</groupId>
<artifactId>wagon-ssh</artifactId>
<version>3.1.0</version>
@@ -1162,8 +1217,6 @@
<moduleExcludes>
<xdoc>navigation.xml,changes.xml</xdoc>
</moduleExcludes>
- <templateDirectory>${log4jParentDir}/src/site</templateDirectory>
- <template>site.vm</template>
<asciidoc>
<attributes>
<!-- copy any site properties wanted in asciidoc files -->
@@ -1252,6 +1305,13 @@
<!-- Other -->
<exclude>felix-cache/**</exclude>
<exclude>RELEASE-NOTES.md</exclude>
+ <exclude>**/*.yml</exclude>
+ <exclude>**/*.yaml</exclude>
+ <exclude>**/*.json</exclude>
+ <excllude>**/images/*.drawio</excllude>
+ <exclude>**/fluent-bit.conf</exclude>
+ <exclude>**/rabbitmq.config</exclude>
+ <exclude>**/MANIFEST.MF</exclude>
</excludes>
</configuration>
</plugin>
@@ -1283,6 +1343,62 @@
</execution>
</executions>
</plugin>
+ <plugin>
+ <groupId>org.revapi</groupId>
+ <artifactId>revapi-maven-plugin</artifactId>
+ <version>${revapi.plugin.version}</version>
+ <dependencies>
+ <dependency>
+ <groupId>org.revapi</groupId>
+ <artifactId>revapi-java</artifactId>
+ <version>0.18.2</version>
+ </dependency>
+ </dependencies>
+ <executions>
+ <execution>
+ <goals><goal>check</goal></goals>
+ <configuration>
+ <checkDependencies>false</checkDependencies>
+ <skip>${revapi.skip}</skip>
+ <failOnMissingConfigurationFiles>false</failOnMissingConfigurationFiles>
+ <analysisConfigurationFiles>
+ <path>revapi.json</path>
+ </analysisConfigurationFiles>
+ <analysisConfiguration><![CDATA[
+[
+ {
+ "extension": "revapi.java",
+ "configuration": {
+ "missing-classes": {
+ "behavior": "report",
+ "ignoreMissingAnnotations": false
+ },
+ "reportUsesFor": [
+ "java.missing.newClass",
+ "java.class.nonPublicPartOfAPI"
+ ],
+ "filter": {
+ "classes": {
+ "regex": true,
+ "include": [
+ "org\\.apache\\.logging\\.log4j(\\..+)?"
+ ]
+ },
+ "packages": {
+ "regex": true,
+ "include": [
+ "org\\.apache\\.logging\\.log4j(\\..+)?"
+ ]
+ }
+ }
+ }
+ }
+]
+ ]]></analysisConfiguration>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
</plugins>
</build>
<reporting>
@@ -1379,9 +1495,28 @@
<!-- Other -->
<exclude>felix-cache/**</exclude>
<exclude>RELEASE-NOTES.txt</exclude>
+ <exclude>**/*.yml</exclude>
+ <exclude>**/*.yaml</exclude>
+ <exclude>**/*.json</exclude>
+ <excllude>**/images/*.drawio</excllude>
+ <exclude>**/fluent-bit.conf</exclude>
+ <exclude>**/rabbitmq.config</exclude>
+ <exclude>**/MANIFEST.MF</exclude>
</excludes>
</configuration>
</plugin>
+ <plugin>
+ <groupId>org.revapi</groupId>
+ <artifactId>revapi-maven-plugin</artifactId>
+ <version>${revapi.plugin.version}</version>
+ <reportSets>
+ <reportSet>
+ <reports>
+ <report>report</report>
+ </reports>
+ </reportSet>
+ </reportSets>
+ </plugin>
</plugins>
</reporting>
<distributionManagement>
@@ -1395,12 +1530,15 @@
<modules>
<module>log4j-api-java9</module>
<module>log4j-api</module>
+ <module>log4j-plugins-java9</module>
+ <module>log4j-plugins</module>
<module>log4j-core-java9</module>
<module>log4j-core</module>
<module>log4j-layout-jackson</module>
<module>log4j-layout-jackson-json</module>
<module>log4j-layout-jackson-xml</module>
<module>log4j-layout-jackson-yaml</module>
+ <module>log4j-layout-json-template</module>
<module>log4j-core-its</module>
<module>log4j-1.2-api</module>
<module>log4j-slf4j-impl</module>
@@ -1421,17 +1559,21 @@
<module>log4j-kafka</module>
<module>log4j-redis</module>
<module>log4j-couchdb</module>
- <module>log4j-mongodb2</module>
<module>log4j-mongodb3</module>
+ <module>log4j-mongodb4</module>
<module>log4j-cassandra</module>
<module>log4j-web</module>
<module>log4j-perf</module>
<module>log4j-iostreams</module>
<module>log4j-jul</module>
+ <module>log4j-jpl</module>
<module>log4j-liquibase</module>
<module>log4j-appserver</module>
<module>log4j-smtp</module>
<module>log4j-osgi</module>
+ <module>log4j-docker</module>
+ <module>log4j-kubernetes</module>
+ <module>log4j-spring-cloud-config</module>
</modules>
<profiles>
<profile>
@@ -1550,6 +1692,13 @@
<!-- Other -->
<exclude>felix-cache/**</exclude>
<exclude>RELEASE-NOTES.md</exclude>
+ <exclude>**/*.yml</exclude>
+ <exclude>**/*.yaml</exclude>
+ <exclude>**/*.json</exclude>
+ <excllude>**/images/*.drawio</excllude>
+ <exclude>**/fluent-bit.conf</exclude>
+ <exclude>**/rabbitmq.config</exclude>
+ <exclude>**/MANIFEST.MF</exclude>
</excludes>
</configuration>
<executions>
@@ -1608,59 +1757,6 @@
</build>
</profile>
<profile>
- <id>jdk7</id>
- <activation>
- <jdk>1.7</jdk>
- </activation>
- <build>
- <plugins>
- <plugin>
- <artifactId>maven-surefire-plugin</artifactId>
- <configuration>
- <argLine>-XX:MaxPermSize=512m</argLine>
- </configuration>
- </plugin>
- <plugin>
- <artifactId>maven-failsafe-plugin</artifactId>
- <configuration>
- <argLine>-XX:MaxPermSize=512m</argLine>
- </configuration>
- </plugin>
- </plugins>
- </build>
- </profile>
- <profile>
- <id>useJava7</id>
- <activation>
- <property>
- <name>useJava7</name>
- </property>
- </activation>
- <build>
- <plugins>
- <plugin>
- <groupId>org.apache.maven.plugins</groupId>
- <artifactId>maven-toolchains-plugin</artifactId>
- <version>1.1</version>
- <executions>
- <execution>
- <goals>
- <goal>toolchain</goal>
- </goals>
- </execution>
- </executions>
- <configuration>
- <toolchains>
- <jdk>
- <version>1.7</version>
- </jdk>
- </toolchains>
- </configuration>
- </plugin>
- </plugins>
- </build>
- </profile>
- <profile>
<id>java8-doclint-disabled</id>
<activation>
<jdk>[1.8,)</jdk>
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 088372d..f6e7e53 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -30,7 +30,50 @@
- "update" - Change
- "remove" - Removed
-->
- <release version="3.0.0" date="2018-xx-xx" description="GA Release 3.0.0">
+ <release version="3.0.0" date="2019-xx-xx" description="GA Release 3.0.0">
+ <action issue="LOG4J2-2749" dev="vy" type="fix" due-to="Oleksii Khomchenko">
+ JsonLayout KeyValuePair should discard blank JSON keys.
+ </action>
+ <action issue="LOG4J2-2344" dev="vy" type="fix" due-to="dengliming">
+ Fix exception message in PropertiesConfigurationBuilder#createFilter().
+ </action>
+ <action issue="LOG4J2-2795" dev="rgoers" type="fix">
+ Reduce Log4j 2 initialization time by deferring loading Plugin classes.
+ </action>
+ <action issue="LOG4J2-2678" dev="rgoers" type="update" due-to="Federico D'Ambrosio">
+ Add LogEvent timestamp to ProducerRecord in KafkaAppender.
+ </action>
+ <actino issue="LOG4J2-2688" dev="rgoers" type="add" due-to="Romain Manni-Bucau">
+ Allow web lookup of session attributes.
+ </actino>
+ <action issue="LOG4J2-2701" dev="rgoers" type="update">
+ Update Jackson from 2.9.x to 2.10.1.
+ </action>
+ <action issue="LOG4J2-2700" dev="mattsicker" type="add">
+ Add support for injecting plugin configuration via builder methods.
+ </action>
+ <action issue="LOG4J2-860" dev="mattsicker" type="update">
+ Unify plugin builders and plugin factories.
+ </action>
+ <action issue="LOG4J2-2690" dev="rgoers" type="update">
+ Locate plugins in modules.
+ </action>
+ <action issue="LOG4J2-2617" dev="mattsicker" type="update">
+ Fix typo in method MergeStrategy.mergeConfigurations.
+ </action>
+ <action issue="LOG4J2-2683" dev="mattsicker" type="update">
+ Rename PluginVisitor and related classes to ConfigurationInjectionBuilder.
+ </action>
+ <action issue="LOG4J2-2523" dev="rgoers" type="update" due-to="Romain Manni-Bucau">
+ Allow web lookup to access more information.
+ </action>
+ <action issue="LOG4J2-2621" dev="rgoers" type="update">
+ Separate plugin support to its own module. Plugin annotation processor will now generate a Java source
+ file compatible with java.util.ServiceLoader instead of a binary file.
+ </action>
+ <action issue="LOG4J2-2025" dev="rgoers" type="add" due-to="Thies Wellpott">
+ Implement JUL Bridge Handler.
+ </action>
<action issue="LOG4J2-2171" dev="rmannibucau" type="add">
Allow to force LOG4J2 to use TCCL only.
</action>
@@ -49,172 +92,51 @@
<action issue="LOG4J2-2225" dev="rpopma" type="update">
Moved time-related classes from core.util to core.time. Classes considered private moved to core.time.internal.
</action>
- <action issue="LOG4J2-2228" dev="ggregory" type="update" due-to="Gary Gregory">
+ <action issue="LOG4J2-2228" dev="ggregory" type="update">
Split off ZeroMq/JeroMq support into a new module log4j-jeromq.
</action>
- <action issue="LOG4J2-2227" dev="ggregory" type="update" due-to="Gary Gregory">
+ <action issue="LOG4J2-2227" dev="ggregory" type="update">
Split off Kafka support into a new module log4j-kafka.
</action>
- <action issue="LOG4J2-2230" dev="ggregory" type="update" due-to="Gary Gregory">
+ <action issue="LOG4J2-2230" dev="ggregory" type="update">
Split off SMTP support into a new module log4j-smtp.
</action>
- <action issue="LOG4J2-2231" dev="ggregory" type="update" due-to="Gary Gregory">
+ <action issue="LOG4J2-2231" dev="ggregory" type="update">
Move CSV layout from log4j-core to a new module log4j-csv.
</action>
- <action issue="LOG4J2-2232" dev="ggregory" type="update" due-to="Gary Gregory">
+ <action issue="LOG4J2-2232" dev="ggregory" type="update">
Move JMS code to a new module log4j-jms.
</action>
- <action issue="LOG4J2-2233" dev="ggregory" type="update" due-to="Gary Gregory">
+ <action issue="LOG4J2-2233" dev="ggregory" type="update">
Move JDBC code to a new module log4j-jdbc.
</action>
- <action issue="LOG4J2-2244" dev="ggregory" type="fix" due-to="Gary Gregory">
+ <action issue="LOG4J2-2244" dev="ggregory" type="fix">
org.apache.logging.log4j.core.lookup.EnvironmentLookup may throw NPE.
</action>
- <action issue="LOG4J2-2237" dev="ggregory" type="update" due-to="Gary Gregory">
+ <action issue="LOG4J2-2237" dev="ggregory" type="update">
Move Jackson-based layouts to their own modules: JSON, XML, and YAML.
</action>
- <action issue="LOG4J2-2082" dev="ggregory" type="update" due-to="Gary Gregory">
+ <action issue="LOG4J2-2082" dev="ggregory" type="update">
Update Apache Flume from 1.7.0 to 1.8.0.
</action>
- <action issue="LOG4J2-2079" dev="ggregory" type="update" due-to="Gary Gregory">
+ <action issue="LOG4J2-2079" dev="ggregory" type="update" >
Update Conversant Disruptor from 1.12.10 to 1.12.11.
</action>
- <action issue="LOG4J2-2258" dev="ggregory" type="update" due-to="Gary Gregory">
+ <action issue="LOG4J2-2258" dev="ggregory" type="update" >
Update LMAX Disruptor from 3.3.7 to 3.3.8.
</action>
- <action issue="LOG4J2-2083" dev="ggregory" type="update" due-to="Gary Gregory">
+ <action issue="LOG4J2-2083" dev="ggregory" type="update">
Update Eclipse javax.persistence from 2.1.1 to 2.2.0.
</action>
<action issue="LOG4J2-2083" dev="ggregory" type="update">
Update build to expect Java 8 sources and generate Java 8 byte codes.
</action>
- <action issue="LOG4J2-2259" dev="ggregory" type="update">
- Update MongoDB 3 module from driver 3.6.1 to 3.6.3.
- </action>
- <action issue="LOG4J2-2260" dev="ggregory" type="update">
- [SMTP] Update javax.mail from 1.6.0 to 1.6.1.
- </action>
- <action issue="LOG4J2-2270" dev="ggregory" type="fix" due-to="Cyril Martin">
- Strings::join, when called with [null] returns "null" instead of EMPTY.
- </action>
- <action issue="LOG4J2-2276" dev="ggregory" type="fix" due-to="Sean Baxter">
- ConcurrentModificationException from org.apache.logging.log4j.status.StatusLogger.<clinit>(StatusLogger.java:71).
- </action>
- <action issue="LOG4J2-2274" dev="ggregory" type="fix" due-to="Sebastien Lannez">
- Allow EnvironmentPropertySource to run with a SecurityManager that rejects environment variable access.
- </action>
- <action issue="LOG4J2-2279" dev="ggregory" type="fix" due-to="Gary Gregory">
- Allow SystemPropertiesPropertySource to run with a SecurityManager that rejects system property access.
- </action>
<action issue="LOG4J2-2279" dev="ggregory" type="fix" due-to="Remko Popma">
Move ProcessIdUtil from log4j-api to log4j-core.
</action>
- <action issue="LOG4J2-2283" dev="ggregory" type="fix" due-to="Vishnu Priya Matha">
- ParserConfigurationException when using Log4j with oracle.xml.jaxp.JXDocumentBuilderFactory.
- </action>
- <action issue="LOG4J2-2300" dev="ggregory" type="fix">
- PoolingDriverConnectionSource does not take into account properties, user name, and password.
- </action>
- <action issue="LOG4J2-2302" dev="ggregory" type="update">
- Status logger should show the Log4j name and version when initializing itself.
- </action>
- <action issue="LOG4J2-2304" dev="ggregory" type="update" due-to="wumengsheng">
- Log4j2 2.8.2 JMX unregister NullPointerException.
- </action>
<action issue="LOG4J2-2306" dev="ckozak" type="fix">
FixedDateFormat parses timezone offsets, -8:00 is interpreted as GMT-8:00.
</action>
- <action issue="LOG4J2-2307" dev="ckozak" type="fix">
- MutableLogEvent and RingBufferLogEvent message mementos retain the original format string.
- </action>
- <action issue="LOG4J2-2032" dev="ckozak" type="fix" due-to="Kostiantyn Shchepanovskyi">
- Curly braces in parameters are not treated as placeholders.
- </action>
- <action issue="LOG4J2-2311" dev="ggregory" type="update">
- Update Jackson from 2.9.4 to 2.9.5.
- </action>
- <action issue="LOG4J2-2313" dev="ggregory" type="update">
- Update LMAX Disruptor from 3.3.8 to 3.4.2.
- </action>
- <action issue="LOG4J2-2317" dev="ckozak" type="fix">
- MutableLogEvent.getNonNullImmutableMessage and Log4jLogEvent.makeMessageImmutable retain format and parameters.
- </action>
- <action issue="LOG4J2-2318" dev="ckozak" type="fix">
- Messages are no longer mutated when the asynchronous queue is full. A warning is logged to the status logger instead.
- </action>
- <action issue="LOG4J2-2320" dev="ckozak" type="fix">
- Fix NPE in AbstractLogger when another exception is thrown, masking the root cause.
- </action>
- <action issue="LOG4J2-548" dev="ggregory" type="update" due-to="Shehata, Paresh Varke, Eric Victorson, Martin Laforet">
- Log4j 2.0 ERROR "Could not search jar" with JBoss EAP 6.2.
- </action>
- <action issue="LOG4J2-2321" dev="ckozak" type="fix">
- AsyncLogger uses the correct level when unspecified. This provides parity between AsyncLogger and Logger.
- </action>
- <action issue="LOG4J2-2322" dev="ckozak" type="fix">
- Custom ContextSelector implementations which select an AsyncLoggerContext disable LoggerConfig.includeLocation
- by default for parity with AsyncLoggerContextSelector.
- </action>
- <action issue="LOG4J2-2269" dev="ckozak" type="fix">
- MutableLogEvent references to other objects are cleared after each use.
- Fix a memory leak causing references to parameters to be held after synchronous logging with thread locals enabled.
- </action>
- <action issue="LOG4J2-2328" dev="ggregory" type="update">
- Update JAnsi from 1.17 to 1.17.1.
- </action>
- <action issue="LOG4J2-2301" dev="ckozak" type="fix">
- Mixed async loggers no longer forget parameter values, providing some appenders with an array of nulls.
- </action>
- <action issue="LOG4J2-2331" dev="ckozak" type="fix" due-to="Mike Baranski">
- RollingFileManager debug logging avoids string concatenation and errant braces in favor of parameterized logging.
- </action>
- <action issue="LOG4J2-2333" dev="ckozak" type="fix">
- Handle errors thrown in default disruptor ExceptionHandler implementations to avoid killing background threads.
- </action>
- <action issue="LOG4J2-2334" dev="ggregory" type="fix">
- Add API org.apache.logging.log4j.core.appender.AsyncAppender.getQueueSize().
- </action>
- <action issue="LOG4J2-2336" dev="ckozak" type="fix">
- Remove duplicate hyphen from the AsyncLoggerConfig background thread name.
- </action>
- <action issue="LOG4J2-2347" dev="ggregory" type="fix">
- Update Apache Commons Compress from 1.16.1 to 1.17.
- </action>
- <action issue="LOG4J2-2351" dev="ckozak" type="update">
- Added AbstractLogEvent.getMutableInstant to allow the MutableInstant instance to be modified by classes extending AbstractLogEvent.
- </action>
- <action issue="LOG4J2-2352" dev="ckozak" type="fix">
- RingBufferLogEvent memento messages provide the expected format string, and no longer attempt to substitute parameters into curly braces in parameter toString values.
- Both RingBufferLogEvent and MutableLogEvent memento implementations memoize results to avoid rebuilding formatted string values.
- </action>
- <action issue="LOG4J2-2355" dev="ckozak" type="fix" due-to="Henrik Brautaset Aronsen">
- PropertiesUtil ignores non-string system properties. Fixes a NoClassDefFoundError initializing StatusLogger
- caused by an NPE while initializing the static PropertiesUtil field.
- </action>
- <action issue="LOG4J2-2357" dev="ggregory" type="update">
- Update Jackson from 2.9.5 to 2.9.6.
- </action>
- <action issue="LOG4J2-2362" dev="ckozak" type="fix">
- Fixed a memory leak in which ReusableObjectMessage would hold a reference to the most recently logged object.
- </action>
- <action issue="LOG4J2-2312" dev="ckozak" type="fix">
- Jackson layouts used with AsyncLoggerContextSelector output the expected format rather than only a json string of the message text.
- </action>
- <action issue="LOG4J2-2364" dev="ckozak" type="fix">
- Fixed a memory leak in which ReusableParameterizedMessage would hold a reference to the most recently
- logged throwable and provided varargs array.
- </action>
- <action issue="LOG4J2-2368" dev="ckozak" type="fix">
- Nested logging doesn't clobber AbstractStringLayout cached StringBuidlers
- </action>
- <action issue="LOG4J2-2373" dev="ckozak" type="fix" due-to="Kevin Meurer">
- StringBuilders.escapeJson implementation runs in linear time. Escaping large JSON strings
- in EncodingPatternConverter and MapMessage will perform significantly better.
- </action>
- <action issue="LOG4J2-2376" dev="ckozak" type="fix" due-to="Kevin Meurer">
- StringBuilders.escapeXml implementation runs in linear time. Escaping large XML strings
- in EncodingPatternConverter and MapMessage will perform significantly better.
- </action>
<action issue="LOG4J2-2380" dev="ggregory" type="update">
Update Conversant Disruptor 1.2.11 to 1.2.13.
</action>
@@ -224,25 +146,502 @@
<action issue="LOG4J2-2387" dev="ggregory" type="update">
Update optional Apache Commons DBCP from 2.4.0 to 2.5.0.
</action>
- <action issue="LOG4J2-2391" dev="ckozak" type="update">
- Improve exception logging performance. ThrowableProxy construction uses a more faster
- method to discover the current stack trace. ThrowablePatternConverter and
- ExtendedThrowablePatternConverter default configurations no longer allocate
- an additional buffer for stack trace contents.
+ <action issue="LOG4J2-2473" dev="ggregory" type="update">
+ Update org.eclipse.persistence:javax.persistence from 2.2.0 to 2.2.1.
</action>
- <action issue="LOG4J2-2397" dev="ggregory" type="fix" due-to="EckelDong">
- Predeployment of PersistenceUnit that using Log4j as session logger failed (#198).
+ <action issue="LOG4J2-2492" dev="ggregory" type="update">
+ Update builder methods from the "with" prefix to the "set" prefix.
</action>
- <action issue="LOG4J2-2365" dev="ckozak" type="fix" due-to="Eugene Zimichev">
- NameAbbreviator correctly abbreviates first fragments (#188).
+ <action issue="LOG4J2-2493" dev="ggregory" type="update">
+ Remove deprecated code.
</action>
- <action issue="LOG4J2-2201" dev="ckozak" type="fix">
- Fix memory leak in ReusableParameterizedMessage.
+ <action issue="LOG4J2-2405" dev="ggregory" type="update" due-to="Marco Herrn">
+ Better handling of %highlight pattern when using jul-bridge.
+ </action>
+ <action issue="LOG4J2-2545" dev="ckozak" type="fix">
+ RoutingAppender.BuilderlsetPurgePolicy fluently returns the builder instance.
+ </action>
+ <action dev="ggregory" type="update" due-to="Gary Gregory">
+ Update tests from H2 1.4.197 to 1.4.199.
+ </action>
+ <action issue="LOG4J2-2570" dev="ggregory" type="update">
+ Update Jackson from 2.9.7 to 2.9.8.
+ </action>
+ <action issue="LOG4J2-2571" dev="ggregory" type="update">
+ Update conversant disruptor from 1.2.13 to 1.2.15.
+ </action>
+ <action issue="LOG4J2-2572" dev="ggregory" type="update">
+ Update Apache Flume from 1.8.0 to 1.9.0.
+ </action>
+ <action issue="LOG4J2-2844" dev="ggregory" type="fix">
+ Null pointer exception when no network interfaces are available.
+ </action>
+ </release>
+ <release version="2.14.0" date="2020-MM-DD" description="GA Release 2.14.0">
+ <action issue="LOG4J2-2859" dev="rgoers" type="fix" due-to="Yanming Zhou">
+ Fixed typos where mergeFactory should be mergeStrategy.
+ </action>
+ <action issue="LOG4J2-2832" dev="rgoers" type="fix" due-to="Benjamin Asbach">
+ Correct class name printed in error message in RollingFileAppender.
+ </action>
+ <action issue="LOG4J2-2882" dev="rgoers" type="fix" due-to="Emmanuel Bourg">
+ Support java.util.logging filters when using that API.
+ </action>
+ <action issue="LOG4J2-2880" dev="rgoers" type="fix">
+ Create StackWalker benchmark. Revert back to StackWalker.walk based on benchmark results.
+ </action>
+ <action issue="LOG4J2-2867" dev="rgoers" type="fix">
+ Obtain ContextDataProviders asynchronously.
+ </action>
+ <action issue="LOG4J2-2877" dev="rgoers" type="fix">
+ Determine the container id to obtain container and image information.
+ </action>
+ <action issue="LOG4J2-2848" dev="ggregory" type="add">
+ Create module log4j-mongodb4 to use new major version 4 MongoDB driver.
+ </action>
+ <action issue="LOG4J2-2851" dev="ggregory" type="remove">
+ Drop log4j-mongodb2 module.
+ </action>
+ <action issue="LOG4J2-2895" dev="ckozak" type="fix">
+ Fix potential deadlock in asynchronous logging by avoiding blocking for queue space on Log4jThreads
+ </action>
+ <action issue="LOG4J2-2837" dev="ckozak" type="fix">
+ Disruptor and JUL no longer recursively start the AsyncLoggerDisruptor
+ resulting in an extra disruptor background thread constantly waiting.
+ </action>
+ <action issue="LOG4J2-2867" dev="ckozak" type="fix">
+ RingBufferLogEventTranslator uses a static ContextDataInjector instead of initializing a new object
+ on each thread.
+ </action>
+ <action issue="LOG4J2-2858" dev="ckozak" type="add" due-to="Stepan Gorban">
+ More flexible configuration of the Disruptor WaitStrategy.
+ </action>
+ <action issue="LOG4J2-2898" dev="ckozak" type="fix" due-to="Turbanov Andrey">
+ Avoid initializing volatile fields with default values.
+ </action>
+ <action issue="LOG4J2-2899" dev="ckozak" type="fix">
+ Fix log4j-1.2-api LogEventWrapper threadId and priority accessors when called multiple times.
+ </action>
+ </release>
+ <release version="2.13.3" date="2020-05-10" description="GA Release 2.13.3">
+ <action issue="LOG4J2-2838" dev="rgoers" type="fix">
+ Fix NullPointerException in ThreadContextDataInjector.
+ </action>
+ </release>
+ <release version="2.13.2" date="2020-04-23" description="GA Release 2.13.2">
+ <action issue="LOG4J2-2824" dev="rgoers" type="fix" due-to="CrazyBills">
+ Implement requiresLocation in GelfLayout to reflect whether location information is used in the message Pattern.
+ </action>
+ <action issue="LOG4J2-2588" dev="rgoers" type="fix">
+ Add option to restore printing timeMillis in the JsonLayout.
+ </action>
+ <action issue="LOG4J2-2766" dev="rgoers" type="fix">
+ Initialize pattern processor before triggering policy during reconriguration.
+ </action>
+ <action issue="LOG4J2-2457" dev="rgoers" type="update">
+ Allow the file extension in the file pattern to be modified during reconfiguration.
+ </action>
+ <action issue="LOG4J2-2810" dev="rgoers" type="fix">
+ Add information about using a url in log4j.configurationFile.
+ </action>
+ <action issue="LOG4J2-2813" dev="rgoers" type="fix" due-to="Keith D Gregory">
+ serializeToBytes was checking wrong variable for null.
+ </action>
+ <action issue="LOG4J2-2814" dev="rgoers" type="fix">
+ Fix Javadoc for ScriptPatternSelector.
+ </action>
+ <action issue="LOG4J2-2793" dev="rgoers" type="fix" due-to="Renukaprasad C">
+ Allow trailing and leading spaces in log level.
+ </action>
+ <action issue="LOG4J2-2520" dev="rgoers" type="update">
+ Allow servlet context path to be retrieved without "/".
+ </action>
+ <action issue="LOG4J2-2818" dev="rgoers" type="update">
+ Allow Spring Lookup to return default and active profiles.
+ </action>
+ <action issue="LOG4J2-2817" dev="rgoers" type="fix" due-to="Trejkaz">
+ Allow the file size action to parse the value without being sensitive to the current locale.
+ </action>
+ <action issue="LOG4J2-2815" dev="rgoers" type="update">
+ Allow Spring Boot applications to use composite configuratons.
+ </action>
+ <action issue="LOG4J2-1360" dev="rgoers" type="add" due-to="Kevin Leturc">
+ Provide a Log4j implementation of System.Logger.
+ </action>
+ <action issue="LOG4J2-2790" dev="rgoers" type="fix" due-to="Marius Volkhart">
+ Conditionally allocate PluginEntry during PluginCache loading.
+ </action>
+ <action issue="LOG4J2-2811" dev="rgoers" type="fix" due-to="Kuojian21">
+ Add missing includeLocation parameter when creating AsyncLogger.
+ </action>
+ <action issue="LOG4J2-2761" dev="rgoers" type="fix" due-to="Uwe Schindler">
+ Fix Exceptions when whitespace is in the file path and Java security manager is used.
+ </action>
+ <action issue="LOG4J2-2809" dev="rgoers" type="fix" due-to="Romain Manni-Bucau">
+ Avoid NullPointerException when StackWalker returns null.
+ </action>
+ <action issue="LOG4J2-2807" dev="rgoers" type="add">
+ Added EventLookup to retrieve fields from the log event.
+ </action>
+ <action issue="LOG4J2-2805" dev="rgoers" type="fix">
+ TimeFilter did not handle daylight saving time transitions and did not support a range over 2 days.
+ </action>
+ <action issue="LOG4J2-2779" dev="rgoers" type="update">
+ Add ContextDataProviders as an alternative to having to implement a ContextDataInjector.
+ </action>
+ <action issue="LOG4J2-2812" dev="ggregory" type="update">
+ [JDBC] Throw a AppenderLoggingException instead of an NPE in the JDBC database manager.
+ </action>
+ </release>
+ <release version="2.13.1" date="2020-02-25" description="GA Release 2.13.1">
+ <action issue="LOG4J2-2717" dev="rgoers" type="fix">
+ Slow initialization on Windows due to accessing network interfaces.
+ </action>
+ <action issue="LOG4J2-2789" dev="rgeors" type="update" due-to="Marius Volkhart">
+ Conditionally perform status logging calculations in PluginRegistry.
+ </action>
+ <action issue="LOG4J2-2756" dev="rgoers" type="fix">
+ Prevent LoggerContext from being garbage collected while being created.
+ </action>
+ <action issue="LOG4J2-2769" dev="rgoers" type="fix">
+ Do not log an error if Files.move does not work.
+ </action>
+ <action issue="LOG4J2-2039" dev="rgoers" type="fix">
+ RolloverFails when file matches pattern but index is too large.
+ </action>
+ <action issue="LOG4J2-2784" dev="rgoers" type="fix">
+ Counter stuck at 10 and overwriting files when leading zeros used in the file pattern count.
+ </action>
+ <action issue="LOG4J2-2746" dev="rgoers" type="fix">
+ ClassLoaderContextSelector was not locating the LoggerContext during shutdown.
+ </action>
+ <action issue="LOG4J2-2649" dev="rgoers" type="fix">
+ GraalVM does not allow use of MethodHandles.
+ </action>
+ <action issue="LOG4J2-2211" dev="rgoers" type="fix">
+ Allow Lookup keys with leading dashes by using a slash as an escape character.
+ </action>
+ <action issue="LOG4J2-2782" dev="rgoers" type="update">
+ Use LinkedBlockingQueue instead of synchronized collction in StatusConfiguration.
+ </action>
+ <action issue="LOG4J2-2781" dev="rgoers" type="fix" due-to="qxo">
+ ServletContainerInitializer was obtaining the StatusLogger too soon.
+ </action>
+ <action issue="LOG4J2-2703" dev="goers" type="fix" due-to="Volkan Yazici">
+ MapMessage.getFormattedMesssage() would incorrectly format objects.
+ </action>
+ <action issue="LOG4J2-2760" dev="rgoers" type="fix" due-to="Christoph Kaser">
+ Always write header on a new OutputStream.
+ </action>
+ <action issue="LOG4J2-2777" dev="rgoers" type="update" due-to="joongs4">
+ Add a retry count attribute to the KafkaAppender.
+ </action>
+ <action issue="LOG4J2-2776" dev="rgoers" type="fix" due-to="Christoph Kaser">
+ An error message in RollingFileAppender uses a placeholder for the name but does not specify the name
+ argument in the logging call
+ </action>
+ <action issue="LOG4J2-2758" dev="rgoers" type="fix" due-to="Christoph Kaser">
+ NullPointerException when using a custom DirectFileRolloverStrategy without a file name.
+ </action>
+ <action issue="LOG4J2-2768" dev="rgoers" type="fix" due-to="Marius Volkhart">
+ Add mulit-parameter overloads to LogBuilder.
+ </action>
+ <action issue="LOG4J2-2770" dev="rgoers" type="fix" due-to="Bill Kuker">
+ Fixed NullPointerException after reconfiguring via JMX.
+ </action>
+ <action issue="LOG4J2-2759" dev="rgoers" type="fix">
+ RollingFileAppender was not rolling on startup if createOnDemand was set to true.
+ </action>
+ <action issue="LOG4J2-2767" dev="rgoers" type="fix">
+ Warn if pattern is missing on Routes element. Use default route.
+ </action>
+ <action issue="LOG4J2-2415" dev="ckozak" type="fix" due-to="Andrey Turbanov">
+ Fix lock contention in the classloader using new versions of slf4j without EventData on slf4j logger creation.
+ </action>
+ <action issue="LOG4J2-2677" dev="ckozak" type="fix">
+ Rollover handles parallel file deletion gracefully.
+ </action>
+ <action issue="LOG4J2-2744" dev="ckozak" type="fix">
+ Remove unnecessary EventLogger references from log4j-slf4j18-impl due to removal from slf4j.
+ </action>
+ <action issue="LOG4J2-2745" dev="ckozak" type="update">
+ Update log4j-slf4j18-impl slf4j version to 1.8.0-beta4 from 1.8.0-alpha2.
+ </action>
+ <action issue="LOG4J2-2747" dev="ckozak" type="fix">
+ Fix a memory leak using fully asynchronous logging when the queue is full using the 'discard' asynchronous queue full strategy.
+ </action>
+ <action issue="LOG4J2-2739" dev="ckozak" type="fix">
+ Fix erroneous log4j-jul recursive logger detection resulting in some no-op JUL loggers and 'WARN Recursive call to getLogger' being reported by the status logger.
+ </action>
+ <action issue="LOG4J2-2748" dev="ckozak" type="add">
+ Implement ISO8601_PERIOD_MICROS fixed date format matching ISO8601_PERIOD with support for microsecond precision.
+ </action>
+ <action issue="LOG4J2-2735" dev="ckozak" type="fix" due-to="Andy Wilkinson">
+ PluginCache output is reproducible allowing the annotation processor to produce deterministic results.
+ </action>
+ <action issue="LOG4J2-2751" dev="ckozak" type="fix">
+ Fix StackLocator.getCallerClass performance in cases where Reflection.getCallerClass is not accessible.
+ </action>
+ <action issue="LOG4J2-2752" dev="ckozak" type="fix">
+ MutableLogEvent and RingBufferLogEvent avoid StringBuffer and parameter array allocation unless reusable messages are used.
+ </action>
+ <action issue="LOG4J2-2754" dev="ckozak" type="fix">
+ LoaderUtil.getClassLoaders may discover additional loaders and no longer erroneously returns a result with a null element in some environments.
+ </action>
+ <action issue="LOG4J2-2762" dev="ggregory" type="fix" due-to="Gary Gregory">
+ [JDBC] MS-SQL Server JDBC driver throws SQLServerException when inserting a null value for a VARBINARY column.
+ </action>
+ <action issue="LOG4J2-2770" dev="ggregory" type="fix" due-to="Bill Kuker">
+ NullPointerException after reconfiguring via JMX.
+ </action>
+ </release>
+ <release version="2.13.0" date="2019-12-11" description="GA Release 2.13.0">
+ <action issue="LOG4J2-2058" dev="rgoers" type="fix">
+ Prevent recursive calls to java.util.LogManager.getLogger().
+ </action>
+ <action issue="LOG4J2-2725" dev="ckozak" type="fix" due-to="Dzmitry Anikechanka">
+ LOG4J2-2725 - Added try/finally around event.execute() for RingBufferLogEventHandler to clear memory
+ correctly in case of exception/error
+ </action>
+ <action issue="LOG4J2-2635" dev="rgoers" type="fix" due-to="Filipp Gunbin">
+ Wrong java version check in ThreadNameCachingStrategy.
+ </action>
+ <action issue="LOG4J2-2674" dev="rgoers" type="fix" due-to="Anton Korenkov">
+ Use a less confusing name for the CompositeConfiguration source.
+ </action>
+ <action issue="LOG4J2-2732" dev="rgoers" type="add" due-to="Matt Pavlovich">
+ Add ThreadContext.putIfNotNull method.
+ </action>
+ <action issue="LOG4J2-2731" dev="rgoers" type="add">
+ Add a Level Patttern Selector.
+ </action>
+ <action issue="LOG4J2-2727" dev="rogers" type="fix" due-to="Clément Mathieu">
+ Add setKey method to Kafka Appender Builder.
+ </action>
+ <action issue="LOG4J2-2707" dev="rgoers" type="fix" due-to="Christian Frank">
+ ArrayIndexOutOfBoundsException could occur with MAC address longer than 6 bytes.
+ </action>
+ <action issue="LOG4J2-63" dev="rgoers" type="add">
+ Add experimental support for Log4j 1 configuration files.
+ </action>
+ <action issue="LOG4J2-2712" dev="rgoers" type="fix">
+ The rolling file appenders would fail to compress the file after rollover if the file name matched the
+ file pattern.
+ </action>
+ <action issue="LOG4J2-2716" dev="rgoers" type="add">
+ Add the ability to lookup Kubernetes attributes in the Log4j configuration. Allow Log4j properties to
+ be retrieved from the Spring environment if it is available.
+ </action>
+ <action issue="LOG4J2-2710" dev="rgoers" type="add">
+ Allow Spring Boot application properties to be accessed in the Log4j 2 configuration. Add
+ lower and upper case Lookups.
+ </action>
+ <action issue="LOG4J2-2709" dev="rgoers" type="update">
+ Allow message portion of GELF layout to be formatted using a PatternLayout. Allow
+ ThreadContext attributes to be explicitly included or excluded in the GelfLayout.
+ </action>
+ <action issue="LOG4J2-2693" dev="mattsicker" type="fix">
+ @PluginValue does not support attribute names besides "value".
+ </action>
+ <action issue="LOG4J-2672" dev="rgoers" type="fix" due-to="Stephen Colebourne">
+ Add automatic module names where missing.
+ </action>
+ <action issue="LOG4J2-2639" dev="rgoers" type="add">
+ Add builder pattern to Logger interface.
+ </action>
+ <action issue="LOG4J2-2673" dev="ggregory" type="fix" due-to="Yuichi Sugimura">
+ OutputStreamAppender.Builder ignores setFilter().
+ </action>
+ <action issue="LOG4J2-2725" dev="ckozak" type="fix" due-to="Dzmitry Anikechanka">
+ Prevent a memory leak when async loggers throw errors.
+ </action>
+ </release>
+ <release version="2.12.1" date="2019-08-06" description="GA Release 2.12.1">
+ <action issue="LOG4J2-1946" dev="rgoers" type="fix" due-to="Igor Perelyotov">
+ Allow file renames to work when files are missing from the sequence.
+ </action>
+ <action issue="LOG4J2-2650" dev="rgoers" type="fix" due-to="Mattia Bertorello">
+ Support emulating a MAC address when using ipv6.
+ </action>
+ <action issue="LOG4J2-2366" dev="rgoers" type="fix">
+ Remove references to LoggerContext when it is shutdown.
+ </action>
+ <action issue="LOG4J2-2556" dev="rgoers" type="update">
+ Make Log4j Core optional for Log4j 1.2 API.
+ </action>
+ <action issue="LOG4J2-2575" dev="rgoers" type="fix" due-to="Nathan Friess">
+ CronExpression.getBeforeTime() would sometimes return incorrect result.
+ </action>
+ <action issue="LOG4J2-2644" dev="rgoers" type="fix">
+ Improve the performance of capturing location information.
+ </action>
+ <action issue="LOG4J2-2646" dev="ggregory" type="update">
+ Update MongoDB 3 driver from 3.10.1 to 3.10.2.
+ </action>
+ <action issue="LOG4J2-2657" dev="ggregory" type="update">
+ Improve exception messages in the JDBC appender.
+ </action>
+ <action issue="LOG4J2-2658" dev="ckozak" type="fix">
+ AbstractAction.reportException records a warning to the status logger, providing more information when file
+ based appenders fail to compress rolled data asynchronously.
+ </action>
+ <action issue="LOG4J2-2659" dev="ckozak" type="fix">
+ AbstractAction handles and records unchecked RuntimeException and Error in addition to IOException.
+ </action>
+ <action issue="LOG4J2-2660" dev="ggregory" type="update">
+ Retry when JDBC throws a java.sql.SQLTransactionRollbackException in commitAndClose().
+ </action>
+ <action issue="LOG4J2-2667" dev="ggregory" type="fix" due-to="Gary Gregory, Edith Chui">
+ "Values not bound to statement" when using JDBC appender, appender does not respect bufferSize="0".
+ </action>
+ </release>
+ <release version="2.12.0" date="2019-06-23" description="GA Release 2.12.0">
+ <action issue="LOG4J2-2547" dev="rgoers" type="fix">
+ RollingRandomAccessFileAppender error message referenced incorrect class name.
+ </action>
+ <action issue="LOG4J2-2622" dev="rgoers" type="fix">
+ StructuredDataId was ignoring maxLength atribute.
+ </action>
+ <action issue="LOG4J2-2636" dev="rgoers" type="fix">
+ RFC5424Layout was not properly setting default Structured Element id for the MDC
+ </action>
+ <action issue="LOG4J2-2403" dev="rgoers" type="add" due-to="hupfdule">
+ Allow zero padding the counter of a RollingFileAppender.
+ </action>
+ <action issue="LOG4J2-2427" dev="rgoers" type="add" due-to="Rimaljit Kaur">
+ Add filter that will match events when no marker is present.
+ </action>
+ <action issue="LOG4J2-1143" dev="rgoers" type="fix" due-to="Pascal Heinrich">
+ Lookups were not found if the plugin key was not lowercase.
+ </action>
+ <action issue="LOG4J2-2406" dev="rgoers" type="add">
+ Add reconfiguration methods to Configurator.
+ </action>
+ <action issue="LOG4J2-1852" dev="rgoers" type="fix" due-to="Tanner Altares">
+ Locate plugins within a Jar using a URL Connection.
+ </action>
+ <action issue="LOG4J2-2610" dev="rgoers" type="fix">
+ Explicitly set file creation time.
+ </action>
+ <action issue="LOG4J2-2561" dev="rgoers" type="fix" due-to="Ulrich Enslin">
+ JEP223 version detection fix for JDK 9 and up.
+ </action>
+ <action issue="LOG4J2-1103" dev="rgoers" type="fix" due-to="Seán Dunne">
+ FailoverAppender was failing with ERROR appender Failover has no parameter that matches element Failovers.
+ </action>
+ <action issue="LOG4J2-2602" dev="rgoers" type="fix">
+ Update file time when size based triggering policy is used without a time-based triggering policy.
+ </action>
+ <action issue="LOG4J2-2597" dev="rgoers" type="fix">
+ Throw better exception message when both log4j-slf4j-impl and log4j-to-slf4j are present.
+ </action>
+ <action issue="LOG4J2-913" dev="rgoers" type="add">
+ Add support for reconfiguration via HTTP(S), Docker, and Spring Cloud Configuration.
+ </action>
+ <action issue="LOG4J2-2586" dev="rgoers" type="add">
+ TCP Appender should support a host name resolving to multiple IP addresses.
+ </action>
+ <action issue="LOG4J2-2559" dev="ggregory" type="fix" due-to="Li Lei, Gary Gregory">
+ NullPointerException in JdbcAppender.createAppender().
+ </action>
+ <action dev="ggregory" type="update" due-to="Gary Gregory">
+ Update tests from H2 1.4.197 to 1.4.199.
+ </action>
+ <action issue="LOG4J2-2570" dev="ggregory" type="update" due-to="Gary Gregory">
+ Update Jackson from 2.9.7 to 2.9.8.
+ </action>
+ <action issue="LOG4J2-2574" dev="ggregory" type="update" due-to="Gary Gregory">
+ Update MongoDB 3 module driver from 3.9.0 to 3.10.1.
+ </action>
+ <action issue="LOG4J2-2592" dev="ggregory" type="fix" due-to="Dávid Kaya, Gary Gregory">
+ StackOverflowException when server not reachable with SocketAppender.
+ </action>
+ <action issue="LOG4J2-2337" dev="ggregory" type="add" due-to="Arvind Sahare, Patrice Ferrot">
+ Allow custom end-of-line with JsonLayout.
+ </action>
+ <action issue="LOG4J2-2598" dev="ckozak" type="add" due-to="Carter Kozak">
+ GZIP compression on rollover supports configurable compression levels.
+ </action>
+ <action issue="LOG4J2-2603" dev="ggregory" type="fix" due-to="Gary Gregory">
+ java.lang.StackOverflowError at org.apache.logging.log4j.junit.AbstractExternalFileCleaner.println(AbstractExternalFileCleaner.java:169).
+ </action>
+ <action issue="LOG4J2-2564" dev="ckozak" type="fix">
+ MapPatternConverter is properly created from the '%K', '%map', and '%MAP' patterns.
+ PatternConverter instanceOf methods with unknown parameter types no longer elide those with known parameters.
+ </action>
+ <action issue="LOG4J2-2611" dev="ckozak" type="add">
+ AsyncQueueFullPolicy configuration short values "Default" and "Discard" are case insensitive to avoid confusion.
+ </action>
+ <action issue="LOG4J2-2612" dev="ggregory" type="fix">
+ NullPointerException at org.apache.logging.log4j.core.appender.db.jdbc.JdbcDatabaseManager.writeInternal(JdbcDatabaseManager.java:803).
+ </action>
+ <action issue="LOG4J2-2619" dev="ggregory" type="update">
+ Update Jackson from 2.9.8 to 2.9.9.
+ </action>
+ <action issue="LOG4J2-2631" dev="ckozak" type="fix">
+ RoutingAppender PurgePolicy implementations no longer stop appenders referenced from the logger configuration,
+ only those that have been created by the RoutingAppender. Note that RoutingAppender.getAppenders no longer
+ includes entries for referenced appenders, only those which it has created.
+ </action>
+ <action issue="LOG4J2-2629" dev="ckozak" type="fix">
+ Fix a race allowing events not to be recorded when a RoutingAppender purge policy attempts to delete an idle
+ appender at exactly the same time as a new event is recorded.
+ </action>
+ <action issue="LOG4J2-2606" dev="ckozak" type="fix">
+ Asynchronous logging when the queue is full no longer results in heavy CPU utilization and low throughput.
+ </action>
+ <action issue="LOG4J2-2634" dev="ckozak" type="update">
+ Refactor several AsyncLogger methods below the 35 byte threshold for inlining.
+ </action>
+ <action issue="LOG4J2-2634" dev="ggregory" type="add">
+ Add and use method org.apache.logging.log4j.message.MapMessage.toKey(String) for simpler subclasses.
</action>
</release>
<release version="2.11.2" date="2018-MM-DD" description="GA Release 2.11.2">
+ <action issue="LOG4J2-2500" dev="rgoers" type="fix">
+ Document that Properties element must be the first configuration element.
+ </action>
+ <action issue="LOG4J2-2543" dev="rgoers" type="fix" due-to="Dermot Hardy">
+ Add Log4j-to-SLF4J to BOM pom.xml.
+ </action>
+ <action issue="LOG4J2-2061" dev="rgoers" type="fix">
+ Use the file pattern as the FileManager "name" when no filename is present.
+ </action>
+ <action issue="LOG4J2-2009" dev="rgoers" type="fix">
+ Expose LoggerContext.setConfiguration as a public method.
+ </action>
+ <action issue="LOG4J2-2542" dev="rgoers" type="fix">
+ CronTriggeringPolicy was not rolling properly, especially when used with the SizeBasedTriggeringPolicy.
+ </action>
+ <action issue="LOG4J2-2266" dev="rgoers" type="fix">
+ Load PropertySources from any accessible ClassLoader. Hide any exceptions that may occur accessing a PropertySource.
+ </action>
+ <action issue="LOG4J2-1570" dev="rgoers" type="fix">
+ Logging with a lambda expression with a method call that also logs would cause logs within method call to reference line num and method name of the parent method.
+ </action>
+ <action issue="LOG4J2-1576" dev="rgoers" type="update">
+ Switch from CLIRR to RevAPI for detecting API changes.
+ </action>
+ <action issue="LOG4J2-2485" dev="rgoers" type="fix" due-to="Giovanni Matteo Fumarola">
+ SizeBasedTriggeringPolicy was not honored when using the DirectWriteRolloverStrategy if the machine restarts.
+ </action>
+ <action issue="LOG4J2-1906" dev="rgoers" type="fix">
+ Direct write was creating files with the wrong date/time.
+ </action>
+ <action issue="LOG4J2-2453" dev="rgoers" type="fix" due-to="theit">
+ Add Log4j-slf4j18-impl dependency to bom pom.
+ </action>
+ <action issue="LOG4J2-2515" dev="rgoers" type="fix" due-to="MakarovS">
+ Configuration documentation referenced incorrect method name.
+ </action>
+ <action issue="LOG4J2-2514" dev="rgoers" type="fix" due-to="smilebrian0515">
+ Make Strings.toRootUpperCase a static method so it can be accessed.
+ </action>
+ <action issue="LOG4J2-1571" dev="rgoers" type="fix" due-to="torbenmoeller">
+ Fixed Appenders section in Extending Log4j.
+ </action>
<action issue="LOG4J2-2391" dev="ckozak" type="update">
- Improve exception logging performance. ThrowableProxy construction uses a more faster
+ Improve exception logging performance. ThrowableProxy construction uses a faster
method to discover the current stack trace. ThrowablePatternConverter and
ExtendedThrowablePatternConverter default configurations no longer allocate
an additional buffer for stack trace contents.
@@ -259,6 +658,113 @@
<action issue="LOG4J2-2363" dev="ckozak" type="fix" due-to="Brian Laub">
ReusableObjectMessage parameter is properly passed to appenders (#203).
</action>
+ <action issue="LOG4J2-2418" dev="ggregory" type="fix" due-to="Jonas Rutishauser">
+ NullPointerException when closing never used RollingRandomAccessFileAppender.
+ </action>
+ <action issue="LOG4J2-2422" dev="ggregory" type="fix" due-to="rswart, Gary Gregory">
+ Handle some unchecked exceptions while loading plugins.
+ </action>
+ <action issue="LOG4J2-2441" dev="ckozak" type="fix">
+ Setting a null ErrorHandler on AbstractAppender is not allowed and will no-op as expected.
+ </action>
+ <action issue="LOG4J2-2444" dev="ckozak" type="fix">
+ ErrorHandler is invoked with a LogEvent and Throwable when possible, where previously only a string was used.
+ </action>
+ <action issue="LOG4J2-2446" dev="ggregory" type="add">
+ Add a Base64 string lookup.
+ </action>
+ <action issue="LOG4J2-2447" dev="ggregory" type="update">
+ Let the NullAppender default its name to "null".
+ </action>
+ <action issue="LOG4J2-2468" dev="ggregory" type="update">
+ Update Jackson from 2.9.6 to 2.9.7.
+ </action>
+ <action issue="LOG4J2-2469" dev="ggregory" type="update">
+ Update Apache Commons Compress from 1.17 to 1.18.
+ </action>
+ <action issue="LOG4J2-2470" dev="ggregory" type="update">
+ Update Apache Commons CSV from 1.5 to 1.6.
+ </action>
+ <action issue="LOG4J2-2471" dev="ggregory" type="update">
+ Update javax.mail from 1.6.1 to 1.6.2.
+ </action>
+ <action issue="LOG4J2-2472" dev="ggregory" type="update">
+ Update mongo-java-driver 3 from 3.8.0 to 3.8.2.
+ </action>
+ <action issue="LOG4J2-2413" dev="ggregory" type="fix" due-to="Andres Luuk, Gary Gregory">
+ Exceptions are added to all columns when a JDBC Appender's ColumnMapping uses a Pattern.
+ </action>
+ <action issue="LOG4J2-2466" dev="ggregory" type="fix" due-to="Paolo Bonanomi, Gary Gregory">
+ ColumnMapping literal not working.
+ </action>
+ <action issue="LOG4J2-2478" dev="ckozak" type="fix" due-to="Diego Elias Costa">
+ AbstractStringLayoutStringEncodingBenchmark returns the computed variables on each benchmark to avoid DCE.
+ </action>
+ <action issue="LOG4J2-2134" dev="ggregory" type="fix" due-to="David del Amo Mateos, Gary Gregory">
+ StackOverflowError at AwaitCompletionReliabilityStrategy.
+ </action>
+ <action issue="LOG4J2-2481" dev="ggregory" type="fix">
+ Avoid NullPointerExceptions in org.apache.logging.log4j.core.config.AbstractConfiguration for null arguments.
+ </action>
+ <action issue="LOG4J2-2457" dev="ggregory" type="fix" due-to="Heiko Schwanke, Gary Gregory">
+ RollingRandomAccessFileManager ignores new file patterns from programmatic reconfiguration.
+ </action>
+ <action issue="LOG4J2-2482" dev="ggregory" type="fix" due-to="Rob Gansevles">
+ BasicContextSelector cannot be used in a OSGI application.
+ </action>
+ <action issue="LOG4J2-2476" dev="ggregory" type="fix" due-to="Al Bundy">
+ org.apache.log4j.SimpleLayout and ConsoleAppender missing in log4j-1.2-api.
+ </action>
+ <action issue="LOG4J2-2489" dev="ggregory" type="update">
+ JDBC Appender should release parameter resources ASAP.
+ </action>
+ <action issue="LOG4J2-2491" dev="ggregory" type="update">
+ Allow all Appenders to optionally carry a Property array.
+ </action>
+ <action issue="LOG4J2-2497" dev="ggregory" type="fix">
+ JmsAppender reconnectIntervalMillis cannot be set from a configuration file.
+ </action>
+ <action issue="LOG4J2-2499" dev="ggregory" type="fix">
+ JMS Appender may throw a NullPointerException when JMS is not up while the Appender is starting.
+ </action>
+ <action issue="LOG4J2-2496" dev="ggregory" type="add">
+ JDBC Appender should reconnect to the database when a connection goes stale.
+ </action>
+ <action issue="LOG4J2-2503" dev="ggregory" type="update">
+ Update MongoDB driver from 3.8.2 to 3.9.0 for log4j-mongodb3 module.
+ </action>
+ <action issue="LOG4J2-2505" dev="ggregory" type="add">
+ Let JDBC PoolingDriverConnectionSource with Apache Commons DBCP configure a PoolableConnectionFactory.
+ </action>
+ <action issue="LOG4J2-2508" dev="ggregory" type="fix">
+ JDBC Appender fails when using both parameter, source, and literal ColumnMapping elements.
+ </action>
+ <action issue="LOG4J2-2509" dev="ggregory" type="add">
+ Allow a JDBC Appender to truncate strings to match a table's metadata column length limit.
+ </action>
+ <action issue="LOG4J2-1246" dev="ggregory" type="add">
+ PatternLayout %date conversion pattern should render time zone designator for ISO-ISO8601.
+ </action>
+ <action issue="LOG4J2-2527" dev="ckozak" type="fix">
+ Prevent ConcurrentModificationException while iterating over ListAppender events.
+ </action>
+ <action issue="LOG4J2-2522" dev="ckozak" type="fix" due-to="Adam Lesiak">
+ Fix regression using MapMessageLookup.lookup with MapMessages that do not implement StringMapMessage.
+ </action>
+ <action issue="LOG4J2-2530" dev="ckozak" type="fix" due-to="Travis Spencer">
+ Generalize checks using MapMessage implementations with do not extend StringMapMessage.
+ Introduce new JAVA_UNQUOTED MapMessage format type based on the JAVA formatting, but without
+ quoted values.
+ </action>
+ <action issue="LOG4J2-2533" dev="ckozak" type="fix" due-to="Michail Prusakov">
+ Fix a regression introduced by LOG4J2-2301 in 2.11.1 allowing allocation to occur in AsyncLoggerConfig.
+ </action>
+ <action issue="LOG4J2-2618" dev="ggregory" type="fix">
+ Possible ClassCastException in org.apache.logging.log4j.core.script.ScriptManager.ScriptManager(Configuration, WatchManager)
+ </action>
+ <action issue="LOG4J2-2619" dev="ggregory" type="update">
+ Update Jackson from 2.9.8 to 2.9.9.
+ </action>
</release>
<release version="2.11.1" date="2018-07-22" description="GA Release 2.11.1">
<action issue="LOG4J2-2389" dev="rgoers" type="fix" due-to="Liu Wen">
@@ -1797,7 +2303,7 @@
Removed deprecated Core API org.apache.logging.log4j.core.util.Constants.UTF_8.
</action>
<action issue="LOG4J2-1544" dev="ggregory" type="update">
- Removed deprecated Core API org.apache.logging.log4j.core.util.Assert.requireNonNull(T, String).
+ Removed deprecated Core API org.apache.logging.log4j.util.Assert.requireNonNull(T, String).
</action>
<action issue="LOG4J2-1545" dev="ggregory" type="update">
Removed deprecated Web API org.apache.logging.log4j.web.WebLookup.getServletContext().
diff --git a/src/site/asciidoc/articles.adoc b/src/site/asciidoc/articles.adoc
index 473118c..03698fb 100644
--- a/src/site/asciidoc/articles.adoc
+++ b/src/site/asciidoc/articles.adoc
@@ -43,6 +43,22 @@
== English
+* https://www.ralphgoers.com/post/getting-the-most-out-of-the-log4j-2-api[Getting the most out of the Log4j 2 API]
+(January 1, 2020)
+* https://www.baeldung.com/log4j2-programmatic-config[Programmatic Configuration with Log4j 2]
+(December 31, 2019)
+* https://www.ralphgoers.com/post/log4j-1-compatibility-in-log4j-2[Log4j 1 Compatiblity in Log4j 2]
+(December 22, 2019)
+* https://www.ralphgoers.com/post/why-was-log4j-2-created[Why was Log4j 2 created]
+(December 14, 2019)
+* https://www.marcobehler.com/guides/a-guide-to-logging-in-java[A guide to logging in Java]
+(June 23, 2019)
+* https://www.alibabacloud.com/blog/exploring-the-secrets-of-java-logs-log4j-2-log-system_594821[Exploring the Secrets of Java Logs: Log4j 2 Log System]
+(May 17, 2019)
+* https://www.mkyong.com/logging/apache-log4j-2-tutorials/[Apache Log4j 2 Tutorials]
+(March 27, 2019)
+* https://stackify.com/compare-java-logging-frameworks/[Java Logging Frameworks: log4j vs logback vs log4j2]
+(October 30, 2018)
* https://howtodoinjava.com/log4j2[Log4j2 Tutorial]
(June 3, 2018)
* https://crunchify.com/java-how-to-create-your-own-logging-level-in-log4j-configuring-log4j[In Java How to Create your own Logging Level using Log4j (Configuring Log4j 2)]
@@ -57,8 +73,6 @@
(December 30,2017)
* https://examples.javacodegeeks.com/enterprise-java/log4j/log4j-2-best-practices-example/[Log4j 2 Best Practices example]
(November 14, 2017)
-* http://www.baeldung.com/log4j2-appenders-layouts-filters[Intro to Log4j2 - Appenders, Layouts and Filters]
-(November 14, 2017)
* http://musigma.org/logging/2017/11/06/logging.html[Logging Fundamentals]
(November 6, 2017)
* http://www.rationaljava.com/2017/10/allocation-free-logging-with-log4j2.html[Allocation free logging with Log4j2]
diff --git a/src/site/asciidoc/download.adoc b/src/site/asciidoc/download.adoc
index 38df7e3..fef76a8 100644
--- a/src/site/asciidoc/download.adoc
+++ b/src/site/asciidoc/download.adoc
@@ -29,27 +29,27 @@
|Apache Log4j 2 binary (tar.gz)
|https://www.apache.org/dyn/closer.lua/logging/log4j/{Log4jReleaseVersion}/apache-log4j-{Log4jReleaseVersion}-bin.tar.gz[apache-log4j-{Log4jReleaseVersion}-bin.tar.gz]
-|https://www.apache.org/dist/logging/log4j/{Log4jReleaseVersion}/apache-log4j-{Log4jReleaseVersion}-bin.tar.gz.md5[apache-log4j-{Log4jReleaseVersion}-bin.tar.gz.md5]
+|https://www.apache.org/dist/logging/log4j/{Log4jReleaseVersion}/apache-log4j-{Log4jReleaseVersion}-bin.tar.gz.sha512[apache-log4j-{Log4jReleaseVersion}-bin.tar.gz.sha512]
|https://www.apache.org/dist/logging/log4j/{Log4jReleaseVersion}/apache-log4j-{Log4jReleaseVersion}-bin.tar.gz.asc[apache-log4j-{Log4jReleaseVersion}-bin.tar.gz.asc]
|Apache Log4j 2 binary (zip)
|https://www.apache.org/dyn/closer.lua/logging/log4j/{Log4jReleaseVersion}/apache-log4j-{Log4jReleaseVersion}-bin.zip[apache-log4j-{Log4jReleaseVersion}-bin.zip]
-|https://www.apache.org/dist/logging/log4j/{Log4jReleaseVersion}/apache-log4j-{Log4jReleaseVersion}-bin.zip.md5[apache-log4j-{Log4jReleaseVersion}-bin.zip.md5]
+|https://www.apache.org/dist/logging/log4j/{Log4jReleaseVersion}/apache-log4j-{Log4jReleaseVersion}-bin.zip.sha512[apache-log4j-{Log4jReleaseVersion}-bin.zip.sha512]
|https://www.apache.org/dist/logging/log4j/{Log4jReleaseVersion}/apache-log4j-{Log4jReleaseVersion}-bin.zip.asc[apache-log4j-{Log4jReleaseVersion}-bin.zip.asc]
|Apache Log4j 2 source (tar.gz)
|https://www.apache.org/dyn/closer.lua/logging/log4j/{Log4jReleaseVersion}/apache-log4j-{Log4jReleaseVersion}-src.tar.gz[apache-log4j-{Log4jReleaseVersion}-src.tar.gz]
-|https://www.apache.org/dist/logging/log4j/{Log4jReleaseVersion}/apache-log4j-{Log4jReleaseVersion}-src.tar.gz.md5[apache-log4j-{Log4jReleaseVersion}-src.tar.gz.md5]
+|https://www.apache.org/dist/logging/log4j/{Log4jReleaseVersion}/apache-log4j-{Log4jReleaseVersion}-src.tar.gz.sha512[apache-log4j-{Log4jReleaseVersion}-src.tar.gz.sha512]
|https://www.apache.org/dist/logging/log4j/{Log4jReleaseVersion}/apache-log4j-{Log4jReleaseVersion}-src.tar.gz.asc[apache-log4j-{Log4jReleaseVersion}-src.tar.gz.asc]
|Apache Log4j 2 source (zip)
|https://www.apache.org/dyn/closer.lua/logging/log4j/{Log4jReleaseVersion}/apache-log4j-{Log4jReleaseVersion}-src.zip[apache-log4j-{Log4jReleaseVersion}-src.zip]
-|https://www.apache.org/dist/logging/log4j/{Log4jReleaseVersion}/apache-log4j-{Log4jReleaseVersion}-src.zip.md5[apache-log4j-{Log4jReleaseVersion}-src.zip.md5]
+|https://www.apache.org/dist/logging/log4j/{Log4jReleaseVersion}/apache-log4j-{Log4jReleaseVersion}-src.zip.sha512[apache-log4j-{Log4jReleaseVersion}-src.zip.sha512]
|https://www.apache.org/dist/logging/log4j/{Log4jReleaseVersion}/apache-log4j-{Log4jReleaseVersion}-src.zip.asc[apache-log4j-{Log4jReleaseVersion}-src.zip.asc]
|===
It is essential that you verify the integrity of the downloaded files
-using the PGP or MD5 signatures. Please read
+using the PGP or SHA512 signatures. Please read
https://httpd.apache.org/dev/verification.html[Verifying Apache HTTP
Server Releases] for more information on why you should verify our
releases.
@@ -71,8 +71,8 @@
Apache Log4j {Log4jReleaseVersion} is signed by {Log4jReleaseManager} ({Log4jReleaseKey}).
-Alternatively, you can verify the MD5 signature on the files. A unix
-program called md5 or md5sum is included in many unix distributions.
+Alternatively, you can verify the SHA512 signature on the files. A unix
+program called sha512sum is included in many unix distributions.
== Previous Releases
diff --git a/src/site/asciidoc/guidelines.adoc b/src/site/asciidoc/guidelines.adoc
index 8f4dc72..15b18c5 100644
--- a/src/site/asciidoc/guidelines.adoc
+++ b/src/site/asciidoc/guidelines.adoc
@@ -91,7 +91,7 @@
using either Subversion or Git; Log4j uses
link:source-repository.html[Git]. Only some of the Apache developers
have write access to the Apache Logging repositories; everyone has
-https://git-wip-us.apache.org/repos/asf?p=logging-log4j2.git;a=summary[read
+https://gitbox.apache.org/repos/asf?p=logging-log4j2.git[read
access].
[#issues]
diff --git a/src/site/asciidoc/index.adoc b/src/site/asciidoc/index.adoc
index c31e45cf..4fc8f3f 100644
--- a/src/site/asciidoc/index.adoc
+++ b/src/site/asciidoc/index.adoc
@@ -27,6 +27,19 @@
methods they can use while ensuring forward compatibility. This allows the Log4j team to improve the implementation
safely and in a compatible manner.
+
+The Log4j API is a logging facade that may, of course, be used with the Log4j implementation, but may also be used
+in front of other logging implementations such as Logback. The Log4j API has several advantages over SLF4J:
+
+. The Log4j API supports logging [Messages](manual/messages.html) instead of just Strings.
+. The Log4j API supports lambda expressions.
+. The Log4j API provides many more logging methods than SLF4J.
+. In addition to the "parameterized logging" format supported by SLF4J, the Log4j API also supports events using
+the java.text.MessageFormat syntax as well printf-style messages.
+. The Log4j API provides a LogManager.shutdown() method. The underlying logging implementation must implement the
+Terminable interface for the method to have effect.
+. Other constructs such as Markers, log Levels, and ThreadContext (aka MDC) are fully supported.
+
=== Improved Performance
Log4j 2 contains next-generation Asynchronous Loggers based on the LMAX Disruptor library. In multi-threaded scenarios
@@ -82,6 +95,10 @@
In Log4j 2, link:manual/customloglevels.html[custom log levels] can easily be defined in code or in configuration. No
subclassing is required.
+=== Log Builder API
+In addition to using one of the many log methods in the Log4j API, log events can be constructed using a builder. See
+link:manual/logbuilder.html[Log Builder] for more information.
+
=== Garbage-free
During steady state logging, Log4j 2 is link:manual/garbagefree.html[garbage-free] in stand-alone applications, and low
@@ -91,6 +108,19 @@
Version 2.10.0 introduces a the module log4j-appserver to improve integration with Apache Tomcat and Eclipse Jetty.
+=== Cloud Enabled
+
+Version 2.12.0 introduces support for accessing Docker container information via a Lookup and for accessing
+and updating the Log4j configuration through Spring Cloud Configuration. This support was enhanced in
+version 2.13.0 to add support for accessing Spring Boot properties as well as Kubernetes information.
+See link:manual/cloud.html[Logging in the Cloud] for details.
+
+=== Compatible with Log4j 1.x
+
+The Log4j-1.2-api module of Log4j 2 provides compatiblity for applications using the Log4j 1 logging methods. As
+of Log4j 2.13.0 Log4j 2 also provides experimental support for Log4j 1.x configuration files. See
+link:manual/compatiblity.html[Log4j 2 Compatiblity with Log4j 1] for more information.
+
== Documentation
The Log4j 2 User's Guide is available on this link:manual/index.html[site] or as a downloadable
@@ -98,8 +128,9 @@
== 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.
+Log4j 2.13.0 and greater require Java 8. Version 2.4 through 2.12.1 required Java 7 and versions 2.0-alpha1 to 2.3
+required Java 6. Some features require optional dependencies; the documentation for these features specifies the
+dependencies.
== News
diff --git a/src/site/asciidoc/javadoc.adoc b/src/site/asciidoc/javadoc.adoc
index 8a0ed75..610bd85 100644
--- a/src/site/asciidoc/javadoc.adoc
+++ b/src/site/asciidoc/javadoc.adoc
@@ -83,12 +83,12 @@
|link:log4j-liquibase/apidocs/index.html[Log4j Liquibase Binding]
|The Apache Log4j Liquibase binding to Log4j 2 Core.
-|link:log4j-mongodb2/apidocs/index.html[Log4j MongoDB 2 Support]
-|Additional Appender for MongoDB using the version 2 driver.
-
|link:log4j-mongodb3/apidocs/index.html[Log4j MongoDB 3 Support]
|Additional Appender for MongoDB using the version 3 driver.
+|link:log4j-mongodb4/apidocs/index.html[Log4j MongoDB 4 Support]
+|Additional Appender for MongoDB using the version 4 driver.
+
|link:log4j-cassandra/apidocs/index.html[Log4j Cassandra Support]
|Additional Appender for Cassandra.
|===
diff --git a/src/site/asciidoc/manual/appenders.adoc b/src/site/asciidoc/manual/appenders.adoc
index 7939606..5fc0bf3 100644
--- a/src/site/asciidoc/manual/appenders.adoc
+++ b/src/site/asciidoc/manual/appenders.adoc
@@ -86,7 +86,7 @@
buffer size must be a power of 2.
When the application is logging faster than the underlying appender can
-keep up with for a long enough time to fill up the queue, the behavious
+keep up with for a long enough time to fill up the queue, the behaviour
is determined by the
link:../log4j-core/apidocs/org/apache/logging/log4j/core/async/AsyncQueueFullPolicy.html[`AsyncQueueFullPolicy`].
@@ -839,7 +839,7 @@
== JDBCAppender
As of Log4j 2.11.0, JDBC support has moved from the existing module
-`logj-core` to the new module `log4j-jdbc`.
+`log4j-core` to the new module `log4j-jdbc`.
The JDBCAppender writes log events to a relational database table using
standard JDBC. It can be configured to obtain JDBC connections using a
@@ -911,6 +911,16 @@
will be used as is in the `INSERT` query without any escaping.
Otherwise, the layout or pattern specified will be converted into the
configured type and stored in that column.
+
+|immediateFail |boolean |false |When set to true, log events will not
+wait to try to reconnect and will fail immediately if the JDBC resources
+are not available. New in 2.11.2.
+
+|reconnectIntervalMillis |long |5000 |If set to a value greater than 0,
+after an error, the JDBCDatabaseManager will attempt to reconnect to the database
+after waiting the specified number of milliseconds. If the reconnect
+fails then an exception will be thrown (which can be caught by the
+application if `ignoreExceptions` is set to `false`). New in 2.11.2.
|=======================================================================
When configuring the JDBCAppender, you must specify a `ConnectionSource`
@@ -988,6 +998,31 @@
`jdbc:apache:commons:dbcp:` followed by the pool name if you want to use
a pooled connection elsewhere. For example:
`jdbc:apache:commons:dbcp:example`.
+
+|PoolableConnectionFactory |PoolableConnectionFactory element |Defines a PoolableConnectionFactory.
+|=======================================================================
+
+[#JDBCPoolableConnectionFactory]
+.PoolableConnectionFactory Parameters (Apache Commons DBCP)
+[cols=",,",options="header",]
+|=======================================================================
+|Parameter Name |Type |Description
+|autoCommitOnReturn |boolean | See http://commons.apache.org/proper/commons-dbcp/api-2.5.0/org/apache/commons/dbcp2/PoolableConnectionFactory.html[Apache Commons DBCP PoolableConnectionFactory.]
+|cacheState |boolean | See http://commons.apache.org/proper/commons-dbcp/api-2.5.0/org/apache/commons/dbcp2/PoolableConnectionFactory.html[Apache Commons DBCP PoolableConnectionFactory.]
+|connectionInitSqls |Strings | See http://commons.apache.org/proper/commons-dbcp/api-2.5.0/org/apache/commons/dbcp2/PoolableConnectionFactory.html[Apache Commons DBCP PoolableConnectionFactory.]
+|defaultAutoCommit |Boolean | See http://commons.apache.org/proper/commons-dbcp/api-2.5.0/org/apache/commons/dbcp2/PoolableConnectionFactory.html[Apache Commons DBCP PoolableConnectionFactory.]
+|defaultCatalog |String | See http://commons.apache.org/proper/commons-dbcp/api-2.5.0/org/apache/commons/dbcp2/PoolableConnectionFactory.html[Apache Commons DBCP PoolableConnectionFactory.]
+|defaultQueryTimeoutSeconds |Integer | See http://commons.apache.org/proper/commons-dbcp/api-2.5.0/org/apache/commons/dbcp2/PoolableConnectionFactory.html[Apache Commons DBCP PoolableConnectionFactory.]
+|defaultReadOnly |Boolean | See http://commons.apache.org/proper/commons-dbcp/api-2.5.0/org/apache/commons/dbcp2/PoolableConnectionFactory.html[Apache Commons DBCP PoolableConnectionFactory.]
+|defaultTransactionIsolation |int | See http://commons.apache.org/proper/commons-dbcp/api-2.5.0/org/apache/commons/dbcp2/PoolableConnectionFactory.html[Apache Commons DBCP PoolableConnectionFactory.]
+|disconnectionSqlCodes |Strings | See http://commons.apache.org/proper/commons-dbcp/api-2.5.0/org/apache/commons/dbcp2/PoolableConnectionFactory.html[Apache Commons DBCP PoolableConnectionFactory.]
+|fastFailValidation |boolean | See http://commons.apache.org/proper/commons-dbcp/api-2.5.0/org/apache/commons/dbcp2/PoolableConnectionFactory.html[Apache Commons DBCP PoolableConnectionFactory.]
+|maxConnLifetimeMillis |long | See http://commons.apache.org/proper/commons-dbcp/api-2.5.0/org/apache/commons/dbcp2/PoolableConnectionFactory.html[Apache Commons DBCP PoolableConnectionFactory.]
+|maxOpenPreparedStatements |int | See http://commons.apache.org/proper/commons-dbcp/api-2.5.0/org/apache/commons/dbcp2/PoolableConnectionFactory.html[Apache Commons DBCP PoolableConnectionFactory.]
+|poolStatements |boolean | See http://commons.apache.org/proper/commons-dbcp/api-2.5.0/org/apache/commons/dbcp2/PoolableConnectionFactory.html[Apache Commons DBCP PoolableConnectionFactory.]
+|rollbackOnReturn |boolean | See http://commons.apache.org/proper/commons-dbcp/api-2.5.0/org/apache/commons/dbcp2/PoolableConnectionFactory.html[Apache Commons DBCP PoolableConnectionFactory.]
+|validationQuery |String | See http://commons.apache.org/proper/commons-dbcp/api-2.5.0/org/apache/commons/dbcp2/PoolableConnectionFactory.html[Apache Commons DBCP PoolableConnectionFactory.]
+|validationQueryTimeoutSeconds |int | See http://commons.apache.org/proper/commons-dbcp/api-2.5.0/org/apache/commons/dbcp2/PoolableConnectionFactory.html[Apache Commons DBCP PoolableConnectionFactory.]
|=======================================================================
When configuring the JDBCAppender, use the nested `<Column>` elements to
@@ -1213,7 +1248,7 @@
[[JMSQueueAppender]] [[JMSTopicAppender]]
As of Log4j 2.11.0, JPA support has moved from the existing module
-`logj-core` to the new module `log4j-jms`.
+`log4j-core` to the new module `log4j-jms`.
The JMS Appender sends the formatted log event to a JMS Destination.
@@ -1342,7 +1377,7 @@
== JPAAppender
As of Log4j 2.11.0, JPA support has moved from the existing module
-`logj-core` to the new module `log4j-jpa`.
+`log4j-core` to the new module `log4j-jpa`.
The JPAAppender writes log events to a relational database table using
the Java Persistence API 2.1. It requires the API and a provider
@@ -1601,7 +1636,7 @@
== KafkaAppender
As of Log4j 2.11.0, https://kafka.apache.org/[Apache Kafka] support has
-moved from the existing module `logj-core` to the new module
+moved from the existing module `log4j-core` to the new module
`log4j-kafka`.
The KafkaAppender logs events to an https://kafka.apache.org/[Apache
@@ -1895,126 +1930,16 @@
[#NoSQLAppenderMongoDB]
== NoSQLAppenderMongoDB
-Starting with Log4 2.11.0, we provide two MongoDB modules:
+We provide the following MongoDB modules:
-* `log4j-mongodb2` defines the configuration element
-link:#NoSQLAppenderMongoDB2[`MongoDb2`] matching the MongoDB Driver
-version 2.
-* `log4j-mongodb3` defines the configuration element
+* Added in 2.11.0: `log4j-mongodb3` defines the configuration element
link:#NoSQLAppenderMongoDB3[`MongoDb3`] matching the MongoDB Driver
version 3.
+* Added in 2.14.0: `log4j-mongodb4` defines the configuration element
+link:#NoSQLAppenderMongoDB4[`MongoDb4`] matching the MongoDB Driver
+version 4.
-We no longer provide the module `log4j-mongodb`.
-
-The module `log4j-mongodb2` aliases the old configuration element
-`MongoDb` to link:#NoSQLAppenderMongoDB2[`MongoDb2`].
-
-[#NoSQLAppenderMongoDB2]
-== NoSQLAppenderMongoDB2
-
-This section details specializations of the
-link:#NoSQLAppender[NoSQLAppender] provider for MongoDB using the
-MongoDB driver version 2. The NoSQLAppender Appender writes log events
-to a NoSQL database using an internal lightweight provider interface.
-
-.MongoDB2 Provider Parameters
-[cols=",,",options="header",]
-|=======================================================================
-|Parameter Name |Type |Description
-|collectionName |String |_Required._ The name of the MongoDB collection
-to insert the events into.
-
-|writeConcernConstant |Field |By default, the MongoDB provider inserts
-records with the instructions `com.mongodb.WriteConcern.ACKNOWLEDGED`.
-Use this optional attribute to specify the name of a constant other than
-`ACKNOWLEDGED`.
-
-|writeConcernConstantClass |Class |If you specify
-`writeConcernConstant`, you can use this attribute to specify a class
-other than `com.mongodb.WriteConcern` to find the constant on (to create
-your own custom instructions).
-
-|factoryClassName |Class |To provide a connection to the MongoDB
-database, you can use this attribute and `factoryMethodName` to specify
-a class and static method to get the connection from. The method must
-return a `com.mongodb.DB` or a `com.mongodb.MongoClient`. If the `DB` is
-not authenticated, you must also specify a `username` and `password`. If
-you use the factory method for providing a connection, you must not
-specify the `databaseName`, `server`, or `port` attributes.
-
-|factoryMethodName |Method |See the documentation for attribute
-`factoryClassName`.
-
-|databaseName |String |If you do not specify a `factoryClassName` and
-`factoryMethodName` for providing a MongoDB connection, you must specify
-a MongoDB database name using this attribute. You must also specify a
-`username` and `password`. You can optionally also specify a `server`
-(defaults to localhost), and a `port` (defaults to the default MongoDB
-port).
-
-|server |String |See the documentation for attribute `databaseName`.
-
-|port |int |See the documentation for attribute `databaseName`.
-
-|username |String |See the documentation for attributes `databaseName`
-and `factoryClassName`.
-
-|password |String |See the documentation for attributes `databaseName`
-and `factoryClassName`.
-
-|capped |boolean |Enable support for
-https://docs.mongodb.com/manual/core/capped-collections/[capped
-collections]
-
-|collectionSize |int |Specify the size in bytes of the capped collection
-to use if enabled. The minimum size is 4096 bytes, and larger sizes will
-be increased to the nearest integer multiple of 256. See the capped
-collection documentation linked above for more information.
-|=======================================================================
-
-This appender is link:messages.html#MapMessage[MapMessage]-aware.
-
-Here are a few sample configurations for the NoSQLAppender and MongoDB2
-provider:
-
-[source,xml]
-----
-<?xml version="1.0" encoding="UTF-8"?>
-<Configuration status="error">
- <Appenders>
- <NoSql name="databaseAppender">
- <MongoDb2 databaseName="applicationDb" collectionName="applicationLog" server="mongo.example.org"
- username="loggingUser" password="abc123" />
- </NoSql>
- </Appenders>
- <Loggers>
- <Root level="warn">
- <AppenderRef ref="databaseAppender"/>
- </Root>
- </Loggers>
-</Configuration>
-----
-
-[source,xml]
-----
-<?xml version="1.0" encoding="UTF-8"?>
-<Configuration status="error">
- <Appenders>
- <NoSql name="databaseAppender">
- <MongoDb2 collectionName="applicationLog" factoryClassName="org.example.db.ConnectionFactory"
- factoryMethodName="getNewMongoClient" />
- </NoSql>
- </Appenders>
- <Loggers>
- <Root level="warn">
- <AppenderRef ref="databaseAppender"/>
- </Root>
- </Loggers>
-</Configuration>
-----
-
-Starting in Log4j version 2.11.0, the provider element name is
-`MongoDb2`. The name `MongoDb` is now a deprecated alias for `MongoDb2`.
+We no longer provide the modules `log4j-mongodb` and `log4j-mongodb2`.
[#NoSQLAppenderMongoDB3]
== NoSQLAppenderMongoDB3
@@ -2121,6 +2046,74 @@
</Configuration>
----
+[#NoSQLAppenderMongoDB4]
+== NoSQLAppenderMongoDB4
+
+This section details specializations of the
+link:#NoSQLAppender[NoSQLAppender] provider for MongoDB using the
+MongoDB driver version 4. The NoSQLAppender Appender writes log events
+to a NoSQL database using an internal lightweight provider interface.
+
+.MongoDB3 Provider Parameters
+[cols=",,",options="header",]
+|=======================================================================
+|Parameter Name |Type |Description
+|connection |String |_Required._ The MongoDB
+http://mongodb.github.io/mongo-java-driver/4.0/apidocs/mongodb-driver-core/com/mongodb/ConnectionString.html?is-external=true"[connection string]
+in the format `mongodb://[username:password@]host1[:port1][,host2[:port2],...[,hostN[:portN]]][/[database.collection][?options]]`.
+
+|capped |boolean |Enable support for
+https://docs.mongodb.com/manual/core/capped-collections/[capped
+collections]
+
+|collectionSize |int |Specify the size in bytes of the capped collection
+to use if enabled. The minimum size is 4096 bytes, and larger sizes will
+be increased to the nearest integer multiple of 256. See the capped
+collection documentation linked above for more information.
+|=======================================================================
+
+This appender is link:messages.html#MapMessage[MapMessage]-aware.
+
+Here are a few sample configurations for the NoSQLAppender and MongoDB4
+provider:
+
+[source,xml]
+----
+<?xml version="1.0" encoding="UTF-8"?>
+<Configuration status="WARN">
+ <Appenders>
+ <NoSql name="MongoDbAppender">
+ <MongoDb4 connection="mongodb://log4jUser:12345678@localhost:${sys:MongoDBTestPort:-27017}/testDb.testCollection" />
+ </NoSql>
+ </Appenders>
+ <Loggers>
+ <Root level="ALL">
+ <AppenderRef ref="MongoDbAppender" />
+ </Root>
+ </Loggers>
+</Configuration>
+----
+
+[source,xml]
+----
+<?xml version="1.0" encoding="UTF-8"?>
+<Configuration status="WARN">
+ <Appenders>
+ <NoSql name="MongoDbAppender">
+ <MongoDb4
+ connection="mongodb://localhost:${sys:MongoDBTestPort:-27017}/testDb.testCollection"
+ capped="true"
+ collectionSize="1073741824"/>
+ </NoSql>
+ </Appenders>
+ <Loggers>
+ <Root level="ALL">
+ <AppenderRef ref="MongoDbAppender" />
+ </Root>
+ </Loggers>
+</Configuration>
+----
+
[#NoSQLAppenderCouchDB]
== NoSQLAppenderCouchDB
@@ -2602,7 +2595,10 @@
is used. The DefaultRolloverPolicy will accept both a date/time pattern
compatible with
https://download.oracle.com/javase/7/docs/api/java/text/SimpleDateFormat.html[`SimpleDateFormat`]
-and/or a %i which represents an integer counter. The pattern also
+and/or a %i which represents an integer counter. The integer counter
+allows specifying a padding, like %3i for space-padding the counter to
+3 digits or (usually more useful) %03i for zero-padding the counter to
+3 digits. The pattern also
supports interpolation at runtime so any of the Lookups (such as the
link:./lookups.html#DateLookup[DateLookup]) can be included in the
pattern.
@@ -2696,7 +2692,11 @@
Cron Triggering Policy
-The `CronTriggeringPolicy` triggers rollover based on a cron expression.
+The `CronTriggeringPolicy` triggers rollover based on a cron expression. This policy
+is controlled by a timer and is asynchronous to processing log events, so it is possible that log events
+from the previous or next time period may appear at the beginning or end of the log file.
+The filePattern attribute of the Appender should contain a timestamp otherwise the target file will be
+overwritten on each rollover.
.CronTriggeringPolicy Parameters
[cols=",,",options="header",]
@@ -2738,9 +2738,12 @@
SizeBased Triggering Policy
-The `SizeBasedTriggeringPolicy` causes a rollover once the file has
-reached the specified size. The size can be specified in bytes, with the
-suffix KB, MB or GB, for example `20MB`.
+The `SizeBasedTriggeringPolicy` causes a rollover once the file has reached the specified size. The size can be
+specified in bytes, with the suffix KB, MB or GB, for example `20MB`.
+When combined with a time based triggering policy the file pattern must contain a `%i`
+otherwise the target file will be overwritten on every rollover as the SizeBased Triggering Policy
+will not cause the timestamp value in the file name to change. When used without a time based
+triggering policy the SizeBased Triggering Policy will cause the timestamp value to change.
TimeBased Triggering Policy
@@ -2799,10 +2802,36 @@
resolved at runtime such as is shown in the example below.
The default rollover strategy supports three variations for incrementing
-the counter. The first is the "fixed window" strategy. To illustrate how
-it works, suppose that the min attribute is set to 1, the max attribute
-is set to 3, the file name is "foo.log", and the file name pattern is
-"foo-%i.log".
+the counter. To illustrate how it works, suppose that the min attribute
+is set to 1, the max attribute is set to 3, the file name is "foo.log",
+and the file name pattern is "foo-%i.log".
+
+[cols=",,,",options="header",]
+|=======================================================================
+|Number of rollovers |Active output target |Archived log files
+|Description
+|0 |foo.log |- |All logging is going to the initial file.
+
+|1 |foo.log |foo-1.log |During the first rollover foo.log is renamed to
+foo-1.log. A new foo.log file is created and starts being written to.
+
+|2 |foo.log |foo-2.log, foo-1.log |During the second rollover foo.log is
+renamed to foo-2.log. A new foo.log file is created and starts being
+written to.
+
+|3 |foo.log |foo-3.log, foo-2.log, foo-1.log |During the third rollover
+foo.log is renamed to foo-3.log. A new foo.log file is created and
+starts being written to.
+
+|4 |foo.log |foo-3.log, foo-2.log, foo-1.log |In the fourth and
+subsequent rollovers, foo-1.log is deleted, foo-2.log is renamed to
+foo-1.log, foo-3.log is renamed to foo-2.log and foo.log is renamed to
+foo-3.log. A new foo.log file is created and starts being written to.
+|=======================================================================
+
+By way of contrast, when the fileIndex attribute is set to "min" but all
+the other settings are the same the "fixed window" strategy will be
+performed.
[cols=",,,",options="header",]
|=======================================================================
@@ -2828,32 +2857,6 @@
foo-1.log. A new foo.log file is created and starts being written to.
|=======================================================================
-By way of contrast, when the fileIndex attribute is set to "max" but all
-the other settings are the same the following actions will be performed.
-
-[cols=",,,",options="header",]
-|=======================================================================
-|Number of rollovers |Active output target |Archived log files
-|Description
-|0 |foo.log |- |All logging is going to the initial file.
-
-|1 |foo.log |foo-1.log |During the first rollover foo.log is renamed to
-foo-1.log. A new foo.log file is created and starts being written to.
-
-|2 |foo.log |foo-1.log, foo-2.log |During the second rollover foo.log is
-renamed to foo-2.log. A new foo.log file is created and starts being
-written to.
-
-|3 |foo.log |foo-1.log, foo-2.log, foo-3.log |During the third rollover
-foo.log is renamed to foo-3.log. A new foo.log file is created and
-starts being written to.
-
-|4 |foo.log |foo-1.log, foo-2.log, foo-3.log |In the fourth and
-subsequent rollovers, foo-1.log is deleted, foo-2.log is renamed to
-foo-1.log, foo-3.log is renamed to foo-2.log and foo.log is renamed to
-foo-3.log. A new foo.log file is created and starts being written to.
-|=======================================================================
-
Finally, as of release 2.8, if the fileIndex attribute is set to "nomax"
then the min and max values will be ignored and file numbering will
increment by 1 and each rollover will have an incrementally higher value
@@ -2890,7 +2893,7 @@
The DirectWriteRolloverStrategy causes log events to be written directly
to files represented by the file pattern. With this strategy file
renames are not performed. If the size-based triggering policy causes
-multiple files to be written durring the specified time period they will
+multiple files to be written during the specified time period they will
be numbered starting at one and continually incremented until a
time-based rollover occurs.
@@ -3478,7 +3481,7 @@
attribute view.
|fileGroup |String a|
-File group to define whene action is executed.
+File group to define when action is executed.
Underlying files system shall support
https://docs.oracle.com/javase/7/docs/api/java/nio/file/attribute/PosixFileAttributeView.html[POSIX]
@@ -3978,7 +3981,7 @@
== SMTPAppender
As of Log4j 2.11.0, Simple Mail Transfer Protocol (SMTP) support has
-moved from the existing module `logj-core` to the new module
+moved from the existing module `log4j-core` to the new module
`log4j-smtp`.
Sends an e-mail when a specific logging event occurs, typically on
@@ -4112,7 +4115,13 @@
The `SocketAppender` is an OutputStreamAppender that writes its output
to a remote destination specified by a host and port. The data can be
sent over either TCP or UDP and can be sent in any format. You can
-optionally secure communication with link:#SSL[SSL].
+optionally secure communication with link:#SSL[SSL]. Note that the TCP and SSL
+variants write to the socket as a stream and do not expect response from
+the target destination. Due to limitations in the TCP protocol that
+means that when the target server closes its connection some log events
+may continue to appear to succeed until a closed connection exception
+is raised, causing those events to be lost. If guaranteed delivery is
+required a protocol that requires acknowledgements must be used.
.`SocketAppender` Parameters
[cols=",,",options="header",]
@@ -4124,7 +4133,9 @@
log events. This parameter is required.
|port |integer |The port on the host that is listening for log events.
-This parameter must be specified.
+This parameter must be specified.If the host name resolves to multiple
+IP addresses the TCP and SSL variations will fail over to the next IP
+address when a connection is lost.
|protocol |String |"TCP" (default), "SSL" or "UDP".
@@ -4498,7 +4509,7 @@
== JeroMQAppender
As of Log4j 2.11.0, ZeroMQ/JeroMQ support has moved from the existing
-module `logj-core` to the new module `log4j-jeromq`.
+module `log4j-core` to the new module `log4j-jeromq`.
The ZeroMQ appender uses the https://github.com/zeromq/jeromq[JeroMQ]
library to send log events to one or more ZeroMQ endpoints.
diff --git a/src/site/asciidoc/manual/async.adoc b/src/site/asciidoc/manual/async.adoc
index 9735ae0..c89921b 100644
--- a/src/site/asciidoc/manual/async.adoc
+++ b/src/site/asciidoc/manual/async.adoc
@@ -205,11 +205,11 @@
system.
When the application is logging faster than the underlying appender can
-keep up with for a long enough time to fill up the queue, the behavious
+keep up with for a long enough time to fill up the queue, the behaviour
is determined by the
link:../log4j-core/apidocs/org/apache/logging/log4j/core/async/AsyncQueueFullPolicy.html[AsyncQueueFullPolicy].
-|log4j2.asyncLoggerWaitStrategy
+|[[asyncLoggerWaitStrategy]]log4j2.asyncLoggerWaitStrategy
|`Timeout`
|Valid values: Block,
Timeout, Sleep, Yield.
@@ -232,6 +232,32 @@
performance and CPU resource, but may use more CPU than Sleep in order
to get the message logged to disk sooner.
+|log4j2.asyncLoggerTimeout
+|`10`
+|Timeout in milliseconds of `TimeoutBlockingWaitStrategy`. See
+link:#asyncLoggerWaitStrategy[WaitStrategy System Property] for details.
+
+|log4j2.asyncLoggerSleepTimeNs
+|`100`
+|Sleep time (in nanoseconds) of `SleepingWaitStrategy`. See
+link:#asyncLoggerWaitStrategy[WaitStrategy System Property] for details.
+
+|log4j2.asyncLoggerRetries
+|`200`
+|Total number of spin cycles and `Thread.yield()` cycles of `SleepingWaitStrategy`. See
+link:#asyncLoggerWaitStrategy[WaitStrategy System Property] for details.
+
+
+
+|AsyncLogger.SynchronizeEnqueueWhenQueueFull
+|`true`
+|Synchronizes access to the Disruptor ring buffer for blocking enqueue operations when the queue is full.
+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 ring buffer 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. Setting this value
+to `false` may lead to very high CPU utilization when the async logging queue is full.
+
|log4j2.asyncLoggerThreadNameStrategy
|`CACHED`
|Valid values: CACHED, UNCACHED.
@@ -366,7 +392,7 @@
is determined by the
link:../log4j-core/apidocs/org/apache/logging/log4j/core/async/AsyncQueueFullPolicy.html[AsyncQueueFullPolicy].
-|log4j2.asyncLoggerConfigWaitStrategy
+|[[asyncLoggerConfigWaitStrategy]]log4j2.asyncLoggerConfigWaitStrategy
|`Timeout`
|Valid values: Block,
Timeout, Sleep, Yield. +
@@ -388,6 +414,31 @@
events after an initially spinning. Yield is a good compromise between
performance and CPU resource, but may use more CPU than Sleep in order
to get the message logged to disk sooner.
+
+|log4j2.asyncLoggerConfigTimeout
+|`10`
+|Timeout in milliseconds of `TimeoutBlockingWaitStrategy`. See
+link:#asyncLoggerConfigWaitStrategy[WaitStrategy System Property] for details.
+
+|log4j2.asyncLoggerConfigSleepTimeNs
+|`100`
+|Sleep time (in nanoseconds) of `SleepingWaitStrategy`. See
+link:#asyncLoggerConfigWaitStrategy[WaitStrategy System Property] for details.
+
+|log4j2.asyncLoggerConfigRetries
+|`200`
+|Total number of spin cycles and `Thread.yield()` cycles of `SleepingWaitStrategy`. See
+link:#asyncLoggerConfigWaitStrategy[WaitStrategy System Property] for details.
+
+|AsyncLoggerConfig.SynchronizeEnqueueWhenQueueFull
+|`true`
+|Synchronizes access to the Disruptor ring buffer for blocking enqueue operations when the queue is full.
+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 ring buffer 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. Setting this value
+to `false` may lead to very high CPU utilization when the async logging queue is full.
+
|===
There are also a few system properties that can be used to maintain
diff --git a/src/site/asciidoc/manual/configuration.adoc b/src/site/asciidoc/manual/configuration.adoc
index aef2a6b..bac83d3 100644
--- a/src/site/asciidoc/manual/configuration.adoc
+++ b/src/site/asciidoc/manual/configuration.adoc
@@ -55,7 +55,9 @@
1. Log4j will inspect the `"log4j.configurationFile"` system property
and, if set, will attempt to load the configuration using the
-`ConfigurationFactory` that matches the file extension.
+`ConfigurationFactory` that matches the file extension. Note that this
+is not restricted to a location on the local file system and may contain
+a URL.
2. If no system property is set the properties ConfigurationFactory
will look for `log4j2-test.properties` in the classpath.
3. If no such file is found the YAML ConfigurationFactory will look for
@@ -399,7 +401,7 @@
only Advertiser plugin provided is "multicastdns".
|dest
-|Either "err", which will send output to stderr, or a file path or URL.
+|Either "err" for stderr, "out" for stdout, a file path, or a URL.
|monitorInterval
|The minimum amount of time, in seconds, that must
@@ -456,7 +458,7 @@
|===
[[XML]]
-=== XML
+=== Configuration with XML
Log4j can be configured using two XML flavors; concise and strict. The
concise format makes configuration very easy as the element names match
@@ -734,6 +736,7 @@
appenders:
Console:
name: STDOUT
+ target: SYSTEM_OUT
PatternLayout:
Pattern: "%m%n"
File:
@@ -773,8 +776,90 @@
Additional link:../runtime-dependencies.html[runtime dependencies] are
required for using YAML configuration files.
+[#Properties]
+=== Configuration with Properties
+
+As of version 2.4, Log4j now supports configuration via properties
+files. Note that the property syntax is NOT the same as the syntax used
+in Log4j 1. Like the XML and JSON configurations, properties
+configurations define the configuration in terms of plugins and
+attributes to the plugins.
+
+Prior to version 2.6, the properties configuration requires that you
+list the identifiers of the appenders, filters and loggers, in a comma
+separated list in properties with those names. Each of those components
+will then be expected to be defined in sets of properties that begin
+with _component.<.identifier>._. The identifier does not have to match
+the name of the component being defined but must uniquely identify all
+the attributes and subcomponents that are part of the component. If the
+list of identifiers is not present the identifier must not contain a '.'.
+Each individual component MUST have a "type" attribute specified that
+identifies the component's Plugin type.
+
+As of version 2.6, this list of identifiers is no longer required as
+names are inferred upon first usage, however if you wish to use more
+complex identifies you must still use the list. If the list is present
+it will be used.
+
+Unlike the base components, when creating subcomponents you cannot
+specify an element containing a list of identifiers. Instead, you must
+define the wrapper element with its type as is shown in the policies
+definition in the rolling file appender below. You then define each of
+the subcomponents below that wrapper element, as the
+TimeBasedTriggeringPolicy and SizeBasedTriggeringPolicy are defined
+below.
+
+Properties configuration files support the advertiser, monitorInterval,
+name, packages, shutdownHook, shutdownTimeout, status, verbose, and dest
+attributes. See link:#ConfigurationSyntax[Configuration Syntax] for the
+definitions of these attributes.
+
+[source,properties]
+----
+status = error
+dest = err
+name = PropertiesConfig
+
+property.filename = target/rolling/rollingtest.log
+
+filter.threshold.type = ThresholdFilter
+filter.threshold.level = debug
+
+appender.console.type = Console
+appender.console.name = STDOUT
+appender.console.layout.type = PatternLayout
+appender.console.layout.pattern = %m%n
+appender.console.filter.threshold.type = ThresholdFilter
+appender.console.filter.threshold.level = error
+
+appender.rolling.type = RollingFile
+appender.rolling.name = RollingFile
+appender.rolling.fileName = ${filename}
+appender.rolling.filePattern = target/rolling2/test1-%d{MM-dd-yy-HH-mm-ss}-%i.log.gz
+appender.rolling.layout.type = PatternLayout
+appender.rolling.layout.pattern = %d %p %C{1.} [%t] %m%n
+appender.rolling.policies.type = Policies
+appender.rolling.policies.time.type = TimeBasedTriggeringPolicy
+appender.rolling.policies.time.interval = 2
+appender.rolling.policies.time.modulate = true
+appender.rolling.policies.size.type = SizeBasedTriggeringPolicy
+appender.rolling.policies.size.size=100MB
+appender.rolling.strategy.type = DefaultRolloverStrategy
+appender.rolling.strategy.max = 5
+
+logger.rolling.name = com.example.my.app
+logger.rolling.level = debug
+logger.rolling.additivity = false
+logger.rolling.appenderRef.rolling.ref = RollingFile
+
+rootLogger.level = info
+rootLogger.appenderRef.stdout.ref = STDOUT
+
+----
+
+
[#Loggers]
-=== Configuring loggers
+=== Configuring Loggers
An understanding of how loggers work in Log4j is critical before trying
to configure them. Please reference the Log4j
@@ -790,6 +875,16 @@
additivity attribute may be assigned a value of true or false. If the
attribute is omitted the default value of true will be used.
+Capturing location information (the class name, file name, method name, and line number of the caller)
+can be slow. Log4j tries to optimize this by reducing the size of the stack that must be traversed
+to find the caller of the logging method. It does this by determining if any component that might
+be accessed requires location information. This can cause performance issues if a logger is configured
+at a level like trace or debug with the expectation that most logs will be filtered on an Appender
+reference or Appender as Log4j will calculate the location information even though the log event
+is going to be discarded. To disable this behavior the `includeLocation` attribute
+can be set to false on the LoggerConfig. This will cause Log4j to defer calculating the location
+information until absolutely necessary.
+
A LoggerConfig (including the root LoggerConfig) can be configured with
properties that will be added to the properties copied from the
ThreadContextMap. These properties can be referenced from Appenders,
@@ -817,7 +912,7 @@
=== Configuring Appenders
An appender is configured either using the specific appender plugin's
-name or with an appender element and the type attibute containing the
+name or with an appender element and the type attribute containing the
appender plugin's name. In addition each appender must have a name
attribute specified with a value that is unique within the set of
appenders. The name will be used by loggers to reference the appender as
@@ -905,87 +1000,6 @@
</Configuration>
----
-[#Properties]
-=== Configuration with Properties
-
-As of version 2.4, Log4j now supports configuration via properties
-files. Note that the property syntax is NOT the same as the syntax used
-in Log4j 1. Like the XML and JSON configurations, properties
-configurations define the configuration in terms of plugins and
-attributes to the plugins.
-
-Prior to version 2.6, the properties configuration requires that you
-list the identifiers of the appenders, filters and loggers, in a comma
-separated list in properties with those names. Each of those components
-will then be expected to be defined in sets of properties that begin
-with _component.<.identifier>._. The identifier does not have to match
-the name of the component being defined but must uniquely identify all
-the attributes and subcomponents that are part of the component. If the
-list of identifiers is not present the identier must not contain a '.'.
-Each individual component MUST have a "type" attribute specified that
-identifies the component's Plugin type.
-
-As of version 2.6, this list of identifiers is no longer required as
-names are inferred upon first usage, however if you wish to use more
-complex identifies you must still use the list. If the list is present
-it will be used.
-
-Unlike the base components, when creating subcomponents you cannot
-specify an element containing a list of identifiers. Instead, you must
-define the wrapper element with its type as is shown in the policies
-definition in the rolling file appender below. You then define each of
-the subcomponents below that wrapper element, as the
-TimeBasedTriggeringPolicy and SizeBasedTriggeringPolicy are defined
-below.
-
-Properties configuration files support the advertiser, monitorInterval,
-name, packages, shutdownHook, shutdownTimeout, status, verbose, and dest
-attributes. See link:#ConfigurationSyntax[Configuration Syntax] for the
-definitions of these attributes.
-
-[source,properties]
-----
-status = error
-dest = err
-name = PropertiesConfig
-
-property.filename = target/rolling/rollingtest.log
-
-filter.threshold.type = ThresholdFilter
-filter.threshold.level = debug
-
-appender.console.type = Console
-appender.console.name = STDOUT
-appender.console.layout.type = PatternLayout
-appender.console.layout.pattern = %m%n
-appender.console.filter.threshold.type = ThresholdFilter
-appender.console.filter.threshold.level = error
-
-appender.rolling.type = RollingFile
-appender.rolling.name = RollingFile
-appender.rolling.fileName = ${filename}
-appender.rolling.filePattern = target/rolling2/test1-%d{MM-dd-yy-HH-mm-ss}-%i.log.gz
-appender.rolling.layout.type = PatternLayout
-appender.rolling.layout.pattern = %d %p %C{1.} [%t] %m%n
-appender.rolling.policies.type = Policies
-appender.rolling.policies.time.type = TimeBasedTriggeringPolicy
-appender.rolling.policies.time.interval = 2
-appender.rolling.policies.time.modulate = true
-appender.rolling.policies.size.type = SizeBasedTriggeringPolicy
-appender.rolling.policies.size.size=100MB
-appender.rolling.strategy.type = DefaultRolloverStrategy
-appender.rolling.strategy.max = 5
-
-logger.rolling.name = com.example.my.app
-logger.rolling.level = debug
-logger.rolling.additivity = false
-logger.rolling.appenderRef.rolling.ref = RollingFile
-
-rootLogger.level = info
-rootLogger.appenderRef.stdout.ref = STDOUT
-
-----
-
[#PropertySubstitution]
== Property Substitution
@@ -1051,12 +1065,25 @@
`${prefix:name}` where the prefix identifies tells Log4j that variable
name should be evaluated in a specific context. See the
link:lookups.html[Lookups] manual page for more details. The contexts
-that are built in to Logj4 are:
+that are built in to Log4j are:
[cols="1m,5"]
|===
|Prefix |Context
+ <tr>
+ <td>base64</td>
+ <td>
+ Base64 encoded data. The format is <code>${dollar}{base64:Base64_encoded_data}</code>.
+ For example:
+ <code>${dollar}{base64:SGVsbG8gV29ybGQhCg==}</code> yields <code>Hello World!</code>.
+ </td>
+ </tr>
+
+|base64
+|Base64 encoded data. The format is `${base64:Base64_encoded_data}`.
+For example: `${base64:SGVsbG8gV29ybGQhCg==}` yields `Hello World!`.
+
|bundle
|Resource bundle. The format is `${bundle:BundleName:BundleKey}`.
The bundle name follows package naming conventions, for example:
@@ -1103,11 +1130,21 @@
`${sys:some.property:-default_value}`.
|===
-A default property map can be declared in the configuration file. If the
-value cannot be located in the specified lookup the value in the default
-property map will be used. The default map is pre-populated with a value
+[#DefaultProperties]
+== Default Properties
+A default property map can be declared in the configuration file by placing a Properties
+element directly after the Configuration element and before any Loggers, Filters,
+Appenders, etc. are declared. If the value cannot be located in the specified lookup the
+value in the default property map will be used. The default map is pre-populated with a value
for "hostName" that is the current system's host name or IP address and
-the "contextName" with is the value of the current logging context.
+the "contextName" with is the value of the current logging context. See many places
+a Properties element is used in this section for examples.
+
+Default properties may also be specified in the Lookup by using the syntax `${lookupName:key:-defaultValue}`.
+In some cases the key might contain a leading '-'. When this is the case an escape character must be
+included, such as ``${main:\--file:-app.properties}`. This would use the
+`MainMapLookup` for a key named `--file`. If the key is not found then
+<code>app.properties</code> would be used as the default value.
[#RuntimeLookup]
== Lookup Variables with Multiple Leading '$' Characters
@@ -1361,10 +1398,11 @@
== Composite Configuration
Log4j allows multiple configuration files to be used by specifying them
-as a list of comma separated file paths on log4j.configurationFile. The
-merge logic can be controlled by specifying a class that implements the
-MergeStrategy interface on the log4j.mergeStrategy property. The default
-merge strategy will merge the files using the following rules:
+as a list of comma separated file paths on log4j.configurationFile or,
+when using urls, by adding secondary configuration locations as query
+parameters named "override". The merge logic can be controlled by specifying
+a class that implements the MergeStrategy interface on the log4j.mergeStrategy
+property. The default merge strategy will merge the files using the following rules:
1. The global configuration attributes are aggregated with those in
later configurations replacing those in previous configurations, with
@@ -1574,7 +1612,7 @@
@Test
public void testSomeAwesomeFeature() {
- final LoggerContext ctx = init.getContext();
+ final LoggerContext ctx = init.getLoggerContext();
final Logger logger = init.getLogger("org.apache.logging.log4j.my.awesome.test.logger");
final Configuration cfg = init.getConfiguration();
final ListAppender app = init.getListAppender("List");
@@ -1651,7 +1689,9 @@
|LOG4J_CONFIGURATION_FILE
|
|Path to an Log4j 2 configuration file. May
-also contain a comma separated list of configuration file names.
+also contain a comma separated list of configuration file names. May contain a URL.
+When specified as a URL the "override" query parameter may be used to specify additional
+configuration file locations.
|[[debug]]log4j2.debug +
([[log4j2.debug]]log4j2.debug)
@@ -1661,9 +1701,9 @@
internal logging to the console if system property `log4j2.debug` is
defined (with any or no value).
-|[[mergeFactory]]log4j2.mergeFactory +
-([[log4j.mergeFactory]]log4j.mergeFactory)
-|LOG4J_MERGE_FACTORY
+|[[mergeStrategy]]log4j2.mergeStrategy +
+([[log4j.mergeStrategy]]log4j.mergeStrategy)
+|LOG4J_MERGE_STRATEGY
|
|The name of the class that implements the MergeStrategy interface. If not
specified `DefaultMergeStrategy` will be used when creating a CompositeConfiguration.
@@ -2003,7 +2043,7 @@
|[[asyncLoggerRingBufferSize]]log4j2.asyncLoggerRingBufferSize +
([[AsyncLogger.RingBufferSize]]AsyncLogger.RingBufferSize)
|LOG4J_ASYNC_LOGGER_RING_BUFFER_SIZE
-|256 * 1024
+|256 * 1024 or 4 * 1024 in garbage-free mode
|See
link:async.html#SysPropsAllAsync[Async Logger System Properties] for
details.
@@ -2016,6 +2056,37 @@
link:async.html#SysPropsAllAsync[Async Logger System Properties] for
details.
+|[[asyncLoggerTimeout]]log4j2.asyncLoggerTimeout +
+([[AsyncLogger.Timeout]]AsyncLogger.Timeout)
+|LOG4J_ASYNC_LOGGER_TIMEOUT
+|10
+|See
+link:async.html#SysPropsAllAsync[Async Logger System Properties] for
+details.
+
+|[[asyncLoggerSleepTimeNs]]log4j2.asyncLoggerSleepTimeNs +
+([[AsyncLogger.SleepTimeNs]]AsyncLogger.SleepTimeNs)
+|LOG4J_ASYNC_LOGGER_SLEEP_TIME_NS
+|100
+|See
+link:async.html#SysPropsAllAsync[Async Logger System Properties] for
+details.
+
+|[[asyncLoggerRetries]]log4j2.asyncLoggerRetries +
+([[AsyncLogger.Retries]]AsyncLogger.Retries)
+|LOG4J_ASYNC_LOGGER_RETRIES
+|200
+|See
+link:async.html#SysPropsAllAsync[Async Logger System Properties] for
+details.
+
+|[[AsyncLogger.SynchronizeEnqueueWhenQueueFull]]AsyncLogger.SynchronizeEnqueueWhenQueueFull
+|ASYNC_LOGGER_SYNCHRONIZE_ENQUEUE_WHEN_QUEUE_FULL
+|true
+|See
+link:async.html#SysPropsAllAsync[Async Logger System Properties] for
+details.
+
|[[asyncLoggerThreadNameStrategy]]log4j2.asyncLoggerThreadNameStrategy +
([[AsyncLogger.ThreadNameStrategy]]AsyncLogger.ThreadNameStrategy)
|LOG4J_ASYNC_LOGGER_THREAD_NAME_STRATEGY
@@ -2035,7 +2106,7 @@
|[[asyncLoggerConfigRingBufferSize]]log4j2.asyncLoggerConfigRingBufferSize +
([[AsyncLoggerConfig.RingBufferSize]]AsyncLoggerConfig.RingBufferSize)
|LOG4J_ASYNC_LOGGER_CONFIG_RING_BUFFER_SIZE
-|256 * 1024
+|256 * 1024 or 4 * 1024 in garbage-free mode
|See
link:async.html#SysPropsMixedSync-Async[Mixed Async/Synchronous Logger
System Properties] for details.
@@ -2048,6 +2119,38 @@
link:async.html#SysPropsMixedSync-Async[Mixed Async/Synchronous Logger
System Properties] for details.
+|[[asyncLoggerConfigTimeout]]log4j2.asyncLoggerConfigTimeout +
+([[AsyncLoggerConfig.Timeout]]AsyncLoggerConfig.Timeout)
+|LOG4J_ASYNC_LOGGER_CONFIG_TIMEOUT
+|10
+|See
+link:async.html#SysPropsMixedSync-Async[Mixed Async/Synchronous Logger
+System Properties] for details.
+
+|[[asyncLoggerConfigSleepTimeNs]]log4j2.asyncLoggerConfigSleepTimeNs +
+([[AsyncLoggerConfig.SleepTimeNs]]AsyncLoggerConfig.SleepTimeNs)
+|LOG4J_ASYNC_LOGGER_CONFIG_SLEEP_TIME_NS
+|100
+|See
+link:async.html#SysPropsMixedSync-Async[Mixed Async/Synchronous Logger
+System Properties] for details.
+
+|[[asyncLoggerConfigRetries]]log4j2.asyncLoggerConfigRetries +
+([[AsyncLoggerConfig.Retries]]AsyncLoggerConfig.Retries)
+|LOG4J_ASYNC_LOGGER_CONFIG_RETRIES
+|200
+|See
+link:async.html#SysPropsMixedSync-Async[Mixed Async/Synchronous Logger
+System Properties] for details.
+
+
+|[[AsyncLoggerConfig.SynchronizeEnqueueWhenQueueFull]]AsyncLoggerConfig.SynchronizeEnqueueWhenQueueFull
+|ASYNC_LOGGER_CONFIG_SYNCHRONIZE_ENQUEUE_WHEN_QUEUE_FULL
+|true
+|See
+link:async.html#SysPropsMixedSync-Async[Mixed Async/Synchronous Logger
+System Properties] for details.
+
|[[julLoggerAdapter]]log4j2.julLoggerAdapter +
([[log4j.jul.LoggerAdapter]]log4j.jul.LoggerAdapter)
|LOG4J_JUL_LOGGER_ADAPTER
@@ -2056,7 +2159,7 @@
|Default LoggerAdapter to use in the JUL adapter.
By default, if log4j-core is available, then the class
`org.apache.logging.log4j.jul .CoreLoggerAdapter` will be used.
-Otherwise, the `ApiLogggerAdapter` will be used. Custom implementations
+Otherwise, the `ApiLoggerAdapter` will be used. Custom implementations
must provide a public default constructor.
|[[formatMsgAsync]]log4j2.formatMsgAsync +
@@ -2197,4 +2300,84 @@
|Disables message
pattern lookups globally when set to `true`. This is equivalent to
defining all message patterns using `%m{nolookups}`.
+
+|[[trustStoreLocation]]log4j2.trustStoreLocation +
+([[log4j2.trustStoreLocation]]log4j2.trustStoreLocation)
+|LOG4J_TRUST_STORE_LOCATION
+|
+|The location of the trust store. If not provided the default trust store will be used.
+
+|[[trustStorePassword]]log4j2.trustStorePassword +
+([[log4j2.trustStorePassword]]log4j2.trustStorePassword)
+|LOG4J_TRUST_STORE_PASSWORD
+|
+|Password needed to access the trust store.
+
+|[[trustStorePasswordFile]]log4j2.trustStorePasswordFile +
+([[log4j2.trustStorePasswordFile]]log4j2.trustStorePasswordFile)
+|LOG4J_TRUST_STORE_PASSWORD_FILE
+|
+|The location of a file that contains the password for the trust store.
+
+|[[trustStorePasswordEnvironmentVariable]]log4j2.trustStorePasswordEnvironmentVariable +
+([[log4j2.trustStorePasswordEnvironmentVariable]]log4j2.trustStorePasswordEnvironmentVariable)
+|LOG4J_TRUST_STORE_PASSWORD_ENVIRONMENT_VARIABLE
+|
+|The name of the environment variable that contains the trust store password.
+
+|[[trustStoreType]]log4j2.trustStoreType +
+([[log4j2.trustStoreType]]log4j2.trustStoreType)
+|LOG4J_TRUST_STORE_TYPE
+|
+|The type of key store used for the trust store.
+
+|[[trustStoreKeyManagerFactoryAlgorithm]]log4j2.trustStoreKeyManagerFactoryAlgorithm +
+([[log4j2.trustStoreKeyManagerFactoryAlgorithm]]log4j2.trustStoreKeyStoreFactoryAlgorithm)
+|LOG4J_TRUST_STORE_KEY_MANAGER_FACTORY_ALGORITHM
+|
+|Java cryptographic algorithm.
+
+|[[keyStoreLocation]]log4j2.keyStoreLocation +
+([[log4j2.trustStoreLocation]]log4j2.trustStoreLocation)
+|LOG4J_KEY_STORE_LOCATION
+|
+|The location of the key store. If not provided the default key store will be used.
+
+|[[keyStorePassword]]log4j2.keyStorePassword +
+([[log4j2.keyStorePassword]]log4j2.keyStorePassword)
+|LOG4J_KEY_STORE_PASSWORD
+|
+|Password needed to access the trust store.
+
+|[[keyStorePasswordFile]]log4j2.keyStorePasswordFile +
+([[log4j2.keyStorePasswordFile]]log4j2.keyStorePasswordFile)
+|LOG4J_KEY_STORE_PASSWORD_FILE
+|
+|The location of a file that contains the password for the key store.
+
+|[[keyStorePasswordEnvironmentVariable]]log4j2.keyStorePasswordEnvironmentVariable +
+([[log4j2.keyStorePasswordEnvironmentVariable]]log4j2.keyStorePasswordEnvironmentVariable)
+|LOG4J_KEY_STORE_PASSWORD_ENVIRONMENT_VARIABLE
+|
+|The name of the environment variable that contains the key store password.
+
+|[[keyStoreType]]log4j2.keyStoreType +
+([[log4j2.keyStoreType]]log4j2.keyStoreType)
+|LOG4J_KEY_STORE_TYPE
+|
+|The type of key store used for the trust store.
+
+|[[keyStoreKeyManagerFactoryAlgorithm]]log4j2.keyStoreKeyManagerFactoryAlgorithm +
+([[log4j2.keyStoreKeyManagerFactoryAlgorithm]]log4j2.keyStoreKeyStoreFactoryAlgorithm)
+|LOG4J_KEY_STORE_KEY_MANAGER_FACTORY_ALGORITHM
+|
+|Java cryptographic algorithm.
+
+
+|[[sslVerifyHostName]]log4j2.sslVerifyHostName +
+([[log4j2.sslVerifyHostName]]log4j2.sslVerifyHostName)
+|LOG4J_SSL_VERIFY_HOSTNAME
+|false
+|true or false if the host name should be verified
+
|===
diff --git a/src/site/asciidoc/manual/extending.adoc b/src/site/asciidoc/manual/extending.adoc
index db5a6f9..f8ec37f 100644
--- a/src/site/asciidoc/manual/extending.adoc
+++ b/src/site/asciidoc/manual/extending.adoc
@@ -26,15 +26,15 @@
The `LoggerContextFactory` binds the Log4j API to its implementation.
The Log4j `LogManager` locates a `LoggerContextFactory` by using
-java.util.ServiceLoader to locate all instances of
+`java.util.ServiceLoader` to locate all instances of
`org.apache.logging.log4j.spi.Provider`. Each implementation must
-provide a class that extends`org.apache.logging.log4j.spi.Provider` and
+provide a class that extends `org.apache.logging.log4j.spi.Provider` and
should have a no-arg constructor that delegates to Provider's
constructor passing the Priority, the API versions it is compatible
with, and the class that implements
`org.apache.logging.log4j.spi.LoggerContextFactory`. Log4j will compare
-the current API version and if it is compatible the implementation will
-be added to the list of providers. The API version in
+the current API version and if it is compatible the implementation
+will be added to the list of providers. The API version in
`org.apache.logging.log4j.LogManager` is only changed when a feature is
added to the API that implementations need to be aware of. If more than
one valid implementation is located the value for the Priority will be
@@ -47,15 +47,15 @@
Applications may change the LoggerContextFactory that will be used by
1. Create a binding to the logging implementation.
-.. Implement a new `LoggerContextFactory`.
-.. Implement a class that extends `org.apache.logging.spi.Provider.`
+.. Implement a new link:../log4j-core/apidocs/org/apache/logging/log4j/core/impl/Log4jContextFactory.html[`LoggerContextFactory`].
+.. Implement a class that extends link:../log4j-core/apidocs/org/apache/logging/spi/Provider.html[`org.apache.logging.spi.Provider`]
with a no-arg constructor that calls super-class's constructor with the
Priority, the API version(s), `LoggerContextFactory` class, and
-optionally, a `ThreadContextMap` implementation class.
+optionally, a link:../log4j-core/apidocs/org/apache/logging/log4j/spi/ThreadContextMap.html[`ThreadContextMap`] implementation class.
.. Create a `META-INF/services/org.apache.logging.spi.Provider` file
that contains the name of the class that implements
`org.apache.logging.spi.Provider`.
-2. Setting the system property log4j2.loggerContextFactory to the name
+2. Setting the system property "log4j2.loggerContextFactory" to the name
of the `LoggerContextFactory` class to use.
3. Setting the property "log4j2.loggerContextFactory" in a properties
file named "log4j2.LogManager.properties" to the name of the
@@ -82,7 +82,7 @@
common LoggerContext.
link:../log4j-core/apidocs/org/apache/logging/log4j/core/selector/ClassLoaderContextSelector.html[`ClassLoaderContextSelector`]::
Associates LoggerContexts with the ClassLoader that created the caller
- of the getLogger call. This is the default ContextSelector.
+ of the getLogger(...) call. This is the default ContextSelector.
link:../log4j-core/apidocs/org/apache/logging/log4j/core/selector/JndiContextSelector.html[`JndiContextSelector`]::
Locates the LoggerContext by querying JNDI.
link:../log4j-core/apidocs/org/apache/logging/log4j/core/async/AsyncLoggerContextSelector.html[`AsyncLoggerContextSelector`]::
@@ -106,18 +106,16 @@
The second method is by defining the `ConfigurationFactory` as a `Plugin`.
All the ConfigurationFactories are then processed in order. Each factory
-is called on its `getSupportedTypes` method to determine the file
+is called on its `getSupportedTypes()` method to determine the file
extensions it supports. If a configuration file is located with one of
the specified file extensions then control is passed to that
-`ConfigurationFactory` to load the configuration and create the
-`Configuration` object.
+`ConfigurationFactory` to load the configuration and create the link:../log4j-core/apidocs/org/apache/logging/log4j/core/config/Configuration.html[`Configuration`] object.
-Most `Configuration` extend the `BaseConfiguration` class. This class
-expects that the subclass will process the configuration file and create
+Most `Configuration` extend the link:../log4j-core/apidocs/org/apache/logging/log4j/core/config/AbstractConfiguration.html[`AbstractConfiguration`] class. This class expects that the subclass will process the configuration file and create
a hierarchy of `Node` objects. Each `Node` is fairly simple in that it
consists of the name of the node, the name/value pairs associated with
the node, The `PluginType` of the node and a List of all of its child
-Nodes. `BaseConfiguration` will then be passed the `Node` tree and
+Nodes. `Configuration` will then be passed the `Node` tree and
instantiate the configuration objects from that.
[source,java]
@@ -157,7 +155,7 @@
`LoggerConfig` objects are where Loggers created by applications tie into
the configuration. The Log4j implementation requires that all
-LoggerConfigs be based on the LoggerConfig class, so applications
+LoggerConfigs are based on the LoggerConfig class, so applications
wishing to make changes must do so by extending the `LoggerConfig` class.
To declare the new `LoggerConfig`, declare it as a Plugin of type "Core"
and providing the name that applications should specify as the element
@@ -173,8 +171,8 @@
public static class RootLogger extends LoggerConfig {
@PluginFactory
- public static LoggerConfig createLogger(@PluginAttribute(value = "additivity", defaultBooleanValue = true) boolean additivity,
- @PluginAttribute(value = "level", defaultStringValue = "ERROR") Level level,
+ public static LoggerConfig createLogger(@PluginAttribute(defaultBooleanValue = true) boolean additivity,
+ @PluginAttribute(defaultStringValue = "ERROR") Level level,
@PluginElement("AppenderRef") AppenderRef[] refs,
@PluginElement("Filters") Filter filter) {
List<AppenderRef> appenderRefs = Arrays.asList(refs);
@@ -258,7 +256,7 @@
[#Filters]
== Filters
-As might be expected, Filters are the used to reject or accept log
+As might be expected, Filters are used to reject or accept log
events as they pass through the logging system. A Filter is declared
using a `@Plugin` annotation of `type` "Core" and an `elementType` of "filter".
The `name` attribute on the Plugin annotation is used to specify the name
@@ -267,7 +265,7 @@
`toString` will format the arguments to the filter as the configuration is
being processed. The Filter must also specify a `@PluginFactory` method
or `@PluginFactoryBuilder` builder class and method
-that will be called to create the Filter
+that will be called to create the Filter.
The example below shows a Filter used to reject LogEvents based upon
their logging level. Notice the typical pattern where all the filter
@@ -319,9 +317,9 @@
* @return The created ThresholdFilter.
*/
@PluginFactory
- public static ThresholdFilter createFilter(@PluginAttribute(value = "level", defaultStringValue = "ERROR") Level level,
- @PluginAttribute(value = "onMatch", defaultStringValue = "NEUTRAL") Result onMatch,
- @PluginAttribute(value = "onMismatch", defaultStringValue = "DENY") Result onMismatch) {
+ public static ThresholdFilter createFilter(@PluginAttribute(defaultStringValue = "ERROR") Level level,
+ @PluginAttribute(defaultStringValue = "NEUTRAL") Result onMatch,
+ @PluginAttribute(defaultStringValue = "DENY") Result onMismatch) {
return new ThresholdFilter(level, onMatch, onMismatch);
}
}
@@ -339,8 +337,8 @@
as "true" if the toString method renders the values of the attributes
passed to the Appender.
-Appenders must also declare a `@PluginFactory` method or `@PluginFactoryBuilder`
-builder class and method that will create the appender. The example below shows
+Appenders must also declare a `@PluginFactory` method that returns an instance
+of the appender or a builder class used to create the appender. The example below shows
an Appender named "Stub" that can be used as an initial template.
Most Appenders use Managers. A manager actually "owns" the resources,
@@ -354,23 +352,22 @@
[source,java]
----
@Plugin(name = "Stub", category = "Core", elementType = "appender", printObject = true)
-public final class StubAppender extends OutputStreamAppender {
+public final class StubAppender extends AbstractOutputStreamAppender<StubManager> {
- private StubAppender(String name, Layout layout, Filter filter, StubManager manager,
- boolean ignoreExceptions) {
+ private StubAppender(String name,
+ Layout<? extends Serializable> layout,
+ Filter filter,
+ boolean ignoreExceptions,
+ StubManager manager) {
+ super(name, layout, filter, ignoreExceptions, true, manager);
}
@PluginFactory
- public static StubAppender createAppender(@PluginAttribute("name") String name,
- @PluginAttribute("ignoreExceptions") boolean ignoreExceptions,
+ public static StubAppender createAppender(@PluginAttribute @Required(message = "No name provided for StubAppender") String name,
+ @PluginAttribute boolean ignoreExceptions,
@PluginElement("Layout") Layout layout,
@PluginElement("Filters") Filter filter) {
- if (name == null) {
- LOGGER.error("No name provided for StubAppender");
- return null;
- }
-
StubManager manager = StubManager.getStubManager(name);
if (manager == null) {
return null;
@@ -378,7 +375,7 @@
if (layout == null) {
layout = PatternLayout.createDefaultLayout();
}
- return new StubAppender(name, layout, filter, manager, ignoreExceptions);
+ return new StubAppender(name, layout, filter, ignoreExceptions, manager);
}
}
----
@@ -409,13 +406,15 @@
protected SampleLayout(boolean locationInfo, boolean properties, boolean complete,
Charset charset) {
+ super(charset);
+ // handle the boolean parameters
}
@PluginFactory
- public static SampleLayout createLayout(@PluginAttribute("locationInfo") boolean locationInfo,
- @PluginAttribute("properties") boolean properties,
- @PluginAttribute("complete") boolean complete,
- @PluginAttribute(value = "charset", defaultStringValue = "UTF-8") Charset charset) {
+ public static SampleLayout createLayout(@PluginAttribute boolean locationInfo,
+ @PluginAttribute boolean properties,
+ @PluginAttribute boolean complete,
+ @PluginAttribute(defaultStringValue = "UTF-8") Charset charset) {
return new SampleLayout(locationInfo, properties, complete, charset);
}
}
@@ -456,9 +455,18 @@
public static QueryConverter newInstance(final String[] options) {
return new QueryConverter(options);
}
+
+ @Override
+ public void format(LogEvent event, StringBuilder toAppendTo) {
+ // get the data from 'event', to the work and append the result to 'toAppendTo'.
+ }
}
----
+A pattern to use this converter could be specified as `... %q ...` or `... %q{argument} ...`.
+The "argument" will be passed as first (and only) value to the `options` parameter of the
+`newInstance(...)` method.
+
[#Plugin_Builders]
== Plugin Builders
@@ -470,7 +478,7 @@
an annotated factory method:
* Attribute names don't need to be specified if they match the field
-name.
+name or the method name with `set` or `with` prefixes stripped.
* Default values can be specified in code rather than through an
annotation (also allowing a runtime-calculated default value which isn't
allowed in annotations).
@@ -501,49 +509,45 @@
[source,java]
----
-@PluginBuilderFactory
+@PluginFactory
public static Builder newBuilder() {
return new Builder();
}
-public static class Builder implements org.apache.logging.log4j.core.util.Builder<ListAppender> {
+public static class Builder implements org.apache.logging.log4j.plugins.util.Builder<ListAppender> {
- @PluginBuilderAttribute
- @Required(message = "No name provided for ListAppender")
private String name;
-
- @PluginBuilderAttribute
private boolean entryPerNewLine;
-
- @PluginBuilderAttribute
private boolean raw;
-
- @PluginElement("Layout")
private Layout<? extends Serializable> layout;
-
- @PluginElement("Filter")
private Filter filter;
+ @PluginAttribute
+ @Required(message = "No name provided for ListAppender")
public Builder setName(final String name) {
this.name = name;
return this;
}
+ @PluginAttribute
public Builder setEntryPerNewLine(final boolean entryPerNewLine) {
this.entryPerNewLine = entryPerNewLine;
return this;
}
+ @PluginAttribute
public Builder setRaw(final boolean raw) {
this.raw = raw;
return this;
}
+ @PluginElement
public Builder setLayout(final Layout<? extends Serializable> layout) {
this.layout = layout;
return this;
}
+ @PluginElement
public Builder setFilter(final Filter filter) {
this.filter = filter;
return this;
@@ -556,23 +560,12 @@
}
----
-The only difference in annotations is using `@PluginBuilderAttribute`
-instead of `@PluginAttribute` so that default values and reflection can
-be used instead of specifying them in the annotation. Either annotation
-can be used in a builder, but the former is better suited for field
-injection while the latter is better suited for parameter injection.
-Otherwise, the same annotations (`@PluginConfiguration`,
-`@PluginElement`, `@PluginNode`, and `@PluginValue`) are all supported
-on fields. Note that a factory method is still required to supply a
-builder, and this factory method should be annotated with
-`@PluginBuilderFactory`.
-// TODO: this will change with LOG4J2-1188
-
When plugins are being constructed after a configuration has been
parsed, a plugin builder will be used if available, otherwise a plugin
factory method will be used as a fallback. If a plugin contains neither
factory, then it cannot be used from a configuration file (it can still
be used programmatically of course).
+// TODO: this will be simplified
Here is an example of using a plugin factory versus a plugin builder
programmatically:
@@ -583,25 +576,19 @@
ListAppender list2 = ListAppender.newBuilder().setName("List1").setEntryPerNewLine(true).build();
----
-[#Custom_ContextDataInjector]
-== Custom ContextDataInjector
+[#Custom_ContextDataProvider]
+== Custom ContextDataProvider
-The `ContextDataInjector` (introduced in Log4j 2.7) is responsible for
-populating the LogEvent's
-link:../log4j-core/apidocs/org/apache/logging/log4j/core/LogEvent.html#getContextData()[context
-data] with key-value pairs or replacing it completely. The default
-implementation is `ThreadContextDataInjector`, which obtains context
-attributes from the ThreadContext.
-
-Applications may replace the default `ContextDataInjector` by setting the
-value of the system property `log4j2.contextDataInjector` to the name of
-the custom `ContextDataInjector` class.
-
-Implementors should be aware there are some subtleties related to
-thread-safety and implementing a context data injector in a garbage-free
-manner. See the
-link:../log4j-core/apidocs/org/apache/logging/log4j/core/ContextDataInjector.html[`ContextDataInjector`]
-javadoc for detail.
+The link:../log4j-core/apidocs/org/apache/logging/log4j/core/util/ContextDataProvider.html[`ContextDataProvider`]
+(introduced in Log4j 2.13.2) is an interface applications and libraries can use to inject
+additional key-value pairs into the LogEvent's context data. Log4j's
+link:../log4j-core/apidocs/org/apache/logging/log4j/core/impl/ThreadContextDataInjector.html[`ThreadContextDataInjector`]
+uses `java.util.ServiceLoader` to locate and load `ContextDataProvider` instances.
+Log4j itself adds the ThreadContext data to the LogEvent using
+`org.apache.logging.log4j.core.impl.ThreadContextDataProvider`. Custom implementations
+should implement the `org.apache.logging.log4j.core.util.ContextDataProvider` interface and
+declare it as a service by defining the implmentation class in a file named
+`META-INF/services/org.apache.logging.log4j.core.util.ContextDataProvider`.
== Custom ThreadContextMap implementations
@@ -609,10 +596,10 @@
system property `log4j2.garbagefreeThreadContextMap` to true. (Log4j
must be link:garbagefree.html#Config[enabled] to use ThreadLocals.)
-Any custom `ThreadContextMap` implementation can be installed by setting
-system property `log4j2.threadContextMap` to the fully qualified class
-name of the class implementing the `ThreadContextMap` interface. By also
-implementing the `ReadOnlyThreadContextMap` interface, your custom
+Any custom link:../log4j-core/apidocs/org/apache/logging/log4j/spi/ThreadContextMap.html[`ThreadContextMap`]
+implementation can be installed by setting system property `log4j2.threadContextMap`
+to the fully qualified class name of the class implementing the `ThreadContextMap`
+interface. By also implementing the `ReadOnlyThreadContextMap` interface, your custom
`ThreadContextMap` implementation will be accessible to applications via the
link:../log4j-api/apidocs/org/apache/logging/log4j/ThreadContext.html#getThreadContextMap()[`ThreadContext::getThreadContextMap`]
method.
diff --git a/src/site/asciidoc/manual/filters.adoc b/src/site/asciidoc/manual/filters.adoc
index 4796c90..4bdef82 100644
--- a/src/site/asciidoc/manual/filters.adoc
+++ b/src/site/asciidoc/manual/filters.adoc
@@ -397,6 +397,53 @@
</Configuration>
----
+[#NoMarkerFilter]
+== NoMarkerFilter
+
+The NoMarkerFilter checks that there is no marker included in the LogEvent. A match occurs when there is no
+marker in the Log Event.
+
+.No Marker Filter Parameters
+[cols="1m,1,3"]
+|===
+|Parameter Name |Type |Description
+
+|onMatch
+|String
+|Action to take when the filter matches. May be ACCEPT,
+DENY or NEUTRAL. The default value is NEUTRAL.
+
+|onMismatch
+|String
+|Action to take when the filter does not match. May
+be ACCEPT, DENY or NEUTRAL. The default value is DENY.
+|===
+
+A sample configuration that only allows the event to be written by the
+appender if no marker is there:
+
+[source,xml]
+----
+<?xml version="1.0" encoding="UTF-8"?>
+<Configuration status="warn" name="MyApp" packages="">
+ <Appenders>
+ <RollingFile name="RollingFile" fileName="logs/app.log"
+ filePattern="logs/app-%d{MM-dd-yyyy}.log.gz">
+ <NoMarkerFilter onMatch="ACCEPT" onMismatch="DENY"/>
+ <PatternLayout>
+ <pattern>%d %p %c{1.} [%t] %m%n</pattern>
+ </PatternLayout>
+ <TimeBasedTriggeringPolicy />
+ </RollingFile>
+ </Appenders>
+ <Loggers>
+ <Root level="error">
+ <AppenderRef ref="RollingFile"/>
+ </Root>
+ </Loggers>
+</Configuration>
+----
+
[#RegexFilter]
== RegexFilter
diff --git a/src/site/asciidoc/manual/garbagefree.adoc b/src/site/asciidoc/manual/garbagefree.adoc
index 0b88426..f18f6cb 100644
--- a/src/site/asciidoc/manual/garbagefree.adoc
+++ b/src/site/asciidoc/manual/garbagefree.adoc
@@ -165,7 +165,7 @@
* StructuredDataFilter (garbage free since 2.8)
* ThreadContextMapFilter (garbage free since 2.8)
* ThresholdFilter (garbage free since 2.8)
-* TimeFilter (garbage free since 2.8)
+* TimeFilter (garbage free since 2.8 except when range must be recalculated once per day)
Other filters like BurstFilter, RegexFilter and ScriptFilter are not
trivial to make garbage free, and there is currently no plan to change
@@ -179,6 +179,11 @@
GelfLayout is garbage-free when used with compressionType="OFF", as long
as no additional field contains '${' (variable substitution).
+==== JsonTemplateLayout
+
+`JsonTemplateLayout` is garbage-free with
+link:json-template-layout.html#faq-garbage-free[a few exceptions].
+
==== PatternLayout
PatternLayout with the following limited set of conversion patterns is
diff --git a/src/site/asciidoc/manual/json-template-layout.adoc b/src/site/asciidoc/manual/json-template-layout.adoc
new file mode 100644
index 0000000..92ec635
--- /dev/null
+++ b/src/site/asciidoc/manual/json-template-layout.adoc
@@ -0,0 +1,1209 @@
+////
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+////
+= JSON Template Layout
+Volkan Yazıcı <vy@apache.org>
+
+`JsonTemplateLayout` is a customizable, efficient, and garbage-free JSON
+emitting layout. It encodes ``LogEvent``s according to the structure described
+by the JSON template provided. In a nutshell, it shines with its
+
+* Customizable JSON structure (see `eventTemplate[Uri]` and
+ `stackTraceElementTemplate[Uri]` parameters)
+
+* Customizable timestamp formatting (see `timestamp` parameter)
+
+[#usage]
+== Usage
+
+Adding `log4j-layout-json-template` artifact to your list of dependencies is
+enough to enable access to `JsonTemplateLayout` in your Log4j configuration:
+
+[source,xml]
+----
+<dependency>
+ <groupId>org.apache.logging.log4j</groupId>
+ <artifactId>log4j-layout-json-template</artifactId>
+ <version>${log4j.version}</version>
+</dependency>
+----
+
+For instance, given the following JSON template modelling the
+https://github.com/logstash/log4j-jsonevent-layout[the official Logstash
+`JSONEventLayoutV1`] (accessible via `classpath:LogstashJsonEventLayoutV1.json`)
+
+[source,json]
+----
+{
+ "mdc": {
+ "$resolver": "mdc"
+ },
+ "exception": {
+ "exception_class": {
+ "$resolver": "exception",
+ "field": "className"
+ },
+ "exception_message": {
+ "$resolver": "exception",
+ "field": "message",
+ "stringified": true
+ },
+ "stacktrace": {
+ "$resolver": "exception",
+ "field": "stackTrace",
+ "stringified": true
+ }
+ },
+ "line_number": {
+ "$resolver": "source",
+ "field": "lineNumber"
+ },
+ "class": {
+ "$resolver": "source",
+ "field": "className"
+ },
+ "@version": 1,
+ "source_host": "${hostName}",
+ "message": {
+ "$resolver": "message",
+ "stringified": true
+ },
+ "thread_name": {
+ "$resolver": "thread",
+ "field": "name"
+ },
+ "@timestamp": {
+ "$resolver": "timestamp"
+ },
+ "level": {
+ "$resolver": "level",
+ "field": "name"
+ },
+ "file": {
+ "$resolver": "source",
+ "field": "fileName"
+ },
+ "method": {
+ "$resolver": "source",
+ "field": "methodName"
+ },
+ "logger_name": {
+ "$resolver": "logger",
+ "field": "name"
+ }
+}
+----
+
+in combination with the below `log4j2.xml` configuration:
+
+[source,xml]
+----
+<JsonTemplateLayout eventTemplateUri="classpath:LogstashJsonEventLayoutV1.json"/>
+----
+
+or with the below `log4j2.properties` configuration:
+
+[source,ini]
+----
+appender.console.json.type = JsonTemplateLayout
+appender.console.json.eventTemplateUri = classpath:LogstashJsonEventLayoutV1.json
+----
+
+`JsonTemplateLayout` emits JSON strings as follows:
+
+[source,json]
+----
+{
+ "exception": {
+ "exception_class": "java.lang.RuntimeException",
+ "exception_message": "test",
+ "stacktrace": "java.lang.RuntimeException: test\n\tat org.apache.logging.log4j.JsonTemplateLayoutDemo.main(JsonTemplateLayoutDemo.java:11)\n"
+ },
+ "line_number": 12,
+ "class": "org.apache.logging.log4j.JsonTemplateLayoutDemo",
+ "@version": 1,
+ "source_host": "varlik",
+ "message": "Hello, error!",
+ "thread_name": "main",
+ "@timestamp": "2017-05-25T19:56:23.370+02:00",
+ "level": "ERROR",
+ "file": "JsonTemplateLayoutDemo.java",
+ "method": "main",
+ "logger_name": "org.apache.logging.log4j.JsonTemplateLayoutDemo"
+}
+----
+
+[#layout-config]
+== Layout Configuration
+
+`JsonTemplateLayout` is configured with the following parameters:
+
+.`JsonTemplateLayout` parameters
+[cols="1m,1m,4"]
+|===
+| Parameter Name
+| Type
+| Description
+
+| charset
+| Charset
+| `Charset` used for `String` encoding
+
+| locationInfoEnabled
+| boolean
+| toggles access to the `LogEvent` source; file name, line number, etc.
+ (defaults to `false` set by `log4j.layout.jsonTemplate.locationInfoEnabled`
+ property)
+
+| stackTraceEnabled
+| boolean
+| toggles access to the stack traces (defaults to `true` set by
+ `log4j.layout.jsonTemplate.stackTraceEnabled` property)
+
+| eventTemplate
+| String
+| inline JSON template for rendering ``LogEvent``s (has priority over
+ `eventTemplateUri`, defaults to `null` set by
+ `log4j.layout.jsonTemplate.eventTemplate` property)
+
+| eventTemplateUri
+| String
+| URI pointing to the JSON template for rendering ``LogEvent``s (defaults to
+ `classpath:EcsLayout.json` set by `log4j.layout.jsonTemplate.eventTemplateUri`
+ property)
+
+| eventTemplateAdditionalFields
+| EventTemplateAdditionalField[]
+| additional key-value pairs appended to the root of the event template
+
+| stackTraceElementTemplate
+| String
+| inline JSON template for rendering ``StackTraceElement``s (has priority over
+ `stackTraceElementTemplateUri`, defaults to `null` set by
+ `log4j.layout.jsonTemplate.stackTraceElementTemplate` property)
+
+| stackTraceElementTemplateUri
+| String
+| JSON template for rendering ``StackTraceElement``s (defaults to
+ `classpath:StackTraceElementLayout.json` set by
+ `log4j.layout.jsonTemplate.stackTraceElementTemplateUri` property)
+
+| eventDelimiter
+| String
+| delimiter used for separating emitted ``LogEvent``s (defaults to
+ `System.lineSeparator()` set by `log4j.layout.jsonTemplate.eventDelimiter`
+ property)
+
+| nullEventDelimiterEnabled
+| boolean
+| append `\0` (`null`) character to the end of every emitted `eventDelimiter`
+ (defaults to `false` set by
+ `log4j.layout.jsonTemplate.nullEventDelimiterEnabled` property)
+
+| maxStringLength
+| int
+| truncate string values longer than the specified limit (defaults to 16384 set
+ by `log4j.layout.jsonTemplate.maxStringLength` property)
+
+| truncatedStringSuffix
+| String
+| suffix to append to strings truncated due to exceeding `maxStringLength`
+ (defaults to `…` set by `log4j.layout.jsonTemplate.truncatedStringSuffix`
+ property)
+
+| recyclerFactory
+| RecyclerFactory
+| recycling strategy that can either be `dummy`, `threadLocal`, or `queue`
+ (set by `log4j.layout.jsonTemplate.recyclerFactory` property)
+|===
+
+[#additional-event-template-fields]
+=== Additonal event template fields
+
+Additional event template field is a convenient short-cut to add custom fields
+to a template or override the fields of a template. Following configuration
+overrides the `host` field of the `GelfLayout.json` template and adds two new
+custom fields:
+
+[source,xml]
+----
+<JsonTemplateLayout eventTemplateUri="classpath:GelfLayout.json">
+ <EventTemplateAdditionalFields>
+ <EventTemplateAdditionalField key="host" value="www.apache.org"/>
+ <EventTemplateAdditionalField key="_serviceName" value="auth-service"/>
+ <EventTemplateAdditionalField key="_containerId" value="6ede3f0ca7d9"/>
+ </EventTemplateAdditionalFields>
+</JsonTemplateLayout>
+----
+
+One can also pass JSON literals into additional fields:
+
+[source,xml]
+----
+<EventTemplateAdditionalField
+ key="marker"
+ type="JSON"
+ value='{"$resolver": "marker", "field": "name"}'/>
+<EventTemplateAdditionalField
+ key="aNumber"
+ type="JSON"
+ value="1"/>
+<EventTemplateAdditionalField
+ key="aList"
+ type="JSON"
+ value='[1,2,"string"]'/>
+----
+
+[#recycling-strategy]
+=== Recycling strategy
+
+`RecyclerFactory` plays a crucial role for determining the memory footprint of
+the layout. Template resolvers employ it to create recyclers for objects that
+they can reuse. The function of each `RecyclerFactory` and when one should
+prefer one over another is explained below:
+
+* `dummy` performs no recycling, hence each recycling attempt will result in a
+new instance. This will obviously create a load on the garbage-collector. It
+is a good choice for applications with low and medium log rate.
+
+* `threadLocal` performs the best, since every instance is stored in
+``ThreadLocal``s and accessed without any synchronization cost. Though this
+might not be a desirable option for applications running with hundreds of
+threads or more, e.g., a web servlet.
+
+* `queue` is the best of both worlds. It allows recycling of objects up to a
+certain number (`capacity`). When this limit is exceeded due to excessive
+concurrent load (e.g., `capacity` is 50 but there are 51 threads concurrently
+trying to log), it starts allocating. `queue` is a good strategy where
+`threadLocal` is not desirable.
++
+`queue` also accepts optional `supplier` (of type `java.util.Queue`, defaults to
+ `org.jctools.queues.MpmcArrayQueue.new` if JCTools is in the classpath;
+otherwise `java.util.concurrent.ArrayBlockingQueue.new`) and `capacity` (of
+type `int`, defaults to `max(8,2*cpuCount+1)`) parameters:
++
+[source]
+----
+queue:supplier=org.jctools.queues.MpmcArrayQueue.new
+queue:capacity=10
+queue:supplier=java.util.concurrent.ArrayBlockingQueue.new,capacity=50
+----
+
+The default `RecyclerFactory` is `threadLocal`, if
+`log4j2.enable.threadlocals=true`; otherwise, `queue`.
+
+[#template-config]
+== Template Configuration
+
+Templates are configured by means of the following `JsonTemplateLayout`
+parameters:
+
+- `eventTemplate[Uri]` (for serializing ``LogEvent``s)
+- `stackTraceElementTemplate[Uri]` (for serializing ``StackStraceElement``s)
+- `eventTemplateAdditionalFields` (for extending the used event template)
+
+[#event-templates]
+=== Event Templates
+
+`eventTemplate[Uri]` describes the JSON structure `JsonTemplateLayout` uses to
+serialize ``LogEvent``s. The default configuration (accessible by
+`log4j.layout.jsonTemplate.eventTemplate[Uri]` property) is set to
+`classpath:EcsLayout.json` provided by the `log4j-layout-json-template`
+artifact:
+
+[source,json]
+----
+{
+ "@timestamp": {
+ "$resolver": "timestamp",
+ "pattern": {
+ "format": "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'",
+ "timeZone": "UTC"
+ }
+ },
+ "log.level": {
+ "$resolver": "level",
+ "field": "name"
+ },
+ "message": {
+ "$resolver": "message",
+ "stringified": true
+ },
+ "process.thread.name": {
+ "$resolver": "thread",
+ "field": "name"
+ },
+ "log.logger": {
+ "$resolver": "logger",
+ "field": "name"
+ },
+ "labels": {
+ "$resolver": "mdc",
+ "flatten": true,
+ "stringified": true
+ },
+ "tags": {
+ "$resolver": "ndc"
+ },
+ "error.type": {
+ "$resolver": "exception",
+ "field": "className"
+ },
+ "error.message": {
+ "$resolver": "exception",
+ "field": "message"
+ },
+ "error.stack_trace": {
+ "$resolver": "exception",
+ "field": "stackTrace",
+ "stringified": true
+ }
+}
+
+----
+
+`log4j-layout-json-template` artifact contains the following predefined event
+templates:
+
+- https://github.com/apache/logging-log4j2/tree/master/log4j-layout-json-template/src/main/resources/EcsLayout.json[`EcsLayout.json`]
+ described by https://www.elastic.co/guide/en/ecs/current/ecs-reference.html[the Elastic Common Schema (ECS) specification]
+
+- https://github.com/apache/logging-log4j2/tree/master/log4j-layout-json-template/src/main/resources/LogstashJsonEventLayoutV1.json[`LogstashJsonEventLayoutV1.json`]
+ described in https://github.com/logstash/log4j-jsonevent-layout[Logstash
+ `json_event` pattern for log4j]
+
+- https://github.com/apache/logging-log4j2/tree/master/log4j-layout-json-template/src/main/resources/GelfLayout.json[`GelfLayout.json`]
+ described by https://docs.graylog.org/en/3.1/pages/gelf.html#gelf-payload-specification[the
+ Graylog Extended Log Format (GELF) payload specification] with additional
+ `_thread` and `_logger` fields. (Here it is advised to override the obligatory
+ `host` field with a user provided constant via `eventTemplateAdditionalFields`
+ to avoid `hostName` property lookup at runtime, which incurs an extra cost.)
+
+- https://github.com/apache/logging-log4j2/tree/master/log4j-layout-json-template/src/main/resources/JsonLayout.json[`JsonLayout.json`]
+ providing the exact JSON structure generated by link:layouts.html#JSONLayout[`JsonLayout`]
+ with the exception of `thrown` field. (`JsonLayout` serializes the `Throwable`
+ as is via Jackson `ObjectMapper`, whereas `JsonLayout.json` template of
+ `JsonTemplateLayout` employs the `StackTraceElementLayout.json` template
+ for stack traces to generate a document-store-friendly flat structure.)
+
+Below is the list of supported event template resolvers:
+
+[#event-template-resolvers]
+.`LogEvent` template resolvers
+[cols="1m,3,2,2,4"]
+|===
+| Resolver Name
+| Syntax
+| Description
+| Garbage Footprint
+| Examples
+
+| endOfBatch
+|
+| `logEvent.isEndOfBatch()`
+| none
+a|
+[source,json]
+----
+{
+ "$resolver": "endOfBatch"
+}
+----
+
+| exception
+a|
+[source]
+----
+config = field , [ stringified ]
+field = "field" -> (
+ "className" \|
+ "message" \|
+ "stackTrace" )
+stringified = "stringified" -> boolean
+----
+a|
+Resolves fields of the `Throwable` returned by `logEvent.getThrown()`.
+
+Note that this resolver is toggled by
+`log4j.layout.jsonTemplate.stackTraceEnabled` property.
+| Since `Throwable#getStackTrace()` clones the original `StackTraceElement[]`,
+ access to (and hence rendering of) stack traces are not garbage-free.
+a|
+Resolve `logEvent.getThrown().getClass().getCanonicalName()`:
+
+[source,json]
+----
+{
+ "$resolver": "exception",
+ "field": "className"
+}
+----
+
+Resolve the stack trace into a list of `StackTraceElement` objects:
+
+[source,json]
+----
+{
+ "$resolver": "exception",
+ "field": "stackTrace"
+}
+----
+
+Resolve the stack trace into a string field:
+
+[source,json]
+----
+{
+ "$resolver": "exception",
+ "field": "stackTrace",
+ "stringified": true
+}
+----
+
+| exceptionRootCause
+| identical to `exception` resolver
+a|
+Resolves the fields of the innermost `Throwable` returned by
+`logEvent.getThrown()`.
+
+Note that this resolver is toggled by
+`log4j.layout.jsonTemplate.stackTraceEnabled` property.
+| identical to `exception` resolver
+| identical to `exception` resolver
+
+| level
+a|
+[source]
+----
+config = field , [ severity ]
+field = "field" -> ( "name" \| "severity" )
+severity = severity-field
+severity-field = "field" -> ( "keyword" \| "code" )
+----
+| resolves the fields of the `logEvent.getLevel()`
+| none
+a|
+Resolve the level name:
+
+[source,json]
+----
+{
+ "$resolver": "level",
+ "field": "name"
+}
+----
+
+Resolve the https://en.wikipedia.org/wiki/Syslog#Severity_levels[Syslog severity]
+keyword:
+
+[source,json]
+----
+{
+ "$resolver": "level",
+ "field": "severity",
+ "severity": {
+ "field": "keyword"
+ }
+}
+----
+
+Resolve the https://en.wikipedia.org/wiki/Syslog#Severity_levels[Syslog severity]
+code:
+
+[source,json]
+----
+{
+ "$resolver": "level",
+ "field": "severity",
+ "severity": {
+ "field": "code"
+ }
+}
+----
+
+| logger
+a|
+[source]
+----
+config = "field" -> ( "name" \| "fqcn" )
+----
+| resolves `logEvent.getLoggerFqcn()` and `logEvent.getLoggerName()`
+| none
+a|
+Resolve the logger name:
+
+[source,json]
+----
+{
+ "$resolver": "logger",
+ "field": "name"
+}
+----
+
+Resolve the logger's fully qualified class name:
+
+[source,json]
+----
+{
+ "$resolver": "logger",
+ "field": "fqcn"
+}
+----
+
+| main:<key>
+a|
+[source]
+----
+config = ( index \| key )
+index = "index" -> number
+key = "key" -> string
+----
+| performs link:lookups.html#AppMainArgsLookup[Main Argument Lookup] for the
+ given `index` or `key`
+| none
+a|
+Resolve the 1st `main()` method argument:
+
+[source,json]
+----
+{
+ "$resolver": "main",
+ "index": 0
+}
+----
+
+Resolve the argument coming right after `--userId`:
+
+[source,json]
+----
+{
+ "$resolver": "main",
+ "key": "--userId"
+}
+----
+
+| map
+a|
+[source]
+----
+config = key , [ stringified ]
+key = "key" -> string
+stringified = "stringified" -> boolean
+----
+| resolves the given `key` of ``MapMessage``s
+| `stringified` flag translates to `String.valueOf(value)`, hence mind
+ not-`String`-typed values.
+a|
+Resolve the `userRole` field of the message:
+
+[source,json]
+----
+{
+ "$resolver": "map",
+ "key": "userRole"
+}
+----
+
+| marker
+a|
+[source]
+----
+config = "field" -> "name"
+----
+| `logEvent.getMarker().getName()`
+| none
+a|
+Resolve the marker name:
+
+[source,json]
+----
+{
+ "$resolver": "marker",
+ "field": "name"
+}
+----
+
+| mdc
+a|
+[source]
+----
+config = singleAccess \| multiAccess
+
+singleAccess = key , [ stringified ]
+key = "key" -> string
+stringified = "stringified" -> boolean
+
+multi-access = [ pattern ] , [ flatten ] , [ stringified ]
+pattern = "pattern" -> string
+flatten = "flatten" -> ( boolean \| flattenConfig )
+flattenConfig = [ flattenPrefix ]
+flattenPrefix = "prefix" -> string
+----
+a| Mapped Diagnostic Context (MDC), aka. Thread Context Data, resolver.
+
+`singleAccess` resolves the MDC value as is, whilst `multiAccess` resolves a
+multitude of MDC values. If `flatten` is provided, `multiAccess` merges the
+values with the parent, otherwise creates a new JSON object containing the
+values.
+
+Enabling `stringified` flag converts each value to its string representation.
+
+Regex provided in the `pattern` is used to match against the keys.
+a|
+`log4j2.garbagefreeThreadContextMap` flag needs to be turned on to iterate
+the map without allocations.
+
+`stringified` allocates a new `String` for values that are not of type `String`.
+
+Writing certain non-primitive values (e.g., `BigDecimal`, `Set`, etc.) to JSON
+generates garbage, though most (e.g., `int`, `long`, `String`, `List`,
+`boolean[]`, etc.) don't.
+a|
+Resolve the `userRole` MDC value:
+
+[source,json]
+----
+{
+ "$resolver": "mdc",
+ "key": "userRole"
+}
+----
+
+Resolve the string representation of the `userRank` MDC value:
+
+[source,json]
+----
+{
+ "$resolver": "mdc",
+ "key": "userRank",
+ "stringified": true
+}
+----
+
+Resolve all MDC entries into an object:
+
+[source,json]
+----
+{
+ "$resolver": "mdc"
+}
+----
+
+Resolve all MDC entries into an object such that values are converted to string:
+
+[source,json]
+----
+{
+ "$resolver": "mdc",
+ "stringified": true
+}
+----
+
+Merge all MDC entries whose keys are matching with the `user(Role\|Rank)` regex
+into the parent:
+
+[source,json]
+----
+{
+ "$resolver": "mdc",
+ "flatten": true,
+ "pattern": "user(Role\|Rank)"
+}
+----
+
+After converting the corresponding entries to string, merge all MDC entries to
+parent such that keys are prefixed with `_`:
+
+[source,json]
+----
+{
+ "$resolver": "mdc",
+ "stringified": true,
+ "flatten": {
+ "prefix": "_"
+ }
+}
+----
+
+| message
+a|
+[source]
+----
+config = [ stringified ] , [ fallbackKey ]
+pattern = "pattern" -> string
+includeStackTrace = "includeStacktrae" -> boolean
+stringified = "stringified" -> boolean
+fallbackKey = "fallbackKey" -> string
+----
+a| `logEvent.getMessage()`
+| For simple string messages, the resolution is performed without allocations.
+ For ``ObjectMessage``s and ``MultiformatMessage``s, it depends.
+a|
+Resolve the message into a string:
+
+[source,json]
+----
+{
+ "$resolver": "message",
+ "stringified": true
+}
+----
+
+Resolve the message into a string using a pattern:
+
+[source,json]
+----
+{
+ "$resolver": "message",
+ "pattern": ""[%t] %-5p %X{requestId, sessionId, loginId, userId, ipAddress, corpAcctNumber} %C{1.}.%M:%L - %m"",
+ "stringified": true
+}
+----
+
+Resolve the message such that if it is an `ObjectMessage` or a
+`MultiformatMessage` with JSON support, its type (string, list, object, etc.)
+will be retained:
+
+[source,json]
+----
+{
+ "$resolver": "message"
+}
+----
+
+Given the above configuration, a `SimpleMessage` will generate a `"sample log
+message"`, whereas a `MapMessage` will generate a `{"action": "login",
+"sessionId": "87asd97a"}`. Certain indexed log storage systems (e.g.,
+https://www.elastic.co/elasticsearch/[Elasticsearch]) will not allow both values
+to coexist due to type mismatch: one is a `string` while the other is an `object`.
+Here one can use a `fallbackKey` to work around the problem:
+
+[source,json]
+----
+{
+ "$resolver": "message",
+ "fallbackKey": "formattedMessage"
+}
+----
+
+Using this configuration, a `SimpleMessage` will generate a
+`{"formattedMessage": "sample log message"}` and a `MapMessage` will generate a
+`{"action": "login", "sessionId": "87asd97a"}`. Note that both emitted JSONs are
+of type `object` and have no type-conflicting fields.
+
+| ndc
+a|
+[source]
+----
+config = [ pattern ]
+pattern = "pattern" -> string
+----
+| Resolves the Nested Diagnostic Context (NDC), aka. Thread Context Stack,
+ `String[]` returned by `logEvent.getContextStack()`
+| none
+a|
+Resolve all NDC values into a list:
+
+[source,json]
+----
+{
+ "$resolver": "ndc"
+}
+----
+
+Resolve all NDC values matching with the `pattern` regex:
+
+[source,json]
+----
+{
+ "$resolver": "ndc",
+ "pattern": "user(Role\|Rank):\\w+"
+}
+----
+
+| pattern
+a|
+[source]
+----
+config = pattern , [ stackTraceEnabled ]
+pattern = "pattern" -> string
+stackTraceEnabled = "stackTraceEnabled" -> boolean
+----
+a|
+Resolver delegating to link:layouts.html#PatternLayout[`PatternLayout`].
+
+The default value of `stackTraceEnabled` is inherited from the parent
+`JsonTemplateLayout`.
+| none
+a|
+Resolve the string produced by `%p %c{1.} [%t] %X{userId} %X %m%ex` pattern:
+
+[source,json]
+----
+{
+ "$resolver": "pattern",
+ "pattern": "%p %c{1.} [%t] %X{userId} %X %m%ex"
+}
+----
+
+| source
+a|
+[source]
+----
+config = "field" -> (
+ "className" \|
+ "fileName" \|
+ "methodName" \|
+ "lineNumber" )
+----
+a|
+Resolves the fields of the `StackTraceElement` returned by
+`logEvent.getSource()`.
+
+Note that this resolver is toggled by
+`log4j.layout.jsonTemplate.locationInfoEnabled` property.
+| none
+a|
+Resolve the line number:
+
+[source,json]
+----
+{
+ "$resolver": "source",
+ "field": "lineNumber"
+}
+----
+
+| thread
+a|
+[source]
+----
+config = "field" -> ( "name" \| "id" \| "priority" )
+----
+| resolves `logEvent.getThreadId()`, `logEvent.getThreadName()`,
+ `logEvent.getThreadPriority()`
+| none
+a|
+Resolve the thread name:
+
+[source,json]
+----
+{
+ "$resolver": "thread",
+ "field": "name"
+}
+----
+
+| timestamp
+a|
+[source]
+----
+config = [ patternConfig \| epochConfig ]
+
+patternConfig = "pattern" -> (
+ [ format ] ,
+ [ timeZone ] ,
+ [ locale ] )
+format = "format" -> string
+timeZone = "timeZone" -> string
+locale = "locale" -> (
+ language \|
+ ( language , "_" , country ) \|
+ ( language , "_" , country , "_" , variant )
+ )
+
+epochConfig = "epoch" -> ( unit , [ rounded ] )
+unit = "unit" -> (
+ "nanos" \|
+ "millis" \|
+ "secs" \|
+ "millis.nanos" \|
+ "secs.nanos" \|
+ )
+rounded = "rounded" -> boolean
+----
+| resolves `logEvent.getInstant()` in various forms
+| none
+a|
+.`timestamp` template resolver examples
+[cols="5,2m"]
+!===
+! Configuration
+! Output
+
+a!
+[source,json]
+----
+{
+ "$resolver": "timestamp"
+}
+----
+! 2020-02-07T13:38:47.098+02:00
+
+a!
+[source,json]
+----
+{
+ "$resolver": "timestamp",
+ "pattern": {
+ "format": "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'",
+ "timeZone": "UTC",
+ "locale": "en_US"
+ }
+}
+----
+! 2020-02-07T13:38:47.098Z
+
+a!
+[source,json]
+----
+{
+ "$resolver": "timestamp",
+ "epoch": {
+ "unit": "secs"
+ }
+}
+----
+! 1581082727.982123456
+
+a!
+[source,json]
+----
+{
+ "$resolver": "timestamp",
+ "epoch": {
+ "unit": "secs",
+ "rounded": true
+ }
+}
+----
+! 1581082727
+
+a!
+[source,json]
+----
+{
+ "$resolver": "timestamp",
+ "epoch": {
+ "unit": "secs.nanos"
+ }
+}
+----
+! 982123456
+
+a!
+[source,json]
+----
+{
+ "$resolver": "timestamp",
+ "epoch": {
+ "unit": "millis"
+ }
+}
+----
+! 1581082727982.123456
+
+a!
+[source,json]
+----
+{
+ "$resolver": "timestamp",
+ "epoch": {
+ "unit": "millis",
+ "rounded": true
+ }
+}
+----
+! 1581082727982
+
+a!
+[source,json]
+----
+{
+ "$resolver": "timestamp",
+ "epoch": {
+ "unit": "millis.nanos"
+ }
+}
+----
+! 123456
+
+a!
+[source,json]
+----
+{
+ "$resolver": "timestamp",
+ "epoch": {
+ "unit": "nanos"
+ }
+}
+----
+! 1581082727982123456
+!===
+|===
+
+[#stack-trace-element-templates]
+=== Stack Trace Element Templates
+
+`stackTraceElement[Uri]` describes the JSON structure `JsonTemplateLayout` uses
+to format ``StackTraceElement``s. The default configuration (accessible by
+`log4j.layout.jsonTemplate.stackTraceElementTemplate[Uri]` property) is set to
+`classpath:StackTraceElementLayout.json` provided by the
+`log4j-layout-json-template` artifact:
+
+[source,json]
+----
+{
+ "class": {
+ "$resolver": "stackTraceElement",
+ "field": "className"
+ },
+ "method": {
+ "$resolver": "stackTraceElement",
+ "field": "methodName"
+ },
+ "file": {
+ "$resolver": "stackTraceElement",
+ "field": "fileName"
+ },
+ "line": {
+ "$resolver": "stackTraceElement",
+ "field": "lineNumber"
+ }
+}
+----
+
+The allowed template configuration syntax is as follows:
+
+[source]
+----
+config = "field" -> (
+ "className" |
+ "fileName" |
+ "methodName" |
+ "lineNumber" )
+----
+
+All above accesses to `StackTraceElement` is garbage-free.
+
+[#features]
+== Features
+
+Below is a feature comparison matrix between `JsonTemplateLayout` and
+alternatives.
+
+.Feature comparison matrix
+[cols="3,1,1,1,1"]
+|===
+| Feature
+| `JsonTemplateLayout`
+| link:layouts.html#JSONLayout[`JsonLayout`]
+| link:layouts.html#GELFLayout[`GelfLayout`]
+| https://github.com/elastic/java-ecs-logging/tree/master/log4j2-ecs-layout[`EcsLayout`]
+
+| Java version
+| 8
+| 8
+| 8
+| 6
+
+| Dependencies
+| None
+| Jackson
+| None
+| None
+
+| Full schema customization?
+| ✓
+| ✕
+| ✕
+| ✕
+
+| Timestamp customization?
+| ✓
+| ✕
+| ✕
+| ✕
+
+| (Almost) garbage-free?
+| ✓
+| ✕
+| ✓
+| ✓
+
+| Custom typed `Message` serialization?
+| ✓
+| ✕
+| ✕
+| ?footnote:[Only for ``ObjectMessage``s and if Jackson is in the classpath.]
+
+| Custom typed `MDC` value serialization?
+| ✓
+| ✕
+| ✕
+| ✕
+
+| Rendering stack traces as array?
+| ✓
+| ✓
+| ✕
+| ✓
+
+| JSON pretty print?
+| ✕
+| ✓
+| ✕
+| ✕
+
+| Additional fields?
+| ✓
+| ✓
+| ✓
+| ✓
+|===
+
+[#faq]
+== F.A.Q.
+
+[#faq-lookups]
+=== Are lookups supported in templates?
+
+Yes, link:lookups.html[lookups] (e.g., `${java:version}`, `${env:USER}`,
+`${date:MM-dd-yyyy}`) are supported in string literals of templates. Though note
+that they are not garbage-free.
+
+[#faq-garbage-free]
+=== Is `JsonTemplateLayout` garbage-free?
+
+Yes, if the garbage-free layout behaviour toggling properties
+`log4j2.enableDirectEncoders` and `log4j2.garbagefreeThreadContextMap` are
+enabled. Take into account the following caveats:
+
+* The configured link:#recycling-strategy[recycling strategy] might not be
+ garbage-free.
+
+* Since `Throwable#getStackTrace()` clones the original `StackTraceElement[]`,
+ access to (and hence rendering of) stack traces are not garbage-free.
+
+* Serialization of ``MapMessage``s and ``ObjectMessage``s are mostly
+ garbage-free except for certain types (e.g., `BigDecimal`, `BigInteger`,
+ ``Collection``s with the exception of `List`).
+
+* link:lookups.html[Lookups] (that is, `${...}` variables, excluding
+ `${json:...}` ones) are not garbage-free.
+
+Don't forget to checkout link:#event-template-resolvers[the notes on garbage footprint of resolvers]
+you employ in templates.
diff --git a/src/site/asciidoc/manual/layouts.adoc b/src/site/asciidoc/manual/layouts.adoc
index d9270ae..a1d540f 100644
--- a/src/site/asciidoc/manual/layouts.adoc
+++ b/src/site/asciidoc/manual/layouts.adoc
@@ -15,7 +15,7 @@
limitations under the License.
////
= Layouts
-Ralph Goers <rgoers@apache.org>; Gary Gregory <ggregory@apache.org>
+Ralph Goers <rgoers@apache.org>; Gary Gregory <ggregory@apache.org>; Volkan Yazıcı <vy@apache.org>
An Appender uses a Layout to format a LogEvent into a form that meets
the needs of whatever will be consuming the log event. In Log4j 1.x and
@@ -41,7 +41,7 @@
== CSV Layouts
As of Log4j 2.11.0, CSV support has moved from the existing module
-`logj-core` to the new module `log4j-csv`.
+`log4j-core` to the new module `log4j-csv`.
This layout creates
https://en.wikipedia.org/wiki/Comma-separated_values[Comma Separated
@@ -225,13 +225,33 @@
|boolean
|Whether to include NULL byte as delimiter after each event (optional, default to false).
Useful for Graylog GELF TCP input. Cannot be used with compression.
+
+|messagePattern
+|String
+|The pattern to use to format the String. If not supplied only the text derived from the logging
+message will be used. See <<PatternLayout>> for information on the pattern
+strings
+
+|threadContextExcludes
+|String
+|A comma separated list of ThreadContext attributes to exclude when formatting the event. This
+attribute only applies when includeThreadContext="true" is specified. If threadContextIncludes
+are also specified this attribute will be ignored.
+
+|threadContextIncludes
+|String
+|A comma separated list of ThreadContext attributes to include when formatting the event. This
+attribute only applies when includeThreadContext="true" is specified. If threadContextExcludes
+are also specified this attribute will override them. ThreadContext fields specified here that
+have no value will be omitted.
|===
To include any custom field in the output, use following syntax:
[source,xml]
----
-<GelfLayout>
+<GelfLayout includeThreadContext="true" threadContextIncludes="loginId,requestId">
+ <MessagePattern>%d %5p [%t] %c{1} %X{loginId, requestId} - %m%n</MessagePattern>
<KeyValuePair key="additionalField1" value="constant value"/>
<KeyValuePair key="additionalField2" value="$${ctx:key}"/>
</GelfLayout>
@@ -367,11 +387,10 @@
=== Pretty vs. compact JSON
-By default, the JSON layout is not compact (a.k.a. not "pretty") with
-`compact="false"`, which means the appender uses end-of-line characters
-and indents lines to format the text. If `compact="true"`, then no
-end-of-line or indentation is used. Message content may contain, of
-course, escaped end-of-lines.
+The compact attribute determines whether the output will be "pretty" or not. The default value is "false",
+which means the appender uses end-of-line characters and indents lines to format the text. If
+`compact="true"`, then no end-of-line or indentation is used, which will cause the output
+to take less space. Of course, the message content may contain, escaped end-of-lines.
.JsonLayout Parameters
[cols="1m,1,4"]
@@ -396,6 +415,10 @@
each record. Defaults to false. Use with eventEol=true and compact=true
to get one record per line.
+|endOfLine
+|String
+|If set, overrides the default end-of-line string. E.g. set it to "\n" and use with eventEol=true and compact=true to have one record per line separated by "\n" instead of "\r\n". Defaults to null (i.e. not set).
+
|complete
|boolean
|If true, the appender includes the JSON header and
@@ -429,6 +452,11 @@
https://docs.oracle.com/javase/6/docs/api/java/lang/Throwable.html[Throwable]
(optional, default to true).
+|includeTimeMillis
+|boolean
+|If true, the timeMillis attribute is included in the Json payload instead of the instant. timeMillis
+will contain the number of milliseconds since midnight, January 1, 1970 UTC.
+
|stacktraceAsString
|boolean
|Whether to format the stacktrace as a
@@ -462,6 +490,111 @@
Additional link:../runtime-dependencies.html[runtime dependencies] are
required for using JsonLayout.
+[#JSONTemplateLayout]
+== JSON Template Layout
+
+`JsonTemplateLayout` is a customizable, efficient, and garbage-free JSON
+emitting layout. It encodes ``LogEvent``s according to the structure described
+by the JSON template provided. For instance, given the following JSON template
+modelling the https://github.com/logstash/log4j-jsonevent-layout[the official
+Logstash `JSONEventLayoutV1`]
+
+[source,json]
+----
+{
+ "mdc": {
+ "$resolver": "mdc"
+ },
+ "exception": {
+ "exception_class": {
+ "$resolver": "exception",
+ "field": "className"
+ },
+ "exception_message": {
+ "$resolver": "exception",
+ "field": "message",
+ "stringified": true
+ },
+ "stacktrace": {
+ "$resolver": "exception",
+ "field": "stackTrace",
+ "stringified": true
+ }
+ },
+ "line_number": {
+ "$resolver": "source",
+ "field": "lineNumber"
+ },
+ "class": {
+ "$resolver": "source",
+ "field": "className"
+ },
+ "@version": 1,
+ "source_host": "${hostName}",
+ "message": {
+ "$resolver": "message",
+ "stringified": true
+ },
+ "thread_name": {
+ "$resolver": "thread",
+ "field": "name"
+ },
+ "@timestamp": {
+ "$resolver": "timestamp"
+ },
+ "level": {
+ "$resolver": "level",
+ "field": "name"
+ },
+ "file": {
+ "$resolver": "source",
+ "field": "fileName"
+ },
+ "method": {
+ "$resolver": "source",
+ "field": "methodName"
+ },
+ "logger_name": {
+ "$resolver": "logger",
+ "field": "name"
+ }
+}
+----
+
+in combination with the below Log4j configuration:
+
+[source,xml]
+----
+<JsonTemplateLayout eventTemplateUri="classpath:LogstashJsonEventLayoutV1.json"/>
+----
+
+JSON Template Layout will render JSON documents as follows:
+
+[source,json]
+----
+{
+ "exception": {
+ "exception_class": "java.lang.RuntimeException",
+ "exception_message": "test",
+ "stacktrace": "java.lang.RuntimeException: test\n\tat org.apache.logging.log4j.JsonTemplateLayoutDemo.main(JsonTemplateLayoutDemo.java:11)\n"
+ },
+ "line_number": 12,
+ "class": "org.apache.logging.log4j.JsonTemplateLayoutDemo",
+ "@version": 1,
+ "source_host": "varlik",
+ "message": "Hello, error!",
+ "thread_name": "main",
+ "@timestamp": "2017-05-25T19:56:23.370+02:00",
+ "level": "ERROR",
+ "file": "JsonTemplateLayoutDemo.java",
+ "method": "main",
+ "logger_name": "org.apache.logging.log4j.JsonTemplateLayoutDemo"
+}
+----
+
+See link:json-template-layout.html[JSON Template Layout] page for the complete
+documentation.
+
[#PatternLayout]
== Pattern Layout
@@ -674,12 +807,7 @@
be followed by a set of braces containing a date and time pattern string per
https://docs.oracle.com/javase/6/docs/api/java/text/SimpleDateFormat.html[`SimpleDateFormat`].
-The predefined formats are `DEFAULT`, `ABSOLUTE`, `COMPACT`, `DATE`,
-`ISO8601`, and `ISO8601_BASIC`.
-
-You can also use a set of braces containing a time zone id per
-https://docs.oracle.com/javase/6/docs/api/java/util/TimeZone.html#getTimeZone(java.lang.String)[java.util.TimeZone.getTimeZone].
-If no date format specifier is given then the DEFAULT format is used.
+The predefined _named_ formats are:
[cols=",",options="header",]
!===
@@ -700,6 +828,15 @@
!%d{ISO8601_BASIC}
!20121102T143402,781
+!%d{ISO8601_OFFSET_DATE_TIME_HH}
+!2012-11-02'T'14:34:02,781-07
+
+!%d{ISO8601_OFFSET_DATE_TIME_HHMM}
+!2012-11-02'T'14:34:02,781-0700
+
+!%d{ISO8601_OFFSET_DATE_TIME_HHCMM}
+!2012-11-02'T'14:34:02,781-07:00
+
!%d{ABSOLUTE}
!14:34:02,781
@@ -715,6 +852,23 @@
!%d{COMPACT}
!20121102143402781
+!%d{UNIX}
+!1351866842
+
+!%d{UNIX_MILLIS}
+!1351866842781
+!===
+
+You can also use a set of braces containing a time zone id per
+https://docs.oracle.com/javase/6/docs/api/java/util/TimeZone.html#getTimeZone(java.lang.String)[java.util.TimeZone.getTimeZone].
+If no date format specifier is given then the DEFAULT format is used.
+
+You can define custom date formats:
+
+[cols=",",options="header",]
+!===
+!Pattern !Example
+
!%d{HH:mm:ss,SSS}
!14:34:02,123
@@ -729,12 +883,6 @@
!%d{HH:mm:ss}{GMT+0}
!18:34:02
-
-!%d{UNIX}
-!1351866842
-
-!%d{UNIX_MILLIS}
-!1351866842781
!===
%d{UNIX} outputs the UNIX time in seconds. %d{UNIX_MILLIS} outputs the
@@ -1490,7 +1638,8 @@
justify) but you can specify right padding with the left justification
flag. The padding character is space. If the data item is larger than
the minimum field width, the field is expanded to accommodate the data.
-The value is never truncated.
+The value is never truncated. To use zeros as the padding character prepend
+the _minimum field width_ with a zero.
This behavior can be changed using the _maximum field width_ modifier
which is designated by a period followed by a decimal constant. If the
@@ -1625,6 +1774,23 @@
and a set of PatternMatch elements that identify the various patterns
that can be selected.
+[#LevelPatternSelector]
+==== LevelPatternSelector
+
+The LevelPatternSelector selects patterns based on the log level of
+the log event. If the Level in the log event is equal to (ignoring case)
+ the name specified on the PatternMatch key attribute, then
+the pattern specified on that PatternMatch element will be used.
+
+[source,xml]
+----
+<PatternLayout>
+ <MarkerPatternSelector defaultPattern="[%-5level] %c{1.} %msg%n">
+ <PatternMatch key="FLOW" pattern="[%-5level] %c{1.} ====== %C{1.}.%M:%L %msg ======%n"/>
+ </MarkerPatternSelector>
+</PatternLayout>
+----
+
[#MarkerPatternSelector]
==== MarkerPatternSelector
@@ -1649,7 +1815,7 @@
link:../configuration.html#Scripts[Scripts] section of the Configuration
chapter. The script is passed all the properties configured in the
Properties section of the configuration, the StrSubstitutor used by the
-Confguration in the "substitutor" vairables, and the log event in the
+Confguration in the "substitutor" variables, and the log event in the
"logEvent" variable, and is expected to return the value of the
PatternMatch key that should be used, or null if the default pattern
should be used.
diff --git a/src/site/asciidoc/manual/logbuilder.adoc b/src/site/asciidoc/manual/logbuilder.adoc
new file mode 100644
index 0000000..0eba5bb
--- /dev/null
+++ b/src/site/asciidoc/manual/logbuilder.adoc
@@ -0,0 +1,79 @@
+////
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ https://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+////
+= Log4j 2 API
+
+== Log Builder
+
+Log4j has traditionally been used with logging statements like
+[source,java]
+----
+logger.error("Unable to process request due to {}", code, exception);
+----
+
+This has resulted in some confusion as to whether the exception should be a parameter to the message or
+if Log4j should handle it as a throwable. In order to make logging clearer a builder pattern has been
+added to the API. Using the builder syntax the above would be handled as:
+[source,java]
+----
+logger.atError().withThrowable(exception).log("Unable to process request due to {}", code);
+----
+
+With this syntax it is clear that the exception is to be treated as a Throwable by Log4j.
+
+The Logger class now returns a LogBuilder when any of the atTrace, atDebug, atInfo, atWarn, atError,
+atFatal, always, or atLevel(Level) methods are called. The logBuilder then allows a Marker, Throwable,
+and/or location to be added to the event before it is logged. A call to the log method always causes the
+log event to be finalized and sent.
+
+A logging statement with a Marker, Throwable, and location would look like:
+[source,java]
+----
+logger.atInfo().withMarker(marker).withLocation().withThrowable(exception).log("Login for user {} failed", userId);
+----
+Providing the location method on the LogBuilder provides two distinct advantages:
+
+1. Logging wrappers can use it to provide the location information to be used by Log4j.</li>
+2. The overhead of capturing location information when using the location method with no
+parameters is much better than having to calculate the location information when it is needed. Log4j
+can simply ask for the stack trace entry at a fixed index instead of having to walk the stack trace
+to determine the calling class. Of course, if the location information will not be used by the layout
+this will result in slower performance.</li>
+
+=== Location Performance
+
+The table below shows some of the results from the FileAppenderBenchmark and FileAppenderWithLocationBenchmark
+classes in the log4j-perf project when configured to use 4 threads. The results show that lazily including
+the location information is about 8 times slower than not including location information. While using the
+withLocation method of LogBuilder is about 3 times faster than lazily calculating the location information
+it is still about 2.5 times slower than not including location information.
+
+The tests were run on a 2018 MacBook Pro with a 2.9 GHz Intel Core i9 processor with 6 cores, 32 GB of memory
+and 1 TB of SSD storage on Java 11 using Log4j 2.13.0 and Logback 1.2.3.
+image:../images/LocationPerf.png[Location Performance]
+
+|===
+|Test|Print Location Info|No Location Info Printed
+
+|Log4j2 File| 191,509.724 ± 11339.978 ops/s| 1,407,329.130 ± 22595.997 ops/s
+|Log4j2 Log Builder withLocation()|469,200.684 ± 50025.985 ops/s|577,127.463 ± 11464.342 ops/s
+|Logback File|159,116.538 ± 1884.969 ops/s|1,240,438.384 ± 76619.873 ops/s
+|===
+As expected, when using LogBuilder with a call to the withLocation() method logging is much faster when
+location information is used in the output but significantly slower when it is not.
+
+Note: Running the tests at various times provides varying results. Although some results have been as much
+as 10% higher all results are generally affected similarly so the comparisons between them stay the same.
\ No newline at end of file
diff --git a/src/site/asciidoc/manual/lookups.adoc b/src/site/asciidoc/manual/lookups.adoc
index 89317dd..528644e 100644
--- a/src/site/asciidoc/manual/lookups.adoc
+++ b/src/site/asciidoc/manual/lookups.adoc
@@ -67,6 +67,45 @@
</RollingFile>
----
+[#DockerLookup]
+== Docker Lookup
+
+The DockerLookup can be used to lookup attributes from the Docker container the application is running in.
+
+Log4j Docker provides access to the following container attributes:
+[cols="1m,4a"]
+|===
+|Key |Description
+
+|containerId
+|The full id assigned to the container.
+
+|containerName
+|The name assigned to the container.
+
+|imageId
+|The id assigned to the image.
+
+|imageName
+|The name assigned to the image.
+
+|shortContainerId
+|The first 12 characters of the container id.
+
+|shortImageId
+|The first 12 characters of the image id.
+|===
+
+----
+<JsonLayout properties="true" compact="true" eventEol="true">
+ <KeyValuePair key="containerId" value="${docker:containerId}"/>
+ <KeyValuePair key="containerName" value="${docker:containerName}"/>
+ <KeyValuePair key="imageName" value="${docker:imageName}"/>
+</JsonLayout>
+----
+
+This Lookup is subject to the requirements listed at link:../log4j-docker/index.html[Log4j Docker Support]
+
[#EnvironmentLookup]
== Environment Lookup
@@ -97,6 +136,80 @@
</PatternLayout>
</File>
----
+[#EventLookup]
+== Event Lookup
+
+The EventLookup provides access to fields within the log event from the configuration.
+
+[cols="1m,4a"]
+|===
+|Key |Description
+
+|Exception
+|Returns the simple class name of the Exception, if one is included in the event.
+
+|Level
+|Returns the logging Level of the event.
+
+|Logger
+|Returns the name of the Logger.
+
+|Marker
+|Returns the name of the Marker associated with the log event, if one is present.
+
+|Message
+|Returns the formatted Message string.
+
+|ThreadId
+|Returns the thread id associated with the log event.
+
+|ThreadName
+|Returns the name of the thread associate with the log event.
+
+|Timestamp
+|Returns the time in milliseconds when the event occurred.
+
+|===
+
+In this example the RoutingAppender picks a route based on the presence of a Marker named "AUDIT" being
+present in the log event.
+[source,prettyprint,linenums]
+----
+<?xml version="1.0" encoding="UTF-8"?>
+<Configuration status="WARN" name="RoutingTest">
+ <Appenders>
+ <Console name="STDOUT" target="SYSTEM_OUT" />
+ <Flume name="AuditLogger" compress="true">
+ <Agent host="192.168.10.101" port="8800"/>
+ <Agent host="192.168.10.102" port="8800"/>
+ <RFC5424Layout enterpriseNumber="18060" includeMDC="true" appName="MyApp"/>
+ </Flume>
+ <Routing name="Routing">
+ <Routes>
+ <Route pattern="$${event:Marker}">
+ <RollingFile
+ name="Rolling-${mdc:UserId}"
+ fileName="${mdc:UserId}.log"
+ filePattern="${mdc:UserId}.%i.log.gz">
+ <PatternLayout>
+ <pattern>%d %p %c{1.} [%t] %m%n</pattern>
+ </PatternLayout>
+ <SizeBasedTriggeringPolicy size="500" />
+ </RollingFile>
+ </Route>
+ <Route ref="AuditLogger" key="AUDIT"/>
+ <Route ref="STDOUT" key="STDOUT"/>
+ </Routes>
+ <IdlePurgePolicy timeToLive="15" timeUnit="minutes"/>
+ </Routing>
+ </Appenders>
+ <Loggers>
+ <Root level="error">
+ <AppenderRef ref="Routing" />
+ </Root>
+ </Loggers>
+</Configuration>
+----
[#JavaLookup]
== Java Lookup
@@ -182,6 +295,58 @@
*Java's JMX module is not available on Android or on Google App Engine.*
+[#KubernetesLookup]
+== Kubernetes Lookup
+
+The KubernetesLookup can be used to lookup attributes from the Kubernetes environment for the container
+the application is running in.
+
+Log4j Kubernetes provides access to the following container attributes:
+[cols="1m,4a"]
+|===
+|Attribute |Description
+|accountName|The service account name
+|clusterName|The name of the cluster the application is deployed in
+|containerId|>The full id assigned to the container
+|containerName|The name assigned to the container
+|host|The name assigned to the host operating system
+|hostIp|The host's ip address
+|imageId|The id assigned to the container image
+|imageName|The name assigned to the container image
+|labels|All labels formatted in a list
+|labesl.app|The application name
+|labels.podTemplateHash|The pod's template hash value
+|masterUrl|The URL used to access the API server
+|namespaceId|The id of the namespace the various kubernetes components are located within
+|namespaceName|The namespace the various kubernetes components are located within
+|podId|The pod's ip number
+|podIp|The pod's ip address
+|podName|The name of the pod
+
+ <GelfLayout includeStackTrace="true" host="${hostName}" includeThreadContext="true" includeNullDelimiter="true" compressionType="OFF">
+ <ThreadContextIncludes>requestId,sessionId,loginId,userId,ipAddress,callingHost</ThreadContextIncludes>
+ <MessagePattern>%d [%t] %-5p %X{requestId, sessionId, loginId, userId, ipAddress} %C{1.}.%M:%L - %m%n</MessagePattern>
+ <KeyValuePair key="docker.containerId" value="${docker:containerId:-}"/>
+ <KeyValuePair key="application" value="$${lower:${spring:spring.application.name}}"/>
+ <KeyValuePair key="kubernetes.serviceAccountName" value="${k8s:accountName:-}"/>
+ <KeyValuePair key="kubernetes.clusterName" value="${k8s:clusterName:-}/>
+ <KeyValuePair key="kubernetes.containerId" value="${k8s:containerId:-}"/>
+ <KeyValuePair key="kubernetes.containerName" value="${k8s:containerName:-}"/>
+ <KeyValuePair key="kubernetes.host" value="${k8s:host:-}"/>
+ <KeyValuePair key="kubernetes.labels.app" value="${k8s:labels.app:-}"/>
+ <KeyValuePair key="kubernetes.labels.pod-template-hash" value="${k8s:labels.podTemplateHash:-}"/>
+ <KeyValuePair key="kubernetes.master_url" value="${k8s:masterUrl:-}"/>
+ <KeyValuePair key="kubernetes.namespaceId" value="${k8s:namespaceId:-}"/>
+ <KeyValuePair key="kubernetes.namespaceName" value="${k8s:namespaceName:-}"/>
+ <KeyValuePair key="kubernetes.podID" value="${k8s:podId:-}"/>
+ <KeyValuePair key="kubernetes.podIP" value="${k8s:podIp:-}"/>
+ <KeyValuePair key="kubernetes.podName" value="${k8s:podName:-}"/>
+ <KeyValuePair key="kubernetes.imageId" value="${k8s:imageId:-}"/>
+ <KeyValuePair key="kubernetes.imageName" value="${k8s:imageName:-}"/>
+ </GelfLayout>]]></pre>
+
+This Lookup is subject to the configuration requirements listed at link:../log4j-kubernetes/index.html[Log4j Kubernetes Support]
+
[#Log4jConfigLookup]
== Log4j Configuration Location Lookup
@@ -202,6 +367,21 @@
</File>
----
+[#LowerLookup]
+== Lower Lookup
+
+The LowerLookup converts the passed in argument to lower case. Presumably the value will be the
+result of a nested lookup.
+
+[source,xml]
+----
+<File name="Application" fileName="application.log">
+ <PatternLayout>
+ <pattern>%d %p %c{1.} [%t] $${lower:${spring:spring.application.name}} %m%n</pattern>
+ </PatternLayout>
+</File>
+----
+
[#AppMainArgsLookup]
== Main Arguments Lookup (Application)
@@ -225,6 +405,11 @@
`${main:myString}` is substituted with the value that follows `myString`
in the main argument list.
+Note: Many applications use leading dashes to identify command arguments. Specifying
+`${main:--file}` would result in the lookup failing because it would look for a variable
+named "main" with a default value of "-file". To avoid this the ":" separating the Lookup name from the
+key must be followed by a backslash as an escape character as in `${main:\--file}`.
+
For example, suppose the static void main String[] arguments are:
....
@@ -252,16 +437,20 @@
|${main:4}
|bar
-|${main:--file}
+|${main:\--file}
|foo.txt
-|${main:-x}
+|${main:\-x}
|bar
|${main:bar}
|null
|===
+|${main:\--quiet:-true}
+|true
+|===
+
Example usage:
[source,xml]
@@ -411,6 +600,31 @@
</Routing>
----
+[#SpringLookup]
+== Spring Boot Lookup
+
+
+The Spring Boot Lookup retrieves the values of Spring properties from the Spring configuration as well as
+values of the active and default profiles. Specifying a key of `profiles.active` will reutrn the active
+profiles while a key of `profiles.default` will return the default profiles. The default and active
+profiles can be an array. If more than one profile is present they will be returned as a comma separated
+list. To retrieve a single item from the array append `[{index}]` to the key. For example, to return the
+first active profile in the list specify `profiles.active[0]`.
+
+This Lookup will return null values until Spring Boot initializes application logging.
+
+
+[source,xml]
+----
+<File name="Application" fileName="application-${spring:profiles.active[0]}.log">
+ <PatternLayout>
+ <pattern>%d %p %c{1.} [%t] $${spring:spring.application.name} %m%n</pattern>
+ </PatternLayout>
+</File>
+----
+
+This Lookup requires log4j-spring-cloud-config-client be included in the application.
+
[#SystemPropertiesLookup]
== System Properties Lookup
@@ -438,6 +652,21 @@
</Appenders>
----
+[#UpperLookup]
+== Upper Lookup
+
+The LowerLookup converts the passed in argument to upper case. Presumably the value will be the
+result of a nested lookup.
+
+[source,xml]
+----
+<File name="Application" fileName="application.log">
+ <PatternLayout>
+ <pattern>%d %p %c{1.} [%t] $${upper:{${spring:spring.application.name}} %m%n</pattern>
+ </PatternLayout>
+</File>
+----
+
[#WebLookup]
== Web Lookup
@@ -455,9 +684,51 @@
|attr._name_
|Returns the ServletContext attribute with the specified name
+|request.attr._name_
+|Returns the ServletRequest attribute with the specified name - requires Log4jServletFilter
+
+|header._name_
+|Returns the HttpServletRequest header with the specified name - requires Log4jServletFilter
+
+|cookie._name_
+|Returns the HttpServletRequest cookie with the specified name - requires Log4jServletFilter
+
+|header._name_
+|Returns the HttpServletRequest header with the specified name - requires Log4jServletFilter
+
+|request._method_
+|Returns the HttpServletRequest method - requires Log4jServletFilter
+
+|request._uri_
+|Returns the HttpServletRequest URI - requires Log4jServletFilter
+
+|request._url_
+|Returns the HttpServletRequest URL - requires Log4jServletFilter
+
+|request._remoteAddress_
+|Returns the HttpServletRequest remote address - requires Log4jServletFilter
+
+|request._remoteHost_
+|Returns the HttpServletRequest remote host - requires Log4jServletFilter
+
+|request.parameter._name_
+|Returns the HttpServletRequest parameter - requires Log4jServletFilter
+
+|request.principal
+|Returns the HttpServletRequest principal name - requires Log4jServletFilter
+
+|session.id
+|Returns the HttpSession id or null if none is started - requires Log4jServletFilter
+
+|session.attr._name_
+|Returns the HttpSession attribute value (using _toString()_ if not null) or null if absent - requires Log4jServletFilter
+
|contextPath
|The context path of the web application
+|contextPathName
+|The first token in the context path of the web application splitting on "/" characters.
+
|effectiveMajorVersion
|Gets the major version of the Servlet specification that the application
represented by this ServletContext is based on.
@@ -496,3 +767,22 @@
<File name="ApplicationLog" fileName="${web:rootDir}/app.log"/>
</Appenders>
----
+
+=== Request lookups and asynchronous calls
+
+Servlet 3.0 supports asynchronous calls, by default the request tracking - and therefore request related lookups,
+will not work. To make it work you can extract the servlet context attribute `log4j.requestExecutor` which
+is a `BiConsumer<ServletRequest, Runnable>` and call it passing the correct request and task to execute
+synchronously. During this task execution the lookups will be set up properly:
+
+[source,java]
+----
+@GET // example using JAX-RS asynchronous feature backed by servlet AsyncContext
+public void get(@Suspended AsyncResponse response,
+ @Context ServletContext context,
+ @Context ServletRequest request) {
+ final BiConsumer<ServletRequest, Runnable> log4jWrapper =
+ (BiConsumer<ServletRequest, Runnable>) context.getAttribute("log4j.requestExecutor");
+ myThreadPool.submit(() -> log4jWrapper.accept(request, () -> response.resume(doInternalGet()));
+}
+----
diff --git a/src/site/asciidoc/manual/messages.adoc b/src/site/asciidoc/manual/messages.adoc
index 8c27dc3..cf0e069 100644
--- a/src/site/asciidoc/manual/messages.adoc
+++ b/src/site/asciidoc/manual/messages.adoc
@@ -228,8 +228,8 @@
* When a link:appenders.html#JDBCAppender[JDBC Appender] is configured
with a `MessageLayout`, it converts a Log4j `MapMessage` to values in a
SQL INSERT statement.
-* When a link:appenders.html#NoSQLAppenderMongoDB2[MongoDB2 Appender] or
-link:appenders.html#NoSQLAppenderMongoDB3[MongoDB3 Appender] is
+* When a link:appenders.html#NoSQLAppenderMongoDB3[MongoDB3 Appender] or
+link:appenders.html#NoSQLAppenderMongoDB4[MongoDB4 Appender] is
configured with a `MessageLayout`, it converts a Log4j `MapMessage` to
fields in a MongoDB object.
diff --git a/src/site/asciidoc/manual/plugins.adoc b/src/site/asciidoc/manual/plugins.adoc
index 5b3a988..694d7bf 100644
--- a/src/site/asciidoc/manual/plugins.adoc
+++ b/src/site/asciidoc/manual/plugins.adoc
@@ -32,17 +32,21 @@
to load the built-in Log4j plugins as well as any custom plugins. The
`PluginManager` locates plugins by looking in five places:
-1. Serialized plugin listing files on the classpath. These files are
-generated automatically during the build (more details below).
+1. Plugin collection classes on the classpath that are loaded by java.util.ServiceLoader.
+These classes are generated automatically during the build (more details below).
2. (OSGi only) Serialized plugin listing files in each active OSGi
bundle. A `BundleListener` is added on activation to continue checking
-new bundles after `log4j-core` has started.
+new bundles after `log4j-plugins` has started. Bundles must register their plugin collection
+class as an OSGi service.
3. A comma-separated list of packages specified by the
`log4j.plugin.packages` system property.
4. Packages passed to the static `PluginManager.addPackages` method
(before Log4j configuration occurs).
5. The link:./configuration.html#ConfigurationSyntax[packages] declared
in your log4j2 configuration file.
+6. Serialized plugin listing files on the classpath. These files were generated by
+the plugin annotation processor in Log4j 2 2.x. These are processed to allow
+compatibility.
If multiple Plugins specify the same (case-insensitive) `name`, then the
load order above determines which one will be used. For example, to
@@ -61,16 +65,26 @@
TODO: in future, plugins will be able to be annotated with @Order which can override priorities
////
-Serialized plugin listing files are generated by an annotation processor
-contained in the log4j-core artifact which will automatically scan your
-code for Log4j 2 plugins and output a metadata file in your processed
-classes. There is nothing extra that needs to be done to enable this;
+Plugin collection classes are generated by an annotation processor contained
+in the log4j-plugins artifact which will automatically scan your code for
+Log4j 2 plugins and generate a Java source file that references all the
+located plugins. It will also generate a
+META-INF/services/org.apache.logging.log4j.plugins.processor.PluginService
+file in compliance with java.util.ServiceLoader.
+There is nothing extra that needs to be done to enable this;
the Java compiler will automatically pick up the annotation processor on
-the class path unless you explicitly disable it. In that case, it would
-be important to add another compiler pass to your build process that
+the class path unless you explicitly disable it.
+
+If annotation processing is disabled plugins may still be registered by either
+[loweralpha]
+.. manually providing a class that extends `org.apache.logging.log4j.plugins.processor.PluginService`
+and identifies all the plugins and also declaring a
+`META-INF/services/org.apache.logging.log4j.plugins.processor.PluginService` file
+that provides the fully qualified name of the implemented class or
+.. adding another compiler pass to the build process that
only handles annotation processing using the Log4j 2 annotation
processor class,
-`org.apache.logging.log4j.core.config.plugins.processor.PluginProcessor`.
+`org.apache.logging.log4j.plugins.processor.PluginProcessor`.
To do this using Apache Maven, add the following execution to your
_maven-compiler-plugin_ (version 2.2 or higher) build plugin:
@@ -90,7 +104,7 @@
<configuration>
<proc>only</proc>
<annotationProcessors>
- <annotationProcessor>org.apache.logging.log4j.core.config.plugins.processor.PluginProcessor</annotationProcessor>
+ <annotationProcessor>org.apache.logging.log4j.plugins.processor.PluginProcessor</annotationProcessor>
</annotationProcessors>
</configuration>
</execution>
@@ -99,9 +113,10 @@
----
As the configuration is processed the appropriate plugins will be
-automatically configured and initialized. Log4j 2 utilizes a few
-different categories of plugins which are described in the following
-sections.
+automatically configured and initialized.
+
+Log4j 2 utilizes a few different categories of plugins which are described
+in the following sections.
[#Core]
== Core
@@ -112,18 +127,27 @@
may simply be referenced in the configuration, provided they are
appropriate configured to be loaded by the PluginManager.
-Every Core plugin must declare a static method annotated with
-`@PluginFactory` or `@PluginBuilderFactory`. The former is used for
-static factory methods that provide all options as method parameters,
-and the latter is used to construct a new `Builder<T>` class whose
-fields are used for injecting attributes and child nodes. To allow the
-`Configuration` to pass the correct parameters to the method, every
-parameter to the method must be annotated as one of the following
-attribute types. Each attribute or element annotation must include the
+Core plugins follow a few simple dependency injection rules for binding
+`Configuration` parameters into a factory entry point. Each plugin must
+declare a static method annotated with `@PluginFactory`.
+This method can either list all options as method parameters, or it can return
+a new `Builder<T>` instance where options are available as either single-argument
+`setFoo` or `withFoo` methods or fields. There are a few different attribute types
+available to denote `Configuration` data that are injected by the plugin
+framework. These are available as annotations that can either be used on the
+method parameters to return an instance of the plugin, or they can be used on the
+returned builder class's methods or fields. Each annotation may include the
name that must be present in the configuration in order to match the
-configuration item to its respective parameter. For plugin builders, the
-names of the fields will be used by default if no name is specified in
-the annotation. There are dozens of plugins in Log4j Core that can be
+configuration item to its respective parameter. If no name is specified, then
+the name used is as follows:
+
+* When annotated on a parameter, this will use the parameter name.
+* When annotated on a single argument method, if the method name is prefixed by
+ `set` or `with`, this will use the remainder of the method name. Otherwise, the
+ full method name is used.
+* When annotated on a field, this will used the field name.
+
+There are dozens of plugins in Log4j Core that can be
used as examples for more complex scenarios including hierarchical
builder classes (e.g., see `FileAppender`). See
link:extending.html#Plugin_Builders[Extending Log4j with Plugin
@@ -131,40 +155,40 @@
=== Attribute Types
-link:../log4j-core/apidocs/org/apache/logging/log4j/core/config/plugins/PluginAttribute.html[`PluginAttribute`]::
+link:../log4j-plugins/apidocs/org/apache/logging/log4j/plugins/PluginAttribute.html[`PluginAttribute`]::
The parameter must be convertible from a String using a
link:#TypeConverters[TypeConverter]. Most built-in types are already
supported, but custom `TypeConverter` plugins may also be provided for
more type support. Note that `PluginBuilderAttribute` can be used in
builder class fields as an easier way to provide default values.
-link:../log4j-core/apidocs/org/apache/logging/log4j/core/config/plugins/PluginElement.html[`PluginElement`]::
+link:../log4j-plugins/apidocs/org/apache/logging/log4j/plugins/PluginElement.html[`PluginElement`]::
The parameter may represent a complex object that itself has
parameters that can be configured. This also supports injecting an
array of elements.
link:../log4j-core/apidocs/org/apache/logging/log4j/core/config/plugins/PluginConfiguration.html[`PluginConfiguration`]::
The current `Configuration` object will be passed to the plugin as a
parameter.
-link:../log4j-core/apidocs/org/apache/logging/log4j/core/config/plugins/PluginNode.html[`PluginNode`]::
+link:../log4j-plugins/apidocs/org/apache/logging/log4j/plugins/PluginNode.html[`PluginNode`]::
The current `Node` being parsed will be passed to the plugin as a
parameter.
-link:../log4j-core/apidocs/org/apache/logging/log4j/core/config/plugins/PluginValue.html[`PluginValue`]::
+link:../log4j-plugins/apidocs/org/apache/logging/log4j/plugins/PluginValue.html[`PluginValue`]::
The value of the current `Node` or its attribute named `value`.
=== Constraint Validators
-Plugin factory fields and parameters can be automatically validated at
+Plugin factory fields, methods, and parameters can be automatically validated at
runtime using constraint validators inspired by the
http://beanvalidation.org/[Bean Validation spec]. The following
annotations are bundled in Log4j, but custom
-link:../log4j-core/apidocs/org/apache/logging/log4j/core/config/plugins/validation/ConstraintValidator.html[`ConstraintValidators`]
+link:../log4j-plugins/apidocs/org/apache/logging/log4j/plugins/validation/ConstraintValidator.html[`ConstraintValidators`]
can be created as well.
-link:../log4j-core/apidocs/org/apache/logging/log4j/core/config/plugins/validation/constraints/Required.html[`Required`]::
+link:../log4j-plugins/apidocs/org/apache/logging/log4j/plugins/validation/constraints/Required.html[`Required`]::
This annotation validates that a value is non-empty. This covers a
check for `null` as well as several other scenarios: empty
`CharSequence` objects, empty arrays, empty `Collection` instances,
and empty `Map` instances.
-link:../log4j-core/apidocs/org/apache/logging/log4j/core/config/plugins/validation/constraints/ValidHost.html[`ValidHost`]::
+link:../log4j-plugins/apidocs/org/apache/logging/log4j/plugins/validation/constraints/ValidHost.html[`ValidHost`]::
This annotation validates that a value corresponds to a valid
hostname. This uses the same validation as
http://docs.oracle.com/javase/8/docs/api/java/net/InetAddress.html#getByName-java.lang.String-[`InetAddress::getByName`].
@@ -235,7 +259,7 @@
[#TypeConverters]
== TypeConverters
-link:../log4j-core/apidocs/org/apache/logging/log4j/core/config/plugins/convert/TypeConverter.html[`TypeConverter`]s
+link:../log4j-plugins/apidocs/org/apache/logging/log4j/plugins/convert/TypeConverter.html[`TypeConverter`]s
are a sort of meta-plugin used for converting strings into other types
in a plugin factory method parameter. Other plugins can already be
injected via the `@PluginElement` annotation; now, any type supported by
@@ -243,7 +267,9 @@
parameter. Conversion of enum types are supported on demand and do not
require custom `TypeConverter` classes. A large number of built-in Java
classes are already supported; see
-link:../log4j-core/apidocs/org/apache/logging/log4j/core/config/plugins/convert/TypeConverters.html[`TypeConverters`]
+link:../log4j-plugins/apidocs/org/apache/logging/log4j/plugins/convert/TypeConverters.html['TypeConverters']
+and
+link:../log4j-core/apidocs/org/apache/logging/log4j/core/config/plugins/convert/CoreConverters.html[`CoreConverters`]
for a more exhaustive listing.
Unlike other plugins, the plugin name of a `TypeConverter` is purely
diff --git a/src/site/asciidoc/manual/thread-context.adoc b/src/site/asciidoc/manual/thread-context.adoc
index e32cb79..59e862a 100644
--- a/src/site/asciidoc/manual/thread-context.adoc
+++ b/src/site/asciidoc/manual/thread-context.adoc
@@ -188,8 +188,7 @@
http://docs.oracle.com/javase/6/docs/api/java/lang/ThreadLocal.html[`ThreadLocal`]
by default. The Map can be configured to use an
http://docs.oracle.com/javase/6/docs/api/java/lang/InheritableThreadLocal.html[`InheritableThreadLocal`]
-by setting system property `log4j2.isThreadContextMapInheritable` to
-`true`. When configured this way, the contents of the Map will be passed
+(see the Configuration section). When configured this way, the contents of the Map will be passed
to child threads. However, as discussed in the
http://docs.oracle.com/javase/6/docs/api/java/util/concurrent/Executors.html#privilegedThreadFactory()[`Executors`]
class and in other cases where thread pooling is utilized, the
@@ -202,10 +201,17 @@
link:../log4j-api/apidocs/org/apache/logging/log4j/ThreadContext.html[`ThreadContext`]
class are static.
+==== Configuration
+Set the system property `disableThreadContextMap` to `true` to disable the Thread Context Map.
+Set the system property `disableThreadContextStack` to `true` to disable the Thread Context Stack.
+Set the system property `disableThreadContext` to `true` to disable both the Thread Context Map and Stack.
+Set the system property `log4j2.isThreadContextMapInheritable` to `true` to enable child threads to inherit the Thread
+Context Map.
+
=== Including the ThreadContext when writing logs
The
-link:../log4j-api/apidocs/org/apache/logging/log4j/core/PatternLayout.html[`PatternLayout`]
+link:../log4j-core/apidocs/org/apache/logging/log4j/core/layout/PatternLayout.html[`PatternLayout`]
provides mechanisms to print the contents of the
link:../log4j-api/apidocs/org/apache/logging/log4j/ThreadContext.html[`ThreadContext`]
Map and Stack.
diff --git a/src/site/asciidoc/manual/usage.adoc b/src/site/asciidoc/manual/usage.adoc
index 513add8..76f1629 100644
--- a/src/site/asciidoc/manual/usage.adoc
+++ b/src/site/asciidoc/manual/usage.adoc
@@ -20,7 +20,7 @@
[#Static_vs_Non_Static]
== Static vs Non-Static Loggers
As with any variable in Java, Loggers may be declared as static variables or class member variables. However,
-there are a few factors to consider when choosing to delare a logger as static vs non-static. Generally, it
+there are a few factors to consider when choosing to declare a logger as static vs non-static. Generally, it
is better to declare Loggers as static.
1. Instantiation of a new Logger is a fairly expensive operation when using the default ContextSelector,
@@ -194,7 +194,7 @@
7. Logging is performed in the parent class using the logger of Child. The logger name matches the name of the child
and so it is printed.
8. Logging is performed in the parent class using the logger of the Child. Although the method was called against
-the Child instance it is implemented in PArent so that is what appears as the class name.
+the Child instance it is implemented in Parent so that is what appears as the class name.
9. Logging is performed in Child using the logger in the parent which is set to the child logger, so the name of the
child is printed as the logger name.
10. Logging is performed in Child using the logger in the parent, which is set to the child logger. Since
diff --git a/src/site/asciidoc/runtime-dependencies.adoc b/src/site/asciidoc/runtime-dependencies.adoc
index 2224963..e13b82c 100644
--- a/src/site/asciidoc/runtime-dependencies.adoc
+++ b/src/site/asciidoc/runtime-dependencies.adoc
@@ -64,6 +64,18 @@
|org.apache.logging.log4j.appserver
|Automatic Module
+|log4j-cassandra
+|org.apache.logging.log4j.cassandra
+|Automatic Module
+
+|log4j-couchdb
+|org.apache.logging.log4j.couchdb
+|Automatic Module
+
+|log4j-docker
+|org.apache.logging.log4j.docker
+|Automatic Module
+
|log4j-flume-ng
|org.apache.logging.log4j.flume
|Automatic Module
@@ -84,18 +96,10 @@
|org.apache.logging.log4j.jul
|Automatic Module
-|log4j-couchdb
-|org.apache.logging.log4j.couchdb
-|Automatic Module
-
|log4j-mongodb
|org.apache.logging.log4j.mongodb
|Automatic Module
-|log4j-cassandra
-|org.apache.logging.log4j.cassandra
-|Automatic Module
-
|log4j-osgi
|org.apache.logging.log4j.osgi
|Automatic Module. Unclear how OSGi will support Java modules.
@@ -209,86 +213,101 @@
|ZeroMQ Appender
|The ZeroMQ appender uses the https://github.com/zeromq/jeromq[JeroMQ] library which is licensed under the terms of the Mozilla Public License Version 2.0 (MPLv2).
For details see the file https://github.com/zeromq/jeromq/blob/master/LICENSE[LICENSE] included with the JeroMQ distribution.
-|===
-log4j-jcl[[log4j-jcl]]::
-The link:log4j-jcl/index.html[Commons Logging Bridge] requires
+|log4j-1.2-api[[log4j-1.2-api]]
+|The link:log4j-1.2-api/index.html[Log4j 1.2 Bridge] has no external
+dependencies. This only requires the Log4j API. Including Log4j Core provides optional, extra functionality.
+
+|log4j-api-scala[[log4j-api-scala]]
+|The Log4j link:manual/scala-api.html[Scala API] requires Scala runtime
+library and reflection in addition to the Log4j API.
+
+|log4j-cassandra[[log4j-cassandra]]
+|The Log4j link:log4j-cassandra/index.html[Cassandra] module depends on the
+http://docs.datastax.com/en/developer/driver-matrix/doc/javaDrivers.html[Datastax
+Cassandra driver].
+
+|log4j-couchdb[[log4j-couchdb]]
+|The Log4j link:log4j-couchdb/index.html[CouchDB] module depends on the
+http://www.lightcouch.org/[LightCouch] CouchDB client library.
+
+|log4j-docker
+|link:log4j-docker/index.html[Log4j Docker Support] requires
+https://github.com/FasterXML/jackson[Jackson annotations, core, and databind]. See the
+link:log4j-docker/dependencies.html#Dependency_Tree[Dependency Tree] for the exact list of
+JAR files needed.
+
+|log4j-flume-ng[[log4j-flume-ng]]
+|The link:log4j-flume-ng/index.html[Flume Appender] requires
+http://flume.apache.org/[Apache Flume] and
+http://avro.apache.org/[Apache Avro]. The persistent agent uses Berkeley DB. See the
+link:log4j-flume-ng/dependencies.html#Dependency_Tree[Dependency Tree]
+for the exact list of JAR files needed.
+
+|log4j-iostreams[[log4j-iostreams]]
+|The Log4j link:log4j-iostreams/index.html[IO Streams] module has no
+external dependencies. This only requires the Log4j API.
+
+|log4j-jcl[[log4j-jcl]]
+|The link:log4j-jcl/index.html[Commons Logging Bridge] requires
http://commons.apache.org/proper/commons-logging/[Commons Logging]. See
the link:log4j-jcl/dependencies.html#Dependency_Tree[Dependency Tree]
for the exact list of JAR files needed.
-log4j-1.2-api[[log4j-1.2-api]]::
-The link:log4j-1.2-api/index.html[Log4j 1.2 Bridge] has no external
-dependencies. This only requires the Log4j API and Log4j Core.
+|log4j-jmx-gui[[log4j-jmx-gui]]
+|The Log4j link:log4j-jmx-gui/index.html[JMX GUI] requires the JConsole
+jar when run as a JConsole plugin. Otherwise it has no external
+dependencies. See the
+link:log4j-jmx-gui/dependencies.html#Dependency_Tree[Dependency Tree]
+for the exact list of JAR files needed.
-log4j-slf4j-impl[[log4j-slf4j-impl]]::
-The Log4j 2 link:log4j-slf4j-impl/index.html[SLF4J Binding] depends on
+|log4j-jul[[log4j-jul]]
+|The Log4j 2 link:log4j-jul/index.html[Java Util Logging Adapter] has no
+external dependencies. It optionally depends on the
+link:log4j-api/index.html[Log4j Core] library. The only required module
+is the Log4j API.
+
+|log4j-mongodb[[log4j-mongodb]]
+|The Log4j link:log4j-mongodb/index.html[MongoDB] module depends on the
+http://docs.mongodb.org/ecosystem/drivers/java/[MongoDB Java Client
+driver].
+
+|log4j-slf4j-impl[[log4j-slf4j-impl]]
+|The Log4j 2 link:log4j-slf4j-impl/index.html[SLF4J Binding] depends on
the http://www.slf4j.org/[SLF4J] API. See the
link:log4j-slf4j-impl/dependencies.html#Dependency_Tree[Dependency Tree]
for the exact list of JAR files needed.
WARNING: Do not use this with the link:#log4j-to-slf4j[log4j-to-slf4j] module.
-log4j-jul[[log4j-jul]]::
-The Log4j 2 link:log4j-jul/index.html[Java Util Logging Adapter] has no
-external dependencies. It optionally depends on the
-link:log4j-api/index.html[Log4j Core] library. The only required module
-is the Log4j API.
+|log4j-spring-cloud-config-client[[log4j-spring-cloud-config-client]]
+|link:log4j-spring-cloud-config/log4j-spring-cloud-config-client/index.html[Log4j Spring Cloud Config Client] requires
+https://spring.io/projects/spring-cloud-config[Spring Cloud Config].
+https://spring.io/projects/spring-cloud-bus[Spring Cloud Bus] is required if notification of logging
+configuration changes is desired. https://spring.io/projects/spring-boot[Spring Boot] is required
+but applications do not have to be packaged as a Spring Boot application.
+See link:log4j-spring-cloud-config/log4j-spring-cloud-config-client/dependencies.html#Dependency_Tree[Dependency Tree]
+for the exact list of JAR files needed.
-log4j-to-slf4j[[log4j-to-slf4j]]::
-The link:log4j-to-slf4j/index.html[Log4j 2 to SLF4J Adapter] requires
+|log4j-taglib[[log4j-taglib]]
+|The Log4j link:log4j-taglib/index.html[Log Tag Library] requires the
+http://jakarta.apache.org/taglibs/log/[Jakarta Commons Log Taglib] and
+the Servlet API. See the
+link:log4j-taglib/dependencies.html#Dependency_Tree[Dependency Tree] for
+the exact list of JAR files needed.
+
+|log4j-to-slf4j[[log4j-to-slf4j]]
+|The link:log4j-to-slf4j/index.html[Log4j 2 to SLF4J Adapter] requires
the http://www.slf4j.org/[SLF4J] API and an SLF4J implementation. See
the link:log4j-to-slf4j/dependencies.html#Dependency_Tree[Dependency
Tree] for the exact list of JAR files needed.
WARNING: Do not use this with the link:#log4j-slf4j-impl[log4j-slf4j-impl] module.
-log4j-flume-ng[[log4j-flume-ng]]::
-The link:log4j-flume-ng/index.html[Flume Appender] requires
-http://flume.apache.org/[Apache Flume] and
-http://avro.apache.org/[Apache Avro]. The persistent agent uses Berkeley
-DB. See the
-link:log4j-flume-ng/dependencies.html#Dependency_Tree[Dependency Tree]
-for the exact list of JAR files needed.
-
-log4j-taglib[[log4j-taglib]]::
-The Log4j link:log4j-taglib/index.html[Log Tag Library] requires the
-http://jakarta.apache.org/taglibs/log/[Jakarta Commons Log Taglib] and
-the Servlet API. See the
-link:log4j-taglib/dependencies.html#Dependency_Tree[Dependency Tree] for
-the exact list of JAR files needed.
-
-log4j-jmx-gui[[log4j-jmx-gui]]::
-The Log4j link:log4j-jmx-gui/index.html[JMX GUI] requires the JConsole
-jar when run as a JConsole plugin. Otherwise it has no external
-dependencies. See the
-link:log4j-jmx-gui/dependencies.html#Dependency_Tree[Dependency Tree]
-for the exact list of JAR files needed.
-
-log4j-web[[log4j-web]]::
-The Log4j link:log4j-web/index.html[Web] module requires the Servlet
+|log4j-web[[log4j-web]]
+|The Log4j link:log4j-web/index.html[Web] module requires the Servlet
API. See the link:log4j-web/dependencies.html#Dependency_Tree[Dependency
Tree] for the exact list of JAR files needed. Note that this works with
the Servlet 2.5 API as well as the Servlet 3.x API.
-log4j-couchdb[[log4j-couchdb]]::
-The Log4j link:log4j-couchdb/index.html[CouchDB] module depends on the
-http://www.lightcouch.org/[LightCouch] CouchDB client library.
-
-log4j-mongodb[[log4j-mongodb]]::
-The Log4j link:log4j-mongodb/index.html[MongoDB] module depends on the
-http://docs.mongodb.org/ecosystem/drivers/java/[MongoDB Java Client
-driver].
-
-log4j-cassandra[[log4j-cassandra]]::
-The Log4j link:log4j-cassandra/index.html[Cassandra] module depends on the
-http://docs.datastax.com/en/developer/driver-matrix/doc/javaDrivers.html[Datastax
-Cassandra driver].
-
-log4j-iostreams[[log4j-iostreams]]::
-The Log4j link:log4j-iostreams/index.html[IO Streams] module has no
-external dependencies. This only requires the Log4j API.
-
-log4j-api-scala[[log4j-api-scala]]::
-The Log4j link:manual/scala-api.html[Scala API] requires Scala runtime
-library and reflection in addition to the Log4j API.
+|===
\ No newline at end of file
diff --git a/src/site/asciidoc/security.adoc b/src/site/asciidoc/security.adoc
new file mode 100644
index 0000000..6eab1fa
--- /dev/null
+++ b/src/site/asciidoc/security.adoc
@@ -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
+
+ https://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+////
+
+# Apache Log4j Security Vulnerabilities
+
+This page lists all the security vulnerabilities fixed in released versions of Apache Log4j 2.
+Each vulnerability is given a link:#Security_Impact_Levels[security impact rating]
+by the mailto:private@logging.apache.org[Apache Logging security team].
+please note that this rating may vary from platform to platform. We also list the versions
+of Apache Log4j the flaw is known to affect, and where a flaw has not been verified list
+the version with a question mark.
+
+Note: Vulnerabilities that are not Log4j vulnerabilities but have either been incorrectly
+reported against Log4j or where Log4j provides a workaround are listed at the end of this page.
+
+Please note that Log4j 1.x has reached end of life and is no longer supported. Vulnerabilities
+reported after August 2015 against Log4j 1.x were not checked and will not be fixed. Users should
+upgrade to Log4j 2 to obtain security fixes.
+
+Please note that binary patches are never provided. If you need to apply a source code patch,
+use the building instructions for the Apache Log4j version that you are using. For
+Log4j 2 this is BUILDING.md. This file can be found in the
+root subdirectory of a source distributive.
+
+If you need help on building or configuring Log4j or other help on following the instructions
+to mitigate the known vulnerabilities listed here, please send your questions to the public
+Log4j Users mailing list
+
+If you have encountered an unlisted security vulnerability or other unexpected behaviour
+that has security impact, or if the descriptions here are incomplete, please report them
+privately to the mailto:private@logging.apache.org[Log4j Security Team]. Thank you.
+
+### Fixed in Log4j 2.13.2
+
+https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2020-9488[CVE-2020-9488]:
+Improper validation of certificate with host mismatch in Apache Log4j SMTP appender.
+
+Severity: Low
+
+CVSS Base Score: 3.7 (Low) CVSS:3.0/AV:N/AC:H/PR:N/UI:N/S:U/C:L/I:N/A:N
+
+Versions Affected: all versions from 2.0-alpha1 to 2.13.1
+
+Descripton: Improper validation of certificate with host mismatch in
+Log4j2 SMTP appender. This could allow an SMTPS connection to be
+intercepted by a man-in-the-middle attack which could leak any log
+messages sent through that appender.
+
+The reported issue was caused by an error in SslConfiguration. Any element using SslConfiguration
+in the Log4j Configuration is also affected by this issue. This includes HttpAppender,
+SocketAppender, and SyslogAppender. Usages of SslConfiguration that are configured via system
+properties are not affected.
+
+Mitigation: Users should upgrade to Apache Log4j 2.13.2 which fixed
+this issue in LOG4J2-2819 by making SSL settings configurable for
+SMTPS mail sessions. As a workaround for previous releases, users can
+set the `mail.smtp.ssl.checkserveridentity` system property to `true`
+to enable SMTPS hostname verification for all SMTPS mail sessions.
+
+Credit: This issues was discovered by Peter Stöckli.
+
+References: https://issues.apache.org/jira/browse/LOG4J2-2819
+
+### Fixed in Log4j 2.8.2
+
+https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2017-5645[CVE-2017-5645]: Apache Log4j socket
+receiver deserialization vulnerability.
+
+Severity: Moderate
+
+CVSS Base Score: 7.5 (AV:N/AC:L/Au:N/C:P/I:P/A:P)
+
+Versions Affected: all versions from 2.0-alpha1 to 2.8.1
+
+Description: When using the TCP socket server or UDP socket server to
+receive serialized log events from another application, a specially crafted
+binary payload can be sent that, when deserialized, can execute arbitrary
+code.
+
+Mitigation: Java 7+ users should migrate to version 2.8.2 or avoid using
+the socket server classes. Java 6 users should avoid using the TCP or UDP
+socket server classes, or they can manually backport the security fix from
+2.8.2: https://github.com/apache/logging-log4j2/commit/5dcc192
+
+Credit: This issue was discovered by Marcio Almeida de Macedo of Red Team
+at Telstra
+
+References: <https://issues.apache.org/jira/browse/LOG4J2-1863>
+
+[#Security_Impact_Levels]
+## Summary of security impact levels for Apache Log4j
+The Apache Log4j Security Team rates the impact of each security flaw that affects Log4j.
+We've chosen a rating scale quite similar to those used by other major vendors in order to
+be consistent. Basically the goal of the rating system is to answer the question "How worried
+should I be about this vulnerability?".
+
+Note that the rating chosen for each flaw is the worst possible case across all architectures.
+To determine the exact impact of a particular vulnerability on your own systems you will still
+need to read the security advisories to find out more about the flaw.
+
+We use the following descriptions to decide on the impact rating to give each vulnerability:
+
+### Critical
+A vulnerability rated with a Critical impact is one which could potentially be exploited by
+a remote attacker to get Log4j to execute arbitrary code (either as the user the server is
+running as, or root). These are the sorts of vulnerabilities that could be exploited automatically
+by worms.
+
+### Important
+A vulnerability rated as Important impact is one which could result in the compromise of data
+or availability of the server. For Log4j this includes issues that allow an easy remote denial
+of service (something that is out of proportion to the attack or with a lasting consequence),
+access to arbitrary files outside of the context root, or access to files that should be otherwise
+prevented by limits or authentication.
+
+### Moderate
+A vulnerability is likely to be rated as Moderate if there is significant mitigation to make the
+issue less of an impact. This might be because the flaw does not affect likely configurations, or
+it is a configuration that isn't widely used.
+
+### Low
+All other security flaws are classed as a Low impact. This rating is used for issues that are believed
+to be extremely hard to exploit, or where an exploit gives minimal consequences.
\ No newline at end of file
diff --git a/src/site/asciidoc/support.adoc b/src/site/asciidoc/support.adoc
new file mode 100644
index 0000000..3cd35c3
--- /dev/null
+++ b/src/site/asciidoc/support.adoc
@@ -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
+
+ https://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+////
+
+= Log4j Support
+
+The Apache Software Foundation does not employ individuals to develop and support any of its projects. The
+individuals who contribute to Apache projects do it either as part of specific tasks assigned to them by their
+employer, on their own initiative to benefit their employer, or on their own free time. While some projects
+at the ASF have employees who are specifically paid to work on the project, none of the committers to any
+of the Logging Services projects are directly paid to work on them.
+
+The Log4j project uses https://issues.apache.org/jira/projects/LOG4J2[Jira] as its issue tracking system.
+Issues get resolved in one of the following ways:
+
+1. The reporter or another interested party provide a patch attached to the Jira issue, or (preferred) a pull request
+is provided at the https://github.com/apache/logging-log4j2[Log4j GitHub site].
+2. A committer is interested in the issue and decides to work on it.
+3. The reporter or another interested party sponsors one or more of the people listed below to encourage them to
+work on the issue.
+
+== GitHub Sponsorship
+
+The following are Log4j committers who accept sponsorship through GitHub. GitHub sponsorship can be used simply as
+a way to say thank you for the work that has been done or as a way to encourage specific issues to be worked on. In either
+case, while the Apache Logging Services Project thanks you for your support we cannot be responsible for any
+promises and/or contributions made by an individual committer as individual commits must be reviewed and accepted
+by the project team. While the Logging Services team has accepted the individuals listed below as committers to the
+projects, we cannot recommend any particular individual for any specific issue.
+
+==== Committers who accept GitHub Sponsorship
+
+* https://github.com/garydgregory[Gary Gregory]
+* https://github.com/rgoers[Ralph Goers]
\ No newline at end of file
diff --git a/src/site/markdown/manual/cloud.md b/src/site/markdown/manual/cloud.md
new file mode 100644
index 0000000..84fb63f
--- /dev/null
+++ b/src/site/markdown/manual/cloud.md
@@ -0,0 +1,476 @@
+<!-- vim: set syn=markdown : -->
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+# Using Log4j in Cloud Enabled Applications
+
+## The Twelve-Factor Application
+
+The Logging Guidelines for [The Twelve-Factor App](https://12factor.net/logs) state that all logs should be routed
+unbuffered to stdout. Since this is the least common denominator it is guaranteed to work for all applications. However,
+as with any set of general guidelines, choosing the least common denominator approach comes at a cost. Some of the costs
+in Java applications include:
+
+1. Java stack traces are multi-line log messages. The standard docker log driver cannot handle these properly. See
+[Docker Issue #22920](https://github.com/moby/moby/issues/22920) which was closed with the message "Don't Care".
+Solutions for this are to:
+ a. Use a docker log driver that does support multi-line log message,
+ b. Use a logging format that does not produce multi-line messages,
+ c. Log from Log4j directly to a logging forwarder or aggregator and bypass the docker logging driver.
+1. When logging to stdout in Docker, log events pass through Java's standard output handling which is then directed
+to the operating system so that the output can be piped into a file. The overhead of all this is measurably slower
+than just writing directly to a file as can be seen in these benchmark results where logging
+to stdout is 16-20 times slower over repeated runs than logging directly to the file. The results below were obtained by
+running the [Output Benchmark](https://github.com/apache/logging-log4j2/blob/release-2.x/log4j-perf/src/main/java/org/apache/logging/log4j/perf/jmh/OutputBenchmark.java)
+on a 2018 MacBook Pro with a 2.9GHz Intel Core i9 processor and a 1TB SSD. However, these results alone would not be
+enough to argue against writing to the standard output stream as they only amount to about 14-25 microseconds
+per logging call vs 1.5 microseconds when writing to the file.
+ ```
+ Benchmark Mode Cnt Score Error Units
+ OutputBenchmark.console thrpt 20 39291.885 ± 3370.066 ops/s
+ OutputBenchmark.file thrpt 20 654584.309 ± 59399.092 ops/s
+ OutputBenchmark.redirect thrpt 20 70284.576 ± 7452.167 ops/s
+ ```
+1. When performing audit logging using a framework such as log4j-audit guaranteed delivery of the audit events
+is required. Many of the options for writing the output, including writing to the standard output stream, do
+not guarantee delivery. In these cases the event must be delivered to a "forwarder" that acknowledges receipt
+only when it has placed the event in durable storage, such as what [Apache Flume](https://flume.apache.org/)
+or [Apache Kafka](https://kafka.apache.org/) will do.
+
+## Logging Approaches
+
+All the solutions discussed on this page are predicated with the idea that log files cannot permanently
+reside on the file system and that all log events should be routed to one or more log analysis tools that will
+be used for reporting and alerting. There are many ways to forward and collect events to be sent to the
+log analysis tools.
+
+Note that any approach that bypasses Docker's logging drivers requires Log4j's
+[Docker Lookup](lookups.html#DockerLookup) to allow Docker attributes to be injected into the log events.
+
+### Logging to the Standard Output Stream
+
+As discussed above, this is the recommended 12-Factor approach for applications running in a docker container.
+The Log4j team does not recommend this approach if exceptions will be logged by the Java application.
+
+![Stdout](../images/DockerStdout.png "Application Logging to the Standard Output Stream")
+
+### Logging to the Standard Output Stream with the Docker Fluentd Logging Driver
+
+Docker provides alternate [logging drivers](https://docs.docker.com/config/containers/logging/configure/),
+such as [gelf](https://docs.docker.com/config/containers/logging/gelf/) or
+[fluentd](https://docs.docker.com/config/containers/logging/fluentd/), that
+can be used to redirect the standard output stream to a log forwarder or log aggregator.
+
+When routing to a log forwarder it is expected that the forwarder will have the same lifetime as the
+application. If the forwarder should fail the management tools would be expected to also terminate
+other containers dependent on the forwarder.
+
+![Docker Fluentbit](../images/DockerFluentd.png "Logging via StdOut using the Docker Fluentd Logging Driver to Fluent-bit")
+
+As an alternative the logging drivers could be configured to route events directly to a logging aggregator.
+This is generally not a good idea as the logging drivers only allow a single host and port to be configured.
+The docker documentation isn't clear but infers that log events will be dropped when log events cannot be
+delivered so this method should not be used if a highly available solution is required.
+
+![Docker Fluentd](../images/DockerFluentdAggregator.png "Logging via StdOut using the Docker Fluentd Logging Driver to Fluentd")
+
+### Logging to a File
+
+While this is not the recommended 12-Factor approach, it performs very well. However, it requires that the
+application declares a volume where the log files will reside and then configures the log forwarder to tail
+those files. Care must also be taken to automatically manage the disk space used for the logs, which Log4j
+can perform via the "Delete" action on the [RollingFileAppender](appenders.html#RollingFileAppender).
+
+![File](../images/DockerLogFile.png "Logging to a File")
+
+### Sending Directly to a Log Forwarder via TCP
+
+Sending logs directly to a Log Forwarder is simple as it generally just requires that the forwarder's
+host and port be configured on a SocketAppender with an appropriate layout.
+
+![TCP](../images/DockerTCP.png "Application Logging to a Forwarder via TCP")
+
+### Sending Directly to a Log Aggregator via TCP
+
+Similar to sending logs to a forwarder, logs can also be sent to a cluster of aggregators. However,
+setting this up is not as simple since, to be highly available, a cluster of aggregators must be used.
+However, the SocketAppender currently can only be configured with a single host and port. To allow
+for failover if the primary aggregator fails the SocketAppender must be enclosed in a
+[FailoverAppender](appenders.html#FailoverAppender),
+which would also have the secondary aggregator configured. Another option is to have the SocketAppender
+point to a highly available proxy that can forward to the Log Aggregator.
+
+If the log aggregator used is Apache Flume or Apache Kafka (or similar) the Appenders for these support
+being configured with a list of hosts and ports so high availability is not an issue.
+
+![Aggregator](../images/LoggerAggregator.png "Application Logging to an Aggregator via TCP")
+
+## <a name="ELK"></a>Logging using Elasticsearch, Logstash, and Kibana
+
+There are various approaches with different trade-offs for ingesting logs into
+an ELK stack. Here we will briefly cover how one can forward Log4j generated
+events first to Logstash and then to Elasticsearch.
+
+### Log4j Configuration
+
+Log4j provides a multitude of JSON generating layouts. In particular, [JSON
+Template Layout](layouts.html#JSONTemplateLayout) allows full schema
+customization and bundles ELK-specific layouts by default, which makes it a
+great fit for the bill. Using the EcsLayout template as shown below will generate data in Kibana where
+the message displayed exactly matches the message passed to Log4j and most of the event attributes, including
+any exceptions, are present as individual attributes that can be displayed. Note, however that stack traces
+will be formatted without newlines.
+
+ <Socket name="Logstash"
+ host="${sys:logstash.host}"
+ port="12345"
+ protocol="tcp"
+ bufferedIo="true">
+ <JsonTemplateLayout eventTemplateUri="classpath:EcsLayout.json">
+ <EventTemplateAdditionalFields>
+ <EventTemplateAdditionalField key="containerId" value="${docker:containerId:-}"/>
+ <EventTemplateAdditionalField key="application" value="${lower:${spring:spring.application.name:-spring}}"/>
+ <EventTemplateAdditionalField key="kubernetes.serviceAccountName" value="${k8s:accountName:-}"/>
+ <EventTemplateAdditionalField key="kubernetes.containerId" value="${k8s:containerId:-}"/>
+ <EventTemplateAdditionalField key="kubernetes.containerName" value="${k8s:containerName:-}"/>
+ <EventTemplateAdditionalField key="kubernetes.host" value="${k8s:host:-}"/>
+ <EventTemplateAdditionalField key="kubernetes.labels.app" value="${k8s:labels.app:-}"/>
+ <EventTemplateAdditionalField key="kubernetes.labels.pod-template-hash" value="${k8s:labels.podTemplateHash:-}"/>
+ <EventTemplateAdditionalField key="kubernetes.master_url" value="${k8s:masterUrl:-}"/>
+ <EventTemplateAdditionalField key="kubernetes.namespaceId" value="${k8s:namespaceId:-}"/>
+ <EventTemplateAdditionalField key="kubernetes.namespaceName" value="${k8s:namespaceName:-}"/>
+ <EventTemplateAdditionalField key="kubernetes.podID" value="${k8s:podId:-}"/>
+ <EventTemplateAdditionalField key="kubernetes.podIP" value="${k8s:podIp:-}"/>
+ <EventTemplateAdditionalField key="kubernetes.podName" value="${k8s:podName:-}"/>
+ <EventTemplateAdditionalField key="kubernetes.imageId" value="${k8s:imageId:-}"/>
+ <EventTemplateAdditionalField key="kubernetes.imageName" value="${k8s:imageName:-}"/>
+ </EventTemplateAdditionalFields>
+ </JsonTemplateLayout>
+ </Socket>
+
+The JsonTemplateLayout can also be used to generate JSON that matches the GELF specification which can
+format the message attribute using a pattern in accordance with the PatternLayout. For example, the following
+template, named EnhancedGelf.json, can be used to generate GELF-compliant data that can be passed to Logstash.
+With this template the message attribute will include the thread id, level, specific ThreadContext attributes,
+the class name, method name, and line number as well as the message. If an exception is included it will also
+be included with newlines. This format follows very closely what you would see in a typical log file on disk
+using the PatternLayout but has the additional advantage of including the attributes as separate fields that
+can be queried.
+
+ {
+ "version": "1.1",
+ "host": "${hostName}",
+ "short_message": {
+ "$resolver": "message",
+ "stringified": true
+ },
+ "full_message": {
+ "$resolver": "message",
+ "pattern": "[%t] %-5p %X{requestId, sessionId, loginId, userId, ipAddress, corpAcctNumber} %C{1.}.%M:%L - %m",
+ "stringified": true
+ },
+ "timestamp": {
+ "$resolver": "timestamp",
+ "epoch": {
+ "unit": "secs"
+ }
+ },
+ "level": {
+ "$resolver": "level",
+ "field": "severity",
+ "severity": {
+ "field": "code"
+ }
+ },
+ "_logger": {
+ "$resolver": "logger",
+ "field": "name"
+ },
+ "_thread": {
+ "$resolver": "thread",
+ "field": "name"
+ },
+ "_mdc": {
+ "$resolver": "mdc",
+ "flatten": {
+ "prefix": "_"
+ },
+ "stringified": true
+ }
+ }
+
+The logging configuration to use this template would be
+
+ <Socket name="Elastic"
+ host="\${sys:logstash.search.host}"
+ port="12222"
+ protocol="tcp"
+ bufferedIo="true">
+ <JsonTemplateLayout eventTemplateUri="classpath:EnhancedGelf.json" nullEventDelimiterEnabled="true">
+ <EventTemplateAdditionalFields>
+ <EventTemplateAdditionalField key="containerId" value="${docker:containerId:-}"/>
+ <EventTemplateAdditionalField key="application" value="${lower:${spring:spring.application.name:-spring}}"/>
+ <EventTemplateAdditionalField key="kubernetes.serviceAccountName" value="${k8s:accountName:-}"/>
+ <EventTemplateAdditionalField key="kubernetes.containerId" value="${k8s:containerId:-}"/>
+ <EventTemplateAdditionalField key="kubernetes.containerName" value="${k8s:containerName:-}"/>
+ <EventTemplateAdditionalField key="kubernetes.host" value="${k8s:host:-}"/>
+ <EventTemplateAdditionalField key="kubernetes.labels.app" value="${k8s:labels.app:-}"/>
+ <EventTemplateAdditionalField key="kubernetes.labels.pod-template-hash" value="${k8s:labels.podTemplateHash:-}"/>
+ <EventTemplateAdditionalField key="kubernetes.master_url" value="${k8s:masterUrl:-}"/>
+ <EventTemplateAdditionalField key="kubernetes.namespaceId" value="${k8s:namespaceId:-}"/>
+ <EventTemplateAdditionalField key="kubernetes.namespaceName" value="${k8s:namespaceName:-}"/>
+ <EventTemplateAdditionalField key="kubernetes.podID" value="${k8s:podId:-}"/>
+ <EventTemplateAdditionalField key="kubernetes.podIP" value="${k8s:podIp:-}"/>
+ <EventTemplateAdditionalField key="kubernetes.podName" value="${k8s:podName:-}"/>
+ <EventTemplateAdditionalField key="kubernetes.imageId" value="${k8s:imageId:-}"/>
+ <EventTemplateAdditionalField key="kubernetes.imageName" value="${k8s:imageName:-}"/>
+ </EventTemplateAdditionalFields>
+ </JsonTemplateLayout>
+ </Socket>
+The significant difference with this configuration from the first example is that it references the
+custom template and it specifies an event delimiter of a null character ('\0');
+
+Note: The level being passed with the above template does not strictly conform to the GELF spec as the
+Level being passed is the Log4j Level NOT the Level defined in the GELF spec. However, testing has shown
+that Logstash, Elk, and Kibana are pretty tolerant of whatever data is passed to it.
+
+Finally, the GelfLayout can be used to generate GELF compliant output. Unlike the JsonTemplateLayout it
+adheres closely to the GELF spec.
+
+ <Socket name="Elastic" host="${sys:elastic.search.host}" port="12222" protocol="tcp" bufferedIo="true">
+ <GelfLayout includeStackTrace="true" host="${hostName}" includeThreadContext="true" includeNullDelimiter="true"
+ compressionType="OFF">
+ <ThreadContextIncludes>requestId,sessionId,loginId,userId,ipAddress,callingHost</ThreadContextIncludes>
+ <MessagePattern>%d [%t] %-5p %X{requestId, sessionId, loginId, userId, ipAddress} %C{1.}.%M:%L - %m%n</MessagePattern>
+ <KeyValuePair key="containerId" value="${docker:containerId:-}"/>
+ <KeyValuePair key="application" value="${lower:${spring:spring.application.name:-spring}}"/>
+ <KeyValuePair key="kubernetes.serviceAccountName" value="${k8s:accountName:-}"/>
+ <KeyValuePair key="kubernetes.containerId" value="${k8s:containerId:-}"/>
+ <KeyValuePair key="kubernetes.containerName" value="${k8s:containerName:-}"/>
+ <KeyValuePair key="kubernetes.host" value="${k8s:host:-}"/>
+ <KeyValuePair key="kubernetes.labels.app" value="${k8s:labels.app:-}"/>
+ <KeyValuePair key="kubernetes.labels.pod-template-hash" value="${k8s:labels.podTemplateHash:-}"/>
+ <KeyValuePair key="kubernetes.master_url" value="${k8s:masterUrl:-}"/>
+ <KeyValuePair key="kubernetes.namespaceId" value="${k8s:namespaceId:-}"/>
+ <KeyValuePair key="kubernetes.namespaceName" value="${k8s:namespaceName:-}"/>
+ <KeyValuePair key="kubernetes.podID" value="${k8s:podId:-}"/>
+ <KeyValuePair key="kubernetes.podIP" value="${k8s:podIp:-}"/>
+ <KeyValuePair key="kubernetes.podName" value="${k8s:podName:-}"/>
+ <KeyValuePair key="kubernetes.imageId" value="${k8s:imageId:-}"/>
+ <KeyValuePair key="kubernetes.imageName" value="${k8s:imageName:-}"/>
+ </GelfLayout>
+ </Socket>
+
+### Logstash Configuration
+
+We will configure Logstash to listen on TCP port 12345 for payloads of type JSON
+and then forward these to (either console and/or) an Elasticsearch server.
+
+ input {
+ tcp {
+ port => 12345
+ codec => "json"
+ }
+ }
+
+ output {
+
+ # (Un)comment for debugging purposes.
+ # stdout { codec => rubydebug }
+
+ # Modify the hosts value to reflect where elasticsearch is installed.
+ elasticsearch {
+ hosts => ["http://localhost:9200/"]
+ index => "app-%{application}-%{+YYYY.MM.dd}"
+ }
+
+ }
+
+When one of the GELF compliant formats is used Logstash should be configured as
+
+ gelf {
+ host => "localhost"
+ use_tcp => true
+ use_udp => false
+ port => 12222
+ type => "gelf"
+ }
+ }
+
+ filter {
+ # These are GELF/Syslog logging levels as defined in RFC 3164. Map the integer level to its human readable format.
+ translate {
+ field => "[level]"
+ destination => "[levelName]"
+ dictionary => {
+ "0" => "EMERG"
+ "1" => "ALERT"
+ "2" => "CRITICAL"
+ "3" => "ERROR"
+ "4" => "WARN"
+ "5" => "NOTICE"
+ "6" => "INFO"
+ "7" => "DEBUG"
+ }
+ }
+ }
+
+ output {
+ # (Un)comment for debugging purposes
+ # stdout { codec => rubydebug }
+ # Modify the hosts value to reflect where elasticsearch is installed.
+ elasticsearch {
+ hosts => ["http://localhost:9200/"]
+ index => "app-%{application}-%{+YYYY.MM.dd}"
+ }
+ }
+
+### Kibana
+Using the EnhancedGelf template or the GelfLayout the above configurations the message field will contain a fully
+formatted log event just as it would appear in a file Appender. The ThreadContext attributes, custome fields,
+thread name, etc. will all be available as attributes on each log event that can be used for filtering.
+The result will resemble
+![](../images/kibana.png)
+
+## Managing Logging Configuration
+
+Spring Boot provides another least common denominator approach to logging configuration. It will let you set the
+log level for various Loggers within an application which can be dynamically updated via REST endpoints provided
+by Spring. While this works in a lot of cases it does not support any of the more advanced filtering features of
+Log4j. For example, since it cannot add or modify any Filters other than the log level of a logger, changes cannot be made to allow
+all log events for a specific user or customer to temporarily be logged
+(see [DynamicThresholdFilter](filters.html#DynamicThresholdFilter) or
+[ThreadContextMapFilter](filters.html#ThreadContextMapFilter)) or any other kinds of changes to filters.
+Also, in a micro-services, clustered environment it is quite likely that these changes will need to be propagated
+to multiple servers at the same time. Trying to achieve this via REST calls could be difficult.
+
+Since its first release Log4j has supported reconfiguration through a file.
+Beginning with Log4j 2.12.0 Log4j also supports accessing the configuration via HTTP(S) and monitoring the file
+for changes by using the HTTP "If-Modified-Since" header. A patch has also been integrated into Spring Cloud Config
+starting with versions 2.0.3 and 2.1.1 for it to honor the If-Modified-Since header. In addition, the
+log4j-spring-cloud-config project will listen for update events published by Spring Cloud Bus and then verify
+that the configuration file has been modified, so polling via HTTP is not required.
+
+Log4j also supports composite configurations. A distributed application spread across microservices could
+share a common configuration file that could be used to control things like enabling debug logging for a
+specific user.
+
+While the standard Spring Boot REST endpoints to update logging will still work any changes made by those
+REST endpoints will be lost if Log4j reconfigures itself do to changes in the logging configuration file.
+
+Further information regarding integration of the log4j-spring-cloud-config-client can be found at
+[Log4j Spring Cloud Config Client](../log4j-spring-cloud-config/log4j-spring-cloud-config-client/index.html).
+
+## Integration with Spring Boot
+
+Log4j integrates with Spring Boot in 2 ways:
+
+1. A Spring Lookup can be used to access the Spring application configuration from Log4j configuration files.
+2. Log4j will access the Spring configuration when it is trying to resolve log4j system properties.
+
+Both of these require that the log4j-spring-cloud-client jar is included in the application.
+
+## Integration with Docker
+
+Applications within a Docker container that log using a Docker logging driver can include special
+attributes in the formatted log event as described at
+[Customize Log Driver Output](https://docs.docker.com/config/containers/logging/log_tags/). Log4j
+provides similar functionality via the [Docker Lookup](lookups.html#DockerLookup). More information on
+Log4j's Docker support may also be found at [Log4j-Docker](../log4j-docker/index.html).
+
+## Integration with Kubernetes
+
+Applications managed by Kubernetes can bypass the Docker/Kubernetes logging infrastructure and log directly to
+either a sidecar forwarder or a logging aggragator cluster while still including all the kubernetes
+attributes by using the Log4j 2 [Kubernetes Lookup](lookups.html#KubernetesLookup). More information on
+Log4j's Kubernetes support may also be found at [Log4j-Kubernetes](../log4j-kubernetes/index.html).
+
+## Appender Performance
+The numbers in the table below represent how much time in seconds was required for the application to
+call `logger.debug(...)` 100,000 times. These numbers only include the time taken to deliver to the specifically
+noted endpoint and many not include the actual time required before they are available for viewing. All
+measurements were performed on a MacBook Pro with a 2.9GHz Intel Core I9 processor with 6 physical and 12
+logical cores, 32GB of 2400 MHz DDR4 RAM, and 1TB of Apple SSD storage. The VM used by Docker was managed
+by VMWare Fusion and had 4 CPUs and 2 GB of RAM. These number should be used for relative performance comparisons
+as the results on another system may vary considerably.
+
+The sample application used can be found under the log4j-spring-cloud-config/log4j-spring-cloud-config-samples
+directory in the Log4j [source repository](https://github.com/apache/logging-log4j2).
+
+| Test | 1 Thread | 2 Threads | 4 Threads | 8 Threads |
+|------------------------ |---------:|----------:|----------:|----------:|
+|Flume Avro |||||
+|- Batch Size 1 - JSON |49.11 |46.54 |46.70 |44.92 |
+|- Batch Size 1 - RFC5424 |48.30 |45.79 |46.31 |45.50 |
+|- Batch Size 100 - JSON | 6.33 |3.87 |3.57 |3.84 |
+|- Batch Size 100 - RFC5424 | 6.08 |3.69 |3.22 |3.11 |
+|- Batch Size 1000 - JSON | 4.83 |3.20 |3.02 |2.11 |
+|- Batch Size 1000 - RFC5424 | 4.70 |2.40 |2.37 |2.37 |
+|Flume Embedded |||||
+| - RFC5424 |3.58 |2.10 |2.10 |2.70 |
+| - JSON |4.20 |2.49 |3.53 |2.90 |
+|Kafka Local JSON |||||
+| - sendSync true |58.46 |38.55 |19.59 |19.01 |
+| - sendSync false |9.8 |10.8 |12.23 |11.36 |
+|Console|||||
+| - JSON / Kubernetes |3.03 |3.11 |3.04 |2.51 |
+| - JSON |2.80 |2.74 |2.54 |2.35 |
+| - Docker fluentd driver |10.65 |9.92 |10.42 |10.27 |
+|Rolling File|||||
+| - RFC5424 |1.65 |0.94 |1.22 |1.55
+| - JSON |1.90 |0.95 |1.57 |1.94 |
+|TCP - Fluent Bit - JSON |2.34 |2.167 |1.67 |2.50 |
+|Async Logger|||||
+|- TCP - Fluent Bit - JSON|0.90 |0.58 |0.36 |0.48 |
+|- Console - JSON |0.83 |0.57 |0.55 |0.61 |
+|- Flume Avro - 1000 - JSON|0.76 |0.37 |0.45 |0.68 |
+
+Notes:
+
+1. Flume Avro - Buffering is controlled by the batch size. Each send is complete when the remote
+acknowledges the batch was written to its channel. These number seem to indicate Flume Avro could
+benefit from using a pool of RPCClients, at least for a batchSize of 1.
+1. Flume Embedded - This is essentially asynchronous as it writes to an in-memory buffer. It is
+unclear why the performance isn't closer to the AsyncLogger results.
+1. Kafka was run in standalone mode on the same laptop as the application. See sendSync set to true
+requires waiting for an ack from Kafka for each log event.
+1. Console - System.out is redirected to a file by Docker. Testing shows that it would be much
+slower if it was writing to the terminal screen.
+1. Rolling File - Test uses the default buffer size of 8K.
+1. TCP to Fluent Bit - The Socket Appender uses a default buffer size of 8K.
+1. Async Loggers - These all write to a circular buffer and return to the application. The actual
+I/O will take place on a separate thread. If writing the events is performed more slowly than
+events are being created eventually the buffer will fill up and logging will be performed at
+the same pace that log events are written.
+
+## Logging Recommendations
+
+1. Use asynchronous logging unless guaranteed delivery is absolutely required. As
+the performance numbers show, so long as the volume of logging is not high enough to fill up the
+circular buffer the overhead of logging will almost be unnoticeable to the application.
+1. If overall performance is a consideration or you require multiline events such as stack traces
+be processed properly then log via TCP to a companion container that acts as a log forwarder or directly
+to a log aggregator as shown above in [Logging with ELK](#ELK). Use the
+Log4j Docker Lookup to add the container information to each log event.
+1. Whenever guaranteed delivery is required use Flume Avro with a batch size of 1 or another Appender such
+as the Kafka Appender with syncSend set to true that only return control after the downstream agent
+acknowledges receipt of the event. Beware that using an Appender that writes each event individually should
+be kept to a minimum since it is much slower than sending buffered events.
+1. Logging to files within the container is discouraged. Doing so requires that a volume be declared in
+the Docker configuration and that the file be tailed by a log forwarder. However, it performs
+better than logging to the standard output stream. If logging via TCP is not an option and
+proper multiline handling is required then consider this option.
diff --git a/src/site/markdown/manual/compatibility.md b/src/site/markdown/manual/compatibility.md
new file mode 100644
index 0000000..69a1b30
--- /dev/null
+++ b/src/site/markdown/manual/compatibility.md
@@ -0,0 +1,94 @@
+<!-- vim: set syn=markdown : -->
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+# Log4j 2 Compatibility with Log4j 1
+
+## API Compatibility
+
+Log4j 2 provides support for the Log4j 1 logging methods by providing alternate implementations
+of the classes containing those methods. These classes may be found in the log4j-1.2-api jar
+distributed with the project. All calls to perform logging will result in the data passed to the logging methods
+to be forwarded to the Log4j2 API where they can be processed by implementations of the Log4j 2 API.
+
+## Configuration Compatibility
+
+Log4j 2 provides experimental support for Log4j 1 configuration files. Configuration of the Appenders, Layouts
+and Filters that were provided in the Log4j 1 distribution will be redirected to their Log4j 2 counterparts -
+with the exception of the implemented Rewrite Policies. This means that although the while the behavior of these
+components will be similar they may not be exactly the same. For example, the XML generated by the XMLLayout may
+not exactly match the XML generated by the Log4j 1XMLLayout.
+
+In addition, Log4j 2 supports custom Log4j 1 Appenders, Filters, and Layouts with some constraints. Since the
+original Log4j 1 components may not be present in Log4j 2, custom components that extend them will fail.
+
+As support for Log4j 1 is an experimental feature one of the following steps must be taken to enable it:
+
+1. Set the system property "log4j1.compatibility" to a value of "true". Log4j 2 will then add log4j.properties,
+log4j-test.properties, log4j.xml and log4j-test.xml to the configuration files it searches for on the class path.
+1. Set the Log4j 1 system property "log4j.configuration" to the location of the log4j 1 configuration file. The
+files must have a file extension of either ".properties" or ".xml".
+
+## Supported Components
+### Appenders
+
+* AsyncAppender
+* ConsoleAppender
+* DailyRollingFileAppender
+* FileAppender
+* NullAppender
+* RewriteAppender (limited)
+* RollingFileAppender
+* SyslogAppender
+
+## Filters
+
+* DenyAllFilter
+* LevelMatchFilter
+* LevelRangeFilter
+* StringMatchFilter
+
+## Layouts
+
+* HtmlLayout
+* PatternLayout
+* SimpleLayout
+* TTCCLayout
+* XmlLayout
+
+## Rewrite Policies
+
+* MapRewritePolicy
+* PropertyRewritePolicy
+
+## Unsupported or Unimplemented Components
+### Appenders
+
+* JDBCAppender (cannot be mapped to Log4j 2's JdbcAppender)
+* JMSAppender
+* SMTPAppender
+* SocketAppender (Requires the use of the SerializedLayout which is a security risk)
+* SocketHubAppender (Requires the use of the SerializedLayout which is a securiy risk)
+* TelnetAppender (Security risk)
+
+## Rewrite Policies
+
+* ReflectionRewritePolicy
+* Custom rewrite policies since LoggingEvent is currently a no-op.
+
+### Renderers
+Log4j 2 currently will ignore renderers.
\ No newline at end of file
diff --git a/src/site/resources/css/bootstrap.css b/src/site/resources/css/bootstrap.css
deleted file mode 100644
index 3ef47e1..0000000
--- a/src/site/resources/css/bootstrap.css
+++ /dev/null
@@ -1,5893 +0,0 @@
-/*!
- * Bootstrap v2.2.1
- *
- * Copyright 2012 Twitter, Inc
- * Licensed under the Apache License v2.0
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Designed and built with all the love in the world @twitter by @mdo and @fat.
- */
-
-article,
-aside,
-details,
-figcaption,
-figure,
-footer,
-header,
-hgroup,
-nav,
-section {
- display: block;
-}
-
-audio,
-canvas,
-video {
- display: inline-block;
- *display: inline;
- *zoom: 1;
-}
-
-audio:not([controls]) {
- display: none;
-}
-
-html {
- font-size: 100%;
- -webkit-text-size-adjust: 100%;
- -ms-text-size-adjust: 100%;
-}
-
-a:focus {
- outline: thin dotted #333;
- outline: 5px auto -webkit-focus-ring-color;
- outline-offset: -2px;
-}
-
-a:hover,
-a:active {
- outline: 0;
-}
-
-sub,
-sup {
- position: relative;
- font-size: 75%;
- line-height: 0;
- vertical-align: baseline;
-}
-
-sup {
- top: -0.5em;
-}
-
-sub {
- bottom: -0.25em;
-}
-
-img {
- width: auto\9;
- height: auto;
- max-width: 100%;
- vertical-align: middle;
- border: 0;
- -ms-interpolation-mode: bicubic;
-}
-
-#map_canvas img,
-.google-maps img {
- max-width: none;
-}
-
-button,
-input,
-select,
-textarea {
- margin: 0;
- font-size: 100%;
- vertical-align: middle;
-}
-
-button,
-input {
- *overflow: visible;
- line-height: normal;
-}
-
-button::-moz-focus-inner,
-input::-moz-focus-inner {
- padding: 0;
- border: 0;
-}
-
-button,
-html input[type="button"],
-input[type="reset"],
-input[type="submit"] {
- cursor: pointer;
- -webkit-appearance: button;
-}
-
-input[type="search"] {
- -webkit-box-sizing: content-box;
- -moz-box-sizing: content-box;
- box-sizing: content-box;
- -webkit-appearance: textfield;
-}
-
-input[type="search"]::-webkit-search-decoration,
-input[type="search"]::-webkit-search-cancel-button {
- -webkit-appearance: none;
-}
-
-textarea {
- overflow: auto;
- vertical-align: top;
-}
-
-.clearfix {
- *zoom: 1;
-}
-
-.clearfix:before,
-.clearfix:after {
- display: table;
- line-height: 0;
- content: "";
-}
-
-.clearfix:after {
- clear: both;
-}
-
-.hide-text {
- font: 0/0 a;
- color: transparent;
- text-shadow: none;
- background-color: transparent;
- border: 0;
-}
-
-.input-block-level {
- display: block;
- width: 100%;
- min-height: 30px;
- -webkit-box-sizing: border-box;
- -moz-box-sizing: border-box;
- box-sizing: border-box;
-}
-
-body {
- margin: 0;
- font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
- font-size: 14px;
- line-height: 20px;
- color: #333333;
- background-color: #ffffff;
-}
-
-a {
- color: #0088cc;
- text-decoration: none;
-}
-
-a:hover {
- color: #005580;
- text-decoration: underline;
-}
-
-.img-rounded {
- -webkit-border-radius: 6px;
- -moz-border-radius: 6px;
- border-radius: 6px;
-}
-
-.img-polaroid {
- padding: 4px;
- background-color: #fff;
- border: 1px solid #ccc;
- border: 1px solid rgba(0, 0, 0, 0.2);
- -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
- -moz-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
- box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
-}
-
-.img-circle {
- -webkit-border-radius: 500px;
- -moz-border-radius: 500px;
- border-radius: 500px;
-}
-
-.row {
- margin-left: -20px;
- *zoom: 1;
-}
-
-.row:before,
-.row:after {
- display: table;
- line-height: 0;
- content: "";
-}
-
-.row:after {
- clear: both;
-}
-
-[class*="span"] {
- float: left;
- min-height: 1px;
- margin-left: 20px;
-}
-
-.container,
-.navbar-static-top .container,
-.navbar-fixed-top .container,
-.navbar-fixed-bottom .container {
- width: 940px;
-}
-
-.span12 {
- width: 940px;
-}
-
-.span11 {
- width: 860px;
-}
-
-.span10 {
- width: 780px;
-}
-
-.span9 {
- width: 700px;
-}
-
-.span8 {
- width: 620px;
-}
-
-.span7 {
- width: 540px;
-}
-
-.span6 {
- width: 460px;
-}
-
-.span5 {
- width: 380px;
-}
-
-.span4 {
- width: 300px;
-}
-
-.span3 {
- width: 220px;
-}
-
-.span2 {
- width: 140px;
-}
-
-.span1 {
- width: 60px;
-}
-
-.offset12 {
- margin-left: 980px;
-}
-
-.offset11 {
- margin-left: 900px;
-}
-
-.offset10 {
- margin-left: 820px;
-}
-
-.offset9 {
- margin-left: 740px;
-}
-
-.offset8 {
- margin-left: 660px;
-}
-
-.offset7 {
- margin-left: 580px;
-}
-
-.offset6 {
- margin-left: 500px;
-}
-
-.offset5 {
- margin-left: 420px;
-}
-
-.offset4 {
- margin-left: 340px;
-}
-
-.offset3 {
- margin-left: 260px;
-}
-
-.offset2 {
- margin-left: 180px;
-}
-
-.offset1 {
- margin-left: 100px;
-}
-
-.row-fluid {
- width: 100%;
- *zoom: 1;
-}
-
-.row-fluid:before,
-.row-fluid:after {
- display: table;
- line-height: 0;
- content: "";
-}
-
-.row-fluid:after {
- clear: both;
-}
-
-.row-fluid [class*="span"] {
- display: block;
- float: left;
- width: 100%;
- min-height: 30px;
- margin-left: 2.127659574468085%;
- *margin-left: 2.074468085106383%;
- -webkit-box-sizing: border-box;
- -moz-box-sizing: border-box;
- box-sizing: border-box;
-}
-
-.row-fluid [class*="span"]:first-child {
- margin-left: 0;
-}
-
-.row-fluid .controls-row [class*="span"] + [class*="span"] {
- margin-left: 2.127659574468085%;
-}
-
-.row-fluid .span12 {
- width: 100%;
- *width: 99.94680851063829%;
-}
-
-.row-fluid .span11 {
- width: 91.48936170212765%;
- *width: 91.43617021276594%;
-}
-
-.row-fluid .span10 {
- width: 82.97872340425532%;
- *width: 82.92553191489361%;
-}
-
-.row-fluid .span9 {
- width: 74.46808510638297%;
- *width: 74.41489361702126%;
-}
-
-.row-fluid .span8 {
- width: 65.95744680851064%;
- *width: 65.90425531914893%;
-}
-
-.row-fluid .span7 {
- width: 57.44680851063829%;
- *width: 57.39361702127659%;
-}
-
-.row-fluid .span6 {
- width: 48.93617021276595%;
- *width: 48.88297872340425%;
-}
-
-.row-fluid .span5 {
- width: 40.42553191489362%;
- *width: 40.37234042553192%;
-}
-
-.row-fluid .span4 {
- width: 31.914893617021278%;
- *width: 31.861702127659576%;
-}
-
-.row-fluid .span3 {
- width: 23.404255319148934%;
- *width: 23.351063829787233%;
-}
-
-.row-fluid .span2 {
- width: 14.893617021276595%;
- *width: 14.840425531914894%;
-}
-
-.row-fluid .span1 {
- width: 6.382978723404255%;
- *width: 6.329787234042553%;
-}
-
-.row-fluid .offset12 {
- margin-left: 104.25531914893617%;
- *margin-left: 104.14893617021275%;
-}
-
-.row-fluid .offset12:first-child {
- margin-left: 102.12765957446808%;
- *margin-left: 102.02127659574467%;
-}
-
-.row-fluid .offset11 {
- margin-left: 95.74468085106382%;
- *margin-left: 95.6382978723404%;
-}
-
-.row-fluid .offset11:first-child {
- margin-left: 93.61702127659574%;
- *margin-left: 93.51063829787232%;
-}
-
-.row-fluid .offset10 {
- margin-left: 87.23404255319149%;
- *margin-left: 87.12765957446807%;
-}
-
-.row-fluid .offset10:first-child {
- margin-left: 85.1063829787234%;
- *margin-left: 84.99999999999999%;
-}
-
-.row-fluid .offset9 {
- margin-left: 78.72340425531914%;
- *margin-left: 78.61702127659572%;
-}
-
-.row-fluid .offset9:first-child {
- margin-left: 76.59574468085106%;
- *margin-left: 76.48936170212764%;
-}
-
-.row-fluid .offset8 {
- margin-left: 70.2127659574468%;
- *margin-left: 70.10638297872339%;
-}
-
-.row-fluid .offset8:first-child {
- margin-left: 68.08510638297872%;
- *margin-left: 67.9787234042553%;
-}
-
-.row-fluid .offset7 {
- margin-left: 61.70212765957446%;
- *margin-left: 61.59574468085106%;
-}
-
-.row-fluid .offset7:first-child {
- margin-left: 59.574468085106375%;
- *margin-left: 59.46808510638297%;
-}
-
-.row-fluid .offset6 {
- margin-left: 53.191489361702125%;
- *margin-left: 53.085106382978715%;
-}
-
-.row-fluid .offset6:first-child {
- margin-left: 51.063829787234035%;
- *margin-left: 50.95744680851063%;
-}
-
-.row-fluid .offset5 {
- margin-left: 44.68085106382979%;
- *margin-left: 44.57446808510638%;
-}
-
-.row-fluid .offset5:first-child {
- margin-left: 42.5531914893617%;
- *margin-left: 42.4468085106383%;
-}
-
-.row-fluid .offset4 {
- margin-left: 36.170212765957444%;
- *margin-left: 36.06382978723405%;
-}
-
-.row-fluid .offset4:first-child {
- margin-left: 34.04255319148936%;
- *margin-left: 33.93617021276596%;
-}
-
-.row-fluid .offset3 {
- margin-left: 27.659574468085104%;
- *margin-left: 27.5531914893617%;
-}
-
-.row-fluid .offset3:first-child {
- margin-left: 25.53191489361702%;
- *margin-left: 25.425531914893618%;
-}
-
-.row-fluid .offset2 {
- margin-left: 19.148936170212764%;
- *margin-left: 19.04255319148936%;
-}
-
-.row-fluid .offset2:first-child {
- margin-left: 17.02127659574468%;
- *margin-left: 16.914893617021278%;
-}
-
-.row-fluid .offset1 {
- margin-left: 10.638297872340425%;
- *margin-left: 10.53191489361702%;
-}
-
-.row-fluid .offset1:first-child {
- margin-left: 8.51063829787234%;
- *margin-left: 8.404255319148938%;
-}
-
-[class*="span"].hide,
-.row-fluid [class*="span"].hide {
- display: none;
-}
-
-[class*="span"].pull-right,
-.row-fluid [class*="span"].pull-right {
- float: right;
-}
-
-.container {
- margin-right: auto;
- margin-left: auto;
- *zoom: 1;
-}
-
-.container:before,
-.container:after {
- display: table;
- line-height: 0;
- content: "";
-}
-
-.container:after {
- clear: both;
-}
-
-.container-fluid {
- padding-right: 20px;
- padding-left: 20px;
- *zoom: 1;
-}
-
-.container-fluid:before,
-.container-fluid:after {
- display: table;
- line-height: 0;
- content: "";
-}
-
-.container-fluid:after {
- clear: both;
-}
-
-p {
- margin: 0 0 10px;
-}
-
-.lead {
- margin-bottom: 20px;
- font-size: 21px;
- font-weight: 200;
- line-height: 30px;
-}
-
-small {
- font-size: 85%;
-}
-
-strong {
- font-weight: bold;
-}
-
-em {
- font-style: italic;
-}
-
-cite {
- font-style: normal;
-}
-
-.muted {
- color: #999999;
-}
-
-.text-warning {
- color: #c09853;
-}
-
-a.text-warning:hover {
- color: #a47e3c;
-}
-
-.text-error {
- color: #b94a48;
-}
-
-a.text-error:hover {
- color: #953b39;
-}
-
-.text-info {
- color: #3a87ad;
-}
-
-a.text-info:hover {
- color: #2d6987;
-}
-
-.text-success {
- color: #468847;
-}
-
-a.text-success:hover {
- color: #356635;
-}
-
-h1,
-h2,
-h3,
-h4,
-h5,
-h6 {
- margin: 10px 0;
- font-family: inherit;
- font-weight: bold;
- line-height: 20px;
- color: inherit;
- text-rendering: optimizelegibility;
-}
-
-h1 small,
-h2 small,
-h3 small,
-h4 small,
-h5 small,
-h6 small {
- font-weight: normal;
- line-height: 1;
- color: #999999;
-}
-
-h1,
-h2,
-h3 {
- line-height: 40px;
-}
-
-h1 {
- font-size: 38.5px;
-}
-
-h2 {
- font-size: 31.5px;
-}
-
-h3 {
- font-size: 24.5px;
-}
-
-h4 {
- font-size: 17.5px;
-}
-
-h5 {
- font-size: 14px;
-}
-
-h6 {
- font-size: 11.9px;
-}
-
-h1 small {
- font-size: 24.5px;
-}
-
-h2 small {
- font-size: 17.5px;
-}
-
-h3 small {
- font-size: 14px;
-}
-
-h4 small {
- font-size: 14px;
-}
-
-.page-header {
- padding-bottom: 9px;
- margin: 20px 0 30px;
- border-bottom: 1px solid #eeeeee;
-}
-
-ul,
-ol {
- padding: 0;
- margin: 0 0 10px 25px;
-}
-
-ul ul,
-ul ol,
-ol ol,
-ol ul {
- margin-bottom: 0;
-}
-
-li {
- line-height: 20px;
-}
-
-ul.unstyled,
-ol.unstyled {
- margin-left: 0;
- list-style: none;
-}
-
-dl {
- margin-bottom: 20px;
-}
-
-dt,
-dd {
- line-height: 20px;
-}
-
-dt {
- font-weight: bold;
-}
-
-dd {
- margin-left: 10px;
-}
-
-.dl-horizontal {
- *zoom: 1;
-}
-
-.dl-horizontal:before,
-.dl-horizontal:after {
- display: table;
- line-height: 0;
- content: "";
-}
-
-.dl-horizontal:after {
- clear: both;
-}
-
-.dl-horizontal dt {
- float: left;
- width: 160px;
- overflow: hidden;
- clear: left;
- text-align: right;
- text-overflow: ellipsis;
- white-space: nowrap;
-}
-
-.dl-horizontal dd {
- margin-left: 180px;
-}
-
-hr {
- margin: 20px 0;
- border: 0;
- border-top: 1px solid #eeeeee;
- border-bottom: 1px solid #ffffff;
-}
-
-abbr[title],
-abbr[data-original-title] {
- cursor: help;
- border-bottom: 1px dotted #999999;
-}
-
-abbr.initialism {
- font-size: 90%;
- text-transform: uppercase;
-}
-
-blockquote {
- padding: 0 0 0 15px;
- margin: 0 0 20px;
- border-left: 5px solid #eeeeee;
-}
-
-blockquote p {
- margin-bottom: 0;
- font-size: 16px;
- font-weight: 300;
- line-height: 25px;
-}
-
-blockquote small {
- display: block;
- line-height: 20px;
- color: #999999;
-}
-
-blockquote small:before {
- content: '\2014 \00A0';
-}
-
-blockquote.pull-right {
- float: right;
- padding-right: 15px;
- padding-left: 0;
- border-right: 5px solid #eeeeee;
- border-left: 0;
-}
-
-blockquote.pull-right p,
-blockquote.pull-right small {
- text-align: right;
-}
-
-blockquote.pull-right small:before {
- content: '';
-}
-
-blockquote.pull-right small:after {
- content: '\00A0 \2014';
-}
-
-q:before,
-q:after,
-blockquote:before,
-blockquote:after {
- content: "";
-}
-
-address {
- display: block;
- margin-bottom: 20px;
- font-style: normal;
- line-height: 20px;
-}
-
-code,
-pre {
- padding: 0 3px 2px;
- font-family: Monaco, Menlo, Consolas, "Courier New", monospace;
- font-size: 12px;
- color: #333333;
- -webkit-border-radius: 3px;
- -moz-border-radius: 3px;
- border-radius: 3px;
-}
-
-code {
- padding: 2px 4px;
- color: #d14; /* FIXME: red? really? */
- background-color: #f7f7f9;
- border: 1px solid #e1e1e8;
-}
-
-pre {
- display: block;
- padding: 9.5px;
- margin: 0 0 10px;
- font-size: 13px;
- line-height: 20px;
- word-break: break-all;
- word-wrap: break-word;
- white-space: pre;
- white-space: pre-wrap;
- background-color: #f5f5f5;
- border: 1px solid #ccc;
- border: 1px solid rgba(0, 0, 0, 0.15);
- -webkit-border-radius: 4px;
- -moz-border-radius: 4px;
- border-radius: 4px;
-}
-
-pre.prettyprint {
- margin-bottom: 20px;
-}
-
-pre code {
- padding: 0;
- color: inherit;
- background-color: transparent;
- border: 0;
-}
-
-.pre-scrollable {
- max-height: 340px;
- overflow-y: scroll;
-}
-
-form {
- margin: 0 0 20px;
-}
-
-fieldset {
- padding: 0;
- margin: 0;
- border: 0;
-}
-
-legend {
- display: block;
- width: 100%;
- padding: 0;
- margin-bottom: 20px;
- font-size: 21px;
- line-height: 40px;
- color: #333333;
- border: 0;
- border-bottom: 1px solid #e5e5e5;
-}
-
-legend small {
- font-size: 15px;
- color: #999999;
-}
-
-label,
-input,
-button,
-select,
-textarea {
- font-size: 14px;
- font-weight: normal;
- line-height: 20px;
-}
-
-input,
-button,
-select,
-textarea {
- font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
-}
-
-label {
- display: block;
- margin-bottom: 5px;
-}
-
-select,
-textarea,
-input[type="text"],
-input[type="password"],
-input[type="datetime"],
-input[type="datetime-local"],
-input[type="date"],
-input[type="month"],
-input[type="time"],
-input[type="week"],
-input[type="number"],
-input[type="email"],
-input[type="url"],
-input[type="search"],
-input[type="tel"],
-input[type="color"],
-.uneditable-input {
- display: inline-block;
- height: 20px;
- padding: 4px 6px;
- margin-bottom: 10px;
- font-size: 14px;
- line-height: 20px;
- color: #555555;
- vertical-align: middle;
- -webkit-border-radius: 4px;
- -moz-border-radius: 4px;
- border-radius: 4px;
-}
-
-input,
-textarea,
-.uneditable-input {
- width: 206px;
-}
-
-textarea {
- height: auto;
-}
-
-textarea,
-input[type="text"],
-input[type="password"],
-input[type="datetime"],
-input[type="datetime-local"],
-input[type="date"],
-input[type="month"],
-input[type="time"],
-input[type="week"],
-input[type="number"],
-input[type="email"],
-input[type="url"],
-input[type="search"],
-input[type="tel"],
-input[type="color"],
-.uneditable-input {
- background-color: #ffffff;
- border: 1px solid #cccccc;
- -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
- -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
- box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
- -webkit-transition: border linear 0.2s, box-shadow linear 0.2s;
- -moz-transition: border linear 0.2s, box-shadow linear 0.2s;
- -o-transition: border linear 0.2s, box-shadow linear 0.2s;
- transition: border linear 0.2s, box-shadow linear 0.2s;
-}
-
-textarea:focus,
-input[type="text"]:focus,
-input[type="password"]:focus,
-input[type="datetime"]:focus,
-input[type="datetime-local"]:focus,
-input[type="date"]:focus,
-input[type="month"]:focus,
-input[type="time"]:focus,
-input[type="week"]:focus,
-input[type="number"]:focus,
-input[type="email"]:focus,
-input[type="url"]:focus,
-input[type="search"]:focus,
-input[type="tel"]:focus,
-input[type="color"]:focus,
-.uneditable-input:focus {
- border-color: rgba(82, 168, 236, 0.8);
- outline: 0;
- outline: thin dotted \9;
- /* IE6-9 */
-
- -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
- -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
- box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(82, 168, 236, 0.6);
-}
-
-input[type="radio"],
-input[type="checkbox"] {
- margin: 4px 0 0;
- margin-top: 1px \9;
- *margin-top: 0;
- line-height: normal;
- cursor: pointer;
-}
-
-input[type="file"],
-input[type="image"],
-input[type="submit"],
-input[type="reset"],
-input[type="button"],
-input[type="radio"],
-input[type="checkbox"] {
- width: auto;
-}
-
-select,
-input[type="file"] {
- height: 30px;
- /* In IE7, the height of the select element cannot be changed by height, only font-size */
-
- *margin-top: 4px;
- /* For IE7, add top margin to align select with labels */
-
- line-height: 30px;
-}
-
-select {
- width: 220px;
- background-color: #ffffff;
- border: 1px solid #cccccc;
-}
-
-select[multiple],
-select[size] {
- height: auto;
-}
-
-select:focus,
-input[type="file"]:focus,
-input[type="radio"]:focus,
-input[type="checkbox"]:focus {
- outline: thin dotted #333;
- outline: 5px auto -webkit-focus-ring-color;
- outline-offset: -2px;
-}
-
-.uneditable-input,
-.uneditable-textarea {
- color: #999999;
- cursor: not-allowed;
- background-color: #fcfcfc;
- border-color: #cccccc;
- -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.025);
- -moz-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.025);
- box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.025);
-}
-
-.uneditable-input {
- overflow: hidden;
- white-space: nowrap;
-}
-
-.uneditable-textarea {
- width: auto;
- height: auto;
-}
-
-input:-moz-placeholder,
-textarea:-moz-placeholder {
- color: #999999;
-}
-
-input:-ms-input-placeholder,
-textarea:-ms-input-placeholder {
- color: #999999;
-}
-
-input::-webkit-input-placeholder,
-textarea::-webkit-input-placeholder {
- color: #999999;
-}
-
-.radio,
-.checkbox {
- min-height: 20px;
- padding-left: 20px;
-}
-
-.radio input[type="radio"],
-.checkbox input[type="checkbox"] {
- float: left;
- margin-left: -20px;
-}
-
-.controls > .radio:first-child,
-.controls > .checkbox:first-child {
- padding-top: 5px;
-}
-
-.radio.inline,
-.checkbox.inline {
- display: inline-block;
- padding-top: 5px;
- margin-bottom: 0;
- vertical-align: middle;
-}
-
-.radio.inline + .radio.inline,
-.checkbox.inline + .checkbox.inline {
- margin-left: 10px;
-}
-
-.input-mini {
- width: 60px;
-}
-
-.input-small {
- width: 90px;
-}
-
-.input-medium {
- width: 150px;
-}
-
-.input-large {
- width: 210px;
-}
-
-.input-xlarge {
- width: 270px;
-}
-
-.input-xxlarge {
- width: 530px;
-}
-
-input[class*="span"],
-select[class*="span"],
-textarea[class*="span"],
-.uneditable-input[class*="span"],
-.row-fluid input[class*="span"],
-.row-fluid select[class*="span"],
-.row-fluid textarea[class*="span"],
-.row-fluid .uneditable-input[class*="span"] {
- float: none;
- margin-left: 0;
-}
-
-.input-append input[class*="span"],
-.input-append .uneditable-input[class*="span"],
-.input-prepend input[class*="span"],
-.input-prepend .uneditable-input[class*="span"],
-.row-fluid input[class*="span"],
-.row-fluid select[class*="span"],
-.row-fluid textarea[class*="span"],
-.row-fluid .uneditable-input[class*="span"],
-.row-fluid .input-prepend [class*="span"],
-.row-fluid .input-append [class*="span"] {
- display: inline-block;
-}
-
-input,
-textarea,
-.uneditable-input {
- margin-left: 0;
-}
-
-.controls-row [class*="span"] + [class*="span"] {
- margin-left: 20px;
-}
-
-input.span12,
-textarea.span12,
-.uneditable-input.span12 {
- width: 926px;
-}
-
-input.span11,
-textarea.span11,
-.uneditable-input.span11 {
- width: 846px;
-}
-
-input.span10,
-textarea.span10,
-.uneditable-input.span10 {
- width: 766px;
-}
-
-input.span9,
-textarea.span9,
-.uneditable-input.span9 {
- width: 686px;
-}
-
-input.span8,
-textarea.span8,
-.uneditable-input.span8 {
- width: 606px;
-}
-
-input.span7,
-textarea.span7,
-.uneditable-input.span7 {
- width: 526px;
-}
-
-input.span6,
-textarea.span6,
-.uneditable-input.span6 {
- width: 446px;
-}
-
-input.span5,
-textarea.span5,
-.uneditable-input.span5 {
- width: 366px;
-}
-
-input.span4,
-textarea.span4,
-.uneditable-input.span4 {
- width: 286px;
-}
-
-input.span3,
-textarea.span3,
-.uneditable-input.span3 {
- width: 206px;
-}
-
-input.span2,
-textarea.span2,
-.uneditable-input.span2 {
- width: 126px;
-}
-
-input.span1,
-textarea.span1,
-.uneditable-input.span1 {
- width: 46px;
-}
-
-.controls-row {
- *zoom: 1;
-}
-
-.controls-row:before,
-.controls-row:after {
- display: table;
- line-height: 0;
- content: "";
-}
-
-.controls-row:after {
- clear: both;
-}
-
-.controls-row [class*="span"],
-.row-fluid .controls-row [class*="span"] {
- float: left;
-}
-
-.controls-row .checkbox[class*="span"],
-.controls-row .radio[class*="span"] {
- padding-top: 5px;
-}
-
-input[disabled],
-select[disabled],
-textarea[disabled],
-input[readonly],
-select[readonly],
-textarea[readonly] {
- cursor: not-allowed;
- background-color: #eeeeee;
-}
-
-input[type="radio"][disabled],
-input[type="checkbox"][disabled],
-input[type="radio"][readonly],
-input[type="checkbox"][readonly] {
- background-color: transparent;
-}
-
-.control-group.warning > label,
-.control-group.warning .help-block,
-.control-group.warning .help-inline {
- color: #c09853;
-}
-
-.control-group.warning .checkbox,
-.control-group.warning .radio,
-.control-group.warning input,
-.control-group.warning select,
-.control-group.warning textarea {
- color: #c09853;
-}
-
-.control-group.warning input,
-.control-group.warning select,
-.control-group.warning textarea {
- border-color: #c09853;
- -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
- -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
- box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
-}
-
-.control-group.warning input:focus,
-.control-group.warning select:focus,
-.control-group.warning textarea:focus {
- border-color: #a47e3c;
- -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #dbc59e;
- -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #dbc59e;
- box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #dbc59e;
-}
-
-.control-group.warning .input-prepend .add-on,
-.control-group.warning .input-append .add-on {
- color: #c09853;
- background-color: #fcf8e3;
- border-color: #c09853;
-}
-
-.control-group.error > label,
-.control-group.error .help-block,
-.control-group.error .help-inline {
- color: #b94a48;
-}
-
-.control-group.error .checkbox,
-.control-group.error .radio,
-.control-group.error input,
-.control-group.error select,
-.control-group.error textarea {
- color: #b94a48;
-}
-
-.control-group.error input,
-.control-group.error select,
-.control-group.error textarea {
- border-color: #b94a48;
- -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
- -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
- box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
-}
-
-.control-group.error input:focus,
-.control-group.error select:focus,
-.control-group.error textarea:focus {
- border-color: #953b39;
- -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d59392;
- -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d59392;
- box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d59392;
-}
-
-.control-group.error .input-prepend .add-on,
-.control-group.error .input-append .add-on {
- color: #b94a48;
- background-color: #f2dede;
- border-color: #b94a48;
-}
-
-.control-group.success > label,
-.control-group.success .help-block,
-.control-group.success .help-inline {
- color: #468847;
-}
-
-.control-group.success .checkbox,
-.control-group.success .radio,
-.control-group.success input,
-.control-group.success select,
-.control-group.success textarea {
- color: #468847;
-}
-
-.control-group.success input,
-.control-group.success select,
-.control-group.success textarea {
- border-color: #468847;
- -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
- -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
- box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
-}
-
-.control-group.success input:focus,
-.control-group.success select:focus,
-.control-group.success textarea:focus {
- border-color: #356635;
- -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7aba7b;
- -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7aba7b;
- box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7aba7b;
-}
-
-.control-group.success .input-prepend .add-on,
-.control-group.success .input-append .add-on {
- color: #468847;
- background-color: #dff0d8;
- border-color: #468847;
-}
-
-.control-group.info > label,
-.control-group.info .help-block,
-.control-group.info .help-inline {
- color: #3a87ad;
-}
-
-.control-group.info .checkbox,
-.control-group.info .radio,
-.control-group.info input,
-.control-group.info select,
-.control-group.info textarea {
- color: #3a87ad;
-}
-
-.control-group.info input,
-.control-group.info select,
-.control-group.info textarea {
- border-color: #3a87ad;
- -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
- -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
- box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
-}
-
-.control-group.info input:focus,
-.control-group.info select:focus,
-.control-group.info textarea:focus {
- border-color: #2d6987;
- -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7ab5d3;
- -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7ab5d3;
- box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7ab5d3;
-}
-
-.control-group.info .input-prepend .add-on,
-.control-group.info .input-append .add-on {
- color: #3a87ad;
- background-color: #d9edf7;
- border-color: #3a87ad;
-}
-
-input:focus:required:invalid,
-textarea:focus:required:invalid,
-select:focus:required:invalid {
- color: #b94a48;
- border-color: #ee5f5b;
-}
-
-input:focus:required:invalid:focus,
-textarea:focus:required:invalid:focus,
-select:focus:required:invalid:focus {
- border-color: #e9322d;
- -webkit-box-shadow: 0 0 6px #f8b9b7;
- -moz-box-shadow: 0 0 6px #f8b9b7;
- box-shadow: 0 0 6px #f8b9b7;
-}
-
-.form-actions {
- padding: 19px 20px 20px;
- margin-top: 20px;
- margin-bottom: 20px;
- background-color: #f5f5f5;
- border-top: 1px solid #e5e5e5;
- *zoom: 1;
-}
-
-.form-actions:before,
-.form-actions:after {
- display: table;
- line-height: 0;
- content: "";
-}
-
-.form-actions:after {
- clear: both;
-}
-
-.help-block,
-.help-inline {
- color: #595959;
-}
-
-.help-block {
- display: block;
- margin-bottom: 10px;
-}
-
-.help-inline {
- display: inline-block;
- *display: inline;
- padding-left: 5px;
- vertical-align: middle;
- *zoom: 1;
-}
-
-.input-append,
-.input-prepend {
- margin-bottom: 5px;
- font-size: 0;
- white-space: nowrap;
-}
-
-.input-append input,
-.input-prepend input,
-.input-append select,
-.input-prepend select,
-.input-append .uneditable-input,
-.input-prepend .uneditable-input,
-.input-append .dropdown-menu,
-.input-prepend .dropdown-menu {
- font-size: 14px;
-}
-
-.input-append input,
-.input-prepend input,
-.input-append select,
-.input-prepend select,
-.input-append .uneditable-input,
-.input-prepend .uneditable-input {
- position: relative;
- margin-bottom: 0;
- *margin-left: 0;
- vertical-align: top;
- -webkit-border-radius: 0 4px 4px 0;
- -moz-border-radius: 0 4px 4px 0;
- border-radius: 0 4px 4px 0;
-}
-
-.input-append input:focus,
-.input-prepend input:focus,
-.input-append select:focus,
-.input-prepend select:focus,
-.input-append .uneditable-input:focus,
-.input-prepend .uneditable-input:focus {
- z-index: 2;
-}
-
-.input-append .add-on,
-.input-prepend .add-on {
- display: inline-block;
- width: auto;
- height: 20px;
- min-width: 16px;
- padding: 4px 5px;
- font-size: 14px;
- font-weight: normal;
- line-height: 20px;
- text-align: center;
- text-shadow: 0 1px 0 #ffffff;
- background-color: #eeeeee;
- border: 1px solid #ccc;
-}
-
-.input-append .add-on,
-.input-prepend .add-on,
-.input-append .btn,
-.input-prepend .btn {
- vertical-align: top;
- -webkit-border-radius: 0;
- -moz-border-radius: 0;
- border-radius: 0;
-}
-
-.input-append .active,
-.input-prepend .active {
- background-color: #a9dba9;
- border-color: #46a546;
-}
-
-.input-prepend .add-on,
-.input-prepend .btn {
- margin-right: -1px;
-}
-
-.input-prepend .add-on:first-child,
-.input-prepend .btn:first-child {
- -webkit-border-radius: 4px 0 0 4px;
- -moz-border-radius: 4px 0 0 4px;
- border-radius: 4px 0 0 4px;
-}
-
-.input-append input,
-.input-append select,
-.input-append .uneditable-input {
- -webkit-border-radius: 4px 0 0 4px;
- -moz-border-radius: 4px 0 0 4px;
- border-radius: 4px 0 0 4px;
-}
-
-.input-append input + .btn-group .btn,
-.input-append select + .btn-group .btn,
-.input-append .uneditable-input + .btn-group .btn {
- -webkit-border-radius: 0 4px 4px 0;
- -moz-border-radius: 0 4px 4px 0;
- border-radius: 0 4px 4px 0;
-}
-
-.input-append .add-on,
-.input-append .btn,
-.input-append .btn-group {
- margin-left: -1px;
-}
-
-.input-append .add-on:last-child,
-.input-append .btn:last-child {
- -webkit-border-radius: 0 4px 4px 0;
- -moz-border-radius: 0 4px 4px 0;
- border-radius: 0 4px 4px 0;
-}
-
-.input-prepend.input-append input,
-.input-prepend.input-append select,
-.input-prepend.input-append .uneditable-input {
- -webkit-border-radius: 0;
- -moz-border-radius: 0;
- border-radius: 0;
-}
-
-.input-prepend.input-append input + .btn-group .btn,
-.input-prepend.input-append select + .btn-group .btn,
-.input-prepend.input-append .uneditable-input + .btn-group .btn {
- -webkit-border-radius: 0 4px 4px 0;
- -moz-border-radius: 0 4px 4px 0;
- border-radius: 0 4px 4px 0;
-}
-
-.input-prepend.input-append .add-on:first-child,
-.input-prepend.input-append .btn:first-child {
- margin-right: -1px;
- -webkit-border-radius: 4px 0 0 4px;
- -moz-border-radius: 4px 0 0 4px;
- border-radius: 4px 0 0 4px;
-}
-
-.input-prepend.input-append .add-on:last-child,
-.input-prepend.input-append .btn:last-child {
- margin-left: -1px;
- -webkit-border-radius: 0 4px 4px 0;
- -moz-border-radius: 0 4px 4px 0;
- border-radius: 0 4px 4px 0;
-}
-
-.input-prepend.input-append .btn-group:first-child {
- margin-left: 0;
-}
-
-input.search-query {
- padding-right: 14px;
- padding-right: 4px \9;
- padding-left: 14px;
- padding-left: 4px \9;
- /* IE7-8 doesn't have border-radius, so don't indent the padding */
-
- margin-bottom: 0;
- -webkit-border-radius: 15px;
- -moz-border-radius: 15px;
- border-radius: 15px;
-}
-
-/* Allow for input prepend/append in search forms */
-
-.form-search .input-append .search-query,
-.form-search .input-prepend .search-query {
- -webkit-border-radius: 0;
- -moz-border-radius: 0;
- border-radius: 0;
-}
-
-.form-search .input-append .search-query {
- -webkit-border-radius: 14px 0 0 14px;
- -moz-border-radius: 14px 0 0 14px;
- border-radius: 14px 0 0 14px;
-}
-
-.form-search .input-append .btn {
- -webkit-border-radius: 0 14px 14px 0;
- -moz-border-radius: 0 14px 14px 0;
- border-radius: 0 14px 14px 0;
-}
-
-.form-search .input-prepend .search-query {
- -webkit-border-radius: 0 14px 14px 0;
- -moz-border-radius: 0 14px 14px 0;
- border-radius: 0 14px 14px 0;
-}
-
-.form-search .input-prepend .btn {
- -webkit-border-radius: 14px 0 0 14px;
- -moz-border-radius: 14px 0 0 14px;
- border-radius: 14px 0 0 14px;
-}
-
-.form-search input,
-.form-inline input,
-.form-horizontal input,
-.form-search textarea,
-.form-inline textarea,
-.form-horizontal textarea,
-.form-search select,
-.form-inline select,
-.form-horizontal select,
-.form-search .help-inline,
-.form-inline .help-inline,
-.form-horizontal .help-inline,
-.form-search .uneditable-input,
-.form-inline .uneditable-input,
-.form-horizontal .uneditable-input,
-.form-search .input-prepend,
-.form-inline .input-prepend,
-.form-horizontal .input-prepend,
-.form-search .input-append,
-.form-inline .input-append,
-.form-horizontal .input-append {
- display: inline-block;
- *display: inline;
- margin-bottom: 0;
- vertical-align: middle;
- *zoom: 1;
-}
-
-.form-search .hide,
-.form-inline .hide,
-.form-horizontal .hide {
- display: none;
-}
-
-.form-search label,
-.form-inline label,
-.form-search .btn-group,
-.form-inline .btn-group {
- display: inline-block;
-}
-
-.form-search .input-append,
-.form-inline .input-append,
-.form-search .input-prepend,
-.form-inline .input-prepend {
- margin-bottom: 0;
-}
-
-.form-search .radio,
-.form-search .checkbox,
-.form-inline .radio,
-.form-inline .checkbox {
- padding-left: 0;
- margin-bottom: 0;
- vertical-align: middle;
-}
-
-.form-search .radio input[type="radio"],
-.form-search .checkbox input[type="checkbox"],
-.form-inline .radio input[type="radio"],
-.form-inline .checkbox input[type="checkbox"] {
- float: left;
- margin-right: 3px;
- margin-left: 0;
-}
-
-.control-group {
- margin-bottom: 10px;
-}
-
-legend + .control-group {
- margin-top: 20px;
- -webkit-margin-top-collapse: separate;
-}
-
-.form-horizontal .control-group {
- margin-bottom: 20px;
- *zoom: 1;
-}
-
-.form-horizontal .control-group:before,
-.form-horizontal .control-group:after {
- display: table;
- line-height: 0;
- content: "";
-}
-
-.form-horizontal .control-group:after {
- clear: both;
-}
-
-.form-horizontal .control-label {
- float: left;
- width: 160px;
- padding-top: 5px;
- text-align: right;
-}
-
-.form-horizontal .controls {
- *display: inline-block;
- *padding-left: 20px;
- margin-left: 180px;
- *margin-left: 0;
-}
-
-.form-horizontal .controls:first-child {
- *padding-left: 180px;
-}
-
-.form-horizontal .help-block {
- margin-bottom: 0;
-}
-
-.form-horizontal input + .help-block,
-.form-horizontal select + .help-block,
-.form-horizontal textarea + .help-block {
- margin-top: 10px;
-}
-
-.form-horizontal .form-actions {
- padding-left: 180px;
-}
-
-table {
- max-width: 100%;
- background-color: transparent;
- border-collapse: collapse;
- border-spacing: 0;
-}
-
-.table {
- width: 100%;
- margin-bottom: 20px;
-}
-
-.table th,
-.table td {
- padding: 8px;
- line-height: 20px;
- text-align: left;
- vertical-align: top;
- border-top: 1px solid #dddddd;
-}
-
-.table th {
- font-weight: bold;
-}
-
-.table thead th {
- vertical-align: bottom;
-}
-
-.table caption + thead tr:first-child th,
-.table caption + thead tr:first-child td,
-.table colgroup + thead tr:first-child th,
-.table colgroup + thead tr:first-child td,
-.table thead:first-child tr:first-child th,
-.table thead:first-child tr:first-child td {
- border-top: 0;
-}
-
-.table tbody + tbody {
- border-top: 2px solid #dddddd;
-}
-
-.table-condensed th,
-.table-condensed td {
- padding: 4px 5px;
-}
-
-.table-bordered {
- border: 1px solid #dddddd;
- border-collapse: separate;
- *border-collapse: collapse;
- border-left: 0;
- -webkit-border-radius: 4px;
- -moz-border-radius: 4px;
- border-radius: 4px;
-}
-
-.table-bordered th,
-.table-bordered td {
- border-left: 1px solid #dddddd;
-}
-
-.table-bordered caption + thead tr:first-child th,
-.table-bordered caption + tbody tr:first-child th,
-.table-bordered caption + tbody tr:first-child td,
-.table-bordered colgroup + thead tr:first-child th,
-.table-bordered colgroup + tbody tr:first-child th,
-.table-bordered colgroup + tbody tr:first-child td,
-.table-bordered thead:first-child tr:first-child th,
-.table-bordered tbody:first-child tr:first-child th,
-.table-bordered tbody:first-child tr:first-child td {
- border-top: 0;
-}
-
-.table-bordered thead:first-child tr:first-child th:first-child,
-.table-bordered tbody:first-child tr:first-child td:first-child {
- -webkit-border-top-left-radius: 4px;
- border-top-left-radius: 4px;
- -moz-border-radius-topleft: 4px;
-}
-
-.table-bordered thead:first-child tr:first-child th:last-child,
-.table-bordered tbody:first-child tr:first-child td:last-child {
- -webkit-border-top-right-radius: 4px;
- border-top-right-radius: 4px;
- -moz-border-radius-topright: 4px;
-}
-
-.table-bordered thead:last-child tr:last-child th:first-child,
-.table-bordered tbody:last-child tr:last-child td:first-child,
-.table-bordered tfoot:last-child tr:last-child td:first-child {
- -webkit-border-radius: 0 0 0 4px;
- -moz-border-radius: 0 0 0 4px;
- border-radius: 0 0 0 4px;
- -webkit-border-bottom-left-radius: 4px;
- border-bottom-left-radius: 4px;
- -moz-border-radius-bottomleft: 4px;
-}
-
-.table-bordered thead:last-child tr:last-child th:last-child,
-.table-bordered tbody:last-child tr:last-child td:last-child,
-.table-bordered tfoot:last-child tr:last-child td:last-child {
- -webkit-border-bottom-right-radius: 4px;
- border-bottom-right-radius: 4px;
- -moz-border-radius-bottomright: 4px;
-}
-
-.table-bordered caption + thead tr:first-child th:first-child,
-.table-bordered caption + tbody tr:first-child td:first-child,
-.table-bordered colgroup + thead tr:first-child th:first-child,
-.table-bordered colgroup + tbody tr:first-child td:first-child {
- -webkit-border-top-left-radius: 4px;
- border-top-left-radius: 4px;
- -moz-border-radius-topleft: 4px;
-}
-
-.table-bordered caption + thead tr:first-child th:last-child,
-.table-bordered caption + tbody tr:first-child td:last-child,
-.table-bordered colgroup + thead tr:first-child th:last-child,
-.table-bordered colgroup + tbody tr:first-child td:last-child {
- -webkit-border-top-right-radius: 4px;
- border-top-right-radius: 4px;
- -moz-border-radius-topright: 4px;
-}
-
-.table-striped tbody tr:nth-child(odd) td,
-.table-striped tbody tr:nth-child(odd) th {
- background-color: #f9f9f9;
-}
-
-.table-hover tbody tr:hover td,
-.table-hover tbody tr:hover th {
- background-color: #f5f5f5;
-}
-
-table td[class*="span"],
-table th[class*="span"],
-.row-fluid table td[class*="span"],
-.row-fluid table th[class*="span"] {
- display: table-cell;
- float: none;
- margin-left: 0;
-}
-
-.table td.span1,
-.table th.span1 {
- float: none;
- width: 44px;
- margin-left: 0;
-}
-
-.table td.span2,
-.table th.span2 {
- float: none;
- width: 124px;
- margin-left: 0;
-}
-
-.table td.span3,
-.table th.span3 {
- float: none;
- width: 204px;
- margin-left: 0;
-}
-
-.table td.span4,
-.table th.span4 {
- float: none;
- width: 284px;
- margin-left: 0;
-}
-
-.table td.span5,
-.table th.span5 {
- float: none;
- width: 364px;
- margin-left: 0;
-}
-
-.table td.span6,
-.table th.span6 {
- float: none;
- width: 444px;
- margin-left: 0;
-}
-
-.table td.span7,
-.table th.span7 {
- float: none;
- width: 524px;
- margin-left: 0;
-}
-
-.table td.span8,
-.table th.span8 {
- float: none;
- width: 604px;
- margin-left: 0;
-}
-
-.table td.span9,
-.table th.span9 {
- float: none;
- width: 684px;
- margin-left: 0;
-}
-
-.table td.span10,
-.table th.span10 {
- float: none;
- width: 764px;
- margin-left: 0;
-}
-
-.table td.span11,
-.table th.span11 {
- float: none;
- width: 844px;
- margin-left: 0;
-}
-
-.table td.span12,
-.table th.span12 {
- float: none;
- width: 924px;
- margin-left: 0;
-}
-
-.table tbody tr.success td {
- background-color: #dff0d8;
-}
-
-.table tbody tr.error td {
- background-color: #f2dede;
-}
-
-.table tbody tr.warning td {
- background-color: #fcf8e3;
-}
-
-.table tbody tr.info td {
- background-color: #d9edf7;
-}
-
-.table-hover tbody tr.success:hover td {
- background-color: #d0e9c6;
-}
-
-.table-hover tbody tr.error:hover td {
- background-color: #ebcccc;
-}
-
-.table-hover tbody tr.warning:hover td {
- background-color: #faf2cc;
-}
-
-.table-hover tbody tr.info:hover td {
- background-color: #c4e3f3;
-}
-
-[class^="icon-"],
-[class*=" icon-"] {
- display: inline-block;
- width: 14px;
- height: 14px;
- margin-top: 1px;
- *margin-right: .3em;
- line-height: 14px;
- vertical-align: text-top;
- background-image: url("../img/glyphicons-halflings.png");
- background-position: 14px 14px;
- background-repeat: no-repeat;
-}
-
-/* White icons with optional class, or on hover/active states of certain elements */
-
-.icon-white,
-.nav-pills > .active > a > [class^="icon-"],
-.nav-pills > .active > a > [class*=" icon-"],
-.nav-list > .active > a > [class^="icon-"],
-.nav-list > .active > a > [class*=" icon-"],
-.navbar-inverse .nav > .active > a > [class^="icon-"],
-.navbar-inverse .nav > .active > a > [class*=" icon-"],
-.dropdown-menu > li > a:hover > [class^="icon-"],
-.dropdown-menu > li > a:hover > [class*=" icon-"],
-.dropdown-menu > .active > a > [class^="icon-"],
-.dropdown-menu > .active > a > [class*=" icon-"],
-.dropdown-submenu:hover > a > [class^="icon-"],
-.dropdown-submenu:hover > a > [class*=" icon-"] {
- background-image: url("../img/glyphicons-halflings-white.png");
-}
-
-.icon-glass {
- background-position: 0 0;
-}
-
-.icon-music {
- background-position: -24px 0;
-}
-
-.icon-search {
- background-position: -48px 0;
-}
-
-.icon-envelope {
- background-position: -72px 0;
-}
-
-.icon-heart {
- background-position: -96px 0;
-}
-
-.icon-star {
- background-position: -120px 0;
-}
-
-.icon-star-empty {
- background-position: -144px 0;
-}
-
-.icon-user {
- background-position: -168px 0;
-}
-
-.icon-film {
- background-position: -192px 0;
-}
-
-.icon-th-large {
- background-position: -216px 0;
-}
-
-.icon-th {
- background-position: -240px 0;
-}
-
-.icon-th-list {
- background-position: -264px 0;
-}
-
-.icon-ok {
- background-position: -288px 0;
-}
-
-.icon-remove {
- background-position: -312px 0;
-}
-
-.icon-zoom-in {
- background-position: -336px 0;
-}
-
-.icon-zoom-out {
- background-position: -360px 0;
-}
-
-.icon-off {
- background-position: -384px 0;
-}
-
-.icon-signal {
- background-position: -408px 0;
-}
-
-.icon-cog {
- background-position: -432px 0;
-}
-
-.icon-trash {
- background-position: -456px 0;
-}
-
-.icon-home {
- background-position: 0 -24px;
-}
-
-.icon-file {
- background-position: -24px -24px;
-}
-
-.icon-time {
- background-position: -48px -24px;
-}
-
-.icon-road {
- background-position: -72px -24px;
-}
-
-.icon-download-alt {
- background-position: -96px -24px;
-}
-
-.icon-download {
- background-position: -120px -24px;
-}
-
-.icon-upload {
- background-position: -144px -24px;
-}
-
-.icon-inbox {
- background-position: -168px -24px;
-}
-
-.icon-play-circle {
- background-position: -192px -24px;
-}
-
-.icon-repeat {
- background-position: -216px -24px;
-}
-
-.icon-refresh {
- background-position: -240px -24px;
-}
-
-.icon-list-alt {
- background-position: -264px -24px;
-}
-
-.icon-lock {
- background-position: -287px -24px;
-}
-
-.icon-flag {
- background-position: -312px -24px;
-}
-
-.icon-headphones {
- background-position: -336px -24px;
-}
-
-.icon-volume-off {
- background-position: -360px -24px;
-}
-
-.icon-volume-down {
- background-position: -384px -24px;
-}
-
-.icon-volume-up {
- background-position: -408px -24px;
-}
-
-.icon-qrcode {
- background-position: -432px -24px;
-}
-
-.icon-barcode {
- background-position: -456px -24px;
-}
-
-.icon-tag {
- background-position: 0 -48px;
-}
-
-.icon-tags {
- background-position: -25px -48px;
-}
-
-.icon-book {
- background-position: -48px -48px;
-}
-
-.icon-bookmark {
- background-position: -72px -48px;
-}
-
-.icon-print {
- background-position: -96px -48px;
-}
-
-.icon-camera {
- background-position: -120px -48px;
-}
-
-.icon-font {
- background-position: -144px -48px;
-}
-
-.icon-bold {
- background-position: -167px -48px;
-}
-
-.icon-italic {
- background-position: -192px -48px;
-}
-
-.icon-text-height {
- background-position: -216px -48px;
-}
-
-.icon-text-width {
- background-position: -240px -48px;
-}
-
-.icon-align-left {
- background-position: -264px -48px;
-}
-
-.icon-align-center {
- background-position: -288px -48px;
-}
-
-.icon-align-right {
- background-position: -312px -48px;
-}
-
-.icon-align-justify {
- background-position: -336px -48px;
-}
-
-.icon-list {
- background-position: -360px -48px;
-}
-
-.icon-indent-left {
- background-position: -384px -48px;
-}
-
-.icon-indent-right {
- background-position: -408px -48px;
-}
-
-.icon-facetime-video {
- background-position: -432px -48px;
-}
-
-.icon-picture {
- background-position: -456px -48px;
-}
-
-.icon-pencil {
- background-position: 0 -72px;
-}
-
-.icon-map-marker {
- background-position: -24px -72px;
-}
-
-.icon-adjust {
- background-position: -48px -72px;
-}
-
-.icon-tint {
- background-position: -72px -72px;
-}
-
-.icon-edit {
- background-position: -96px -72px;
-}
-
-.icon-share {
- background-position: -120px -72px;
-}
-
-.icon-check {
- background-position: -144px -72px;
-}
-
-.icon-move {
- background-position: -168px -72px;
-}
-
-.icon-step-backward {
- background-position: -192px -72px;
-}
-
-.icon-fast-backward {
- background-position: -216px -72px;
-}
-
-.icon-backward {
- background-position: -240px -72px;
-}
-
-.icon-play {
- background-position: -264px -72px;
-}
-
-.icon-pause {
- background-position: -288px -72px;
-}
-
-.icon-stop {
- background-position: -312px -72px;
-}
-
-.icon-forward {
- background-position: -336px -72px;
-}
-
-.icon-fast-forward {
- background-position: -360px -72px;
-}
-
-.icon-step-forward {
- background-position: -384px -72px;
-}
-
-.icon-eject {
- background-position: -408px -72px;
-}
-
-.icon-chevron-left {
- background-position: -432px -72px;
-}
-
-.icon-chevron-right {
- background-position: -456px -72px;
-}
-
-.icon-plus-sign {
- background-position: 0 -96px;
-}
-
-.icon-minus-sign {
- background-position: -24px -96px;
-}
-
-.icon-remove-sign {
- background-position: -48px -96px;
-}
-
-.icon-ok-sign {
- background-position: -72px -96px;
-}
-
-.icon-question-sign {
- background-position: -96px -96px;
-}
-
-.icon-info-sign {
- background-position: -120px -96px;
-}
-
-.icon-screenshot {
- background-position: -144px -96px;
-}
-
-.icon-remove-circle {
- background-position: -168px -96px;
-}
-
-.icon-ok-circle {
- background-position: -192px -96px;
-}
-
-.icon-ban-circle {
- background-position: -216px -96px;
-}
-
-.icon-arrow-left {
- background-position: -240px -96px;
-}
-
-.icon-arrow-right {
- background-position: -264px -96px;
-}
-
-.icon-arrow-up {
- background-position: -289px -96px;
-}
-
-.icon-arrow-down {
- background-position: -312px -96px;
-}
-
-.icon-share-alt {
- background-position: -336px -96px;
-}
-
-.icon-resize-full {
- background-position: -360px -96px;
-}
-
-.icon-resize-small {
- background-position: -384px -96px;
-}
-
-.icon-plus {
- background-position: -408px -96px;
-}
-
-.icon-minus {
- background-position: -433px -96px;
-}
-
-.icon-asterisk {
- background-position: -456px -96px;
-}
-
-.icon-exclamation-sign {
- background-position: 0 -120px;
-}
-
-.icon-gift {
- background-position: -24px -120px;
-}
-
-.icon-leaf {
- background-position: -48px -120px;
-}
-
-.icon-fire {
- background-position: -72px -120px;
-}
-
-.icon-eye-open {
- background-position: -96px -120px;
-}
-
-.icon-eye-close {
- background-position: -120px -120px;
-}
-
-.icon-warning-sign {
- background-position: -144px -120px;
-}
-
-.icon-plane {
- background-position: -168px -120px;
-}
-
-.icon-calendar {
- background-position: -192px -120px;
-}
-
-.icon-random {
- width: 16px;
- background-position: -216px -120px;
-}
-
-.icon-comment {
- background-position: -240px -120px;
-}
-
-.icon-magnet {
- background-position: -264px -120px;
-}
-
-.icon-chevron-up {
- background-position: -288px -120px;
-}
-
-.icon-chevron-down {
- background-position: -313px -119px;
-}
-
-.icon-retweet {
- background-position: -336px -120px;
-}
-
-.icon-shopping-cart {
- background-position: -360px -120px;
-}
-
-.icon-folder-close {
- background-position: -384px -120px;
-}
-
-.icon-folder-open {
- width: 16px;
- background-position: -408px -120px;
-}
-
-.icon-resize-vertical {
- background-position: -432px -119px;
-}
-
-.icon-resize-horizontal {
- background-position: -456px -118px;
-}
-
-.icon-hdd {
- background-position: 0 -144px;
-}
-
-.icon-bullhorn {
- background-position: -24px -144px;
-}
-
-.icon-bell {
- background-position: -48px -144px;
-}
-
-.icon-certificate {
- background-position: -72px -144px;
-}
-
-.icon-thumbs-up {
- background-position: -96px -144px;
-}
-
-.icon-thumbs-down {
- background-position: -120px -144px;
-}
-
-.icon-hand-right {
- background-position: -144px -144px;
-}
-
-.icon-hand-left {
- background-position: -168px -144px;
-}
-
-.icon-hand-up {
- background-position: -192px -144px;
-}
-
-.icon-hand-down {
- background-position: -216px -144px;
-}
-
-.icon-circle-arrow-right {
- background-position: -240px -144px;
-}
-
-.icon-circle-arrow-left {
- background-position: -264px -144px;
-}
-
-.icon-circle-arrow-up {
- background-position: -288px -144px;
-}
-
-.icon-circle-arrow-down {
- background-position: -312px -144px;
-}
-
-.icon-globe {
- background-position: -336px -144px;
-}
-
-.icon-wrench {
- background-position: -360px -144px;
-}
-
-.icon-tasks {
- background-position: -384px -144px;
-}
-
-.icon-filter {
- background-position: -408px -144px;
-}
-
-.icon-briefcase {
- background-position: -432px -144px;
-}
-
-.icon-fullscreen {
- background-position: -456px -144px;
-}
-
-.dropup,
-.dropdown {
- position: relative;
-}
-
-.dropdown-toggle {
- *margin-bottom: -3px;
-}
-
-.dropdown-toggle:active,
-.open .dropdown-toggle {
- outline: 0;
-}
-
-.caret {
- display: inline-block;
- width: 0;
- height: 0;
- vertical-align: top;
- border-top: 4px solid #000000;
- border-right: 4px solid transparent;
- border-left: 4px solid transparent;
- content: "";
-}
-
-.dropdown .caret {
- margin-top: 8px;
- margin-left: 2px;
-}
-
-.dropdown-menu {
- position: absolute;
- top: 100%;
- left: 0;
- z-index: 1000;
- display: none;
- float: left;
- min-width: 160px;
- padding: 5px 0;
- margin: 2px 0 0;
- list-style: none;
- background-color: #ffffff;
- border: 1px solid #ccc;
- border: 1px solid rgba(0, 0, 0, 0.2);
- *border-right-width: 2px;
- *border-bottom-width: 2px;
- -webkit-border-radius: 6px;
- -moz-border-radius: 6px;
- border-radius: 6px;
- -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
- -moz-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
- box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
- -webkit-background-clip: padding-box;
- -moz-background-clip: padding;
- background-clip: padding-box;
-}
-
-.dropdown-menu.pull-right {
- right: 0;
- left: auto;
-}
-
-.dropdown-menu .divider {
- *width: 100%;
- height: 1px;
- margin: 9px 1px;
- *margin: -5px 0 5px;
- overflow: hidden;
- background-color: #e5e5e5;
- border-bottom: 1px solid #ffffff;
-}
-
-.dropdown-menu li > a {
- display: block;
- padding: 3px 20px;
- clear: both;
- font-weight: normal;
- line-height: 20px;
- color: #333333;
- white-space: nowrap;
-}
-
-.dropdown-menu li > a:hover,
-.dropdown-menu li > a:focus,
-.dropdown-submenu:hover > a {
- color: #ffffff;
- text-decoration: none;
- background-color: #0081c2;
- background-image: -moz-linear-gradient(top, #0088cc, #0077b3);
- background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0077b3));
- background-image: -webkit-linear-gradient(top, #0088cc, #0077b3);
- background-image: -o-linear-gradient(top, #0088cc, #0077b3);
- background-image: linear-gradient(to bottom, #0088cc, #0077b3);
- background-repeat: repeat-x;
- filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0077b3', GradientType=0);
-}
-
-.dropdown-menu .active > a,
-.dropdown-menu .active > a:hover {
- color: #333333;
- text-decoration: none;
- background-color: #0081c2;
- background-image: -moz-linear-gradient(top, #0088cc, #0077b3);
- background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0077b3));
- background-image: -webkit-linear-gradient(top, #0088cc, #0077b3);
- background-image: -o-linear-gradient(top, #0088cc, #0077b3);
- background-image: linear-gradient(to bottom, #0088cc, #0077b3);
- background-repeat: repeat-x;
- outline: 0;
- filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0077b3', GradientType=0);
-}
-
-.dropdown-menu .disabled > a,
-.dropdown-menu .disabled > a:hover {
- color: #999999;
-}
-
-.dropdown-menu .disabled > a:hover {
- text-decoration: none;
- cursor: default;
- background-color: transparent;
- background-image: none;
-}
-
-.open {
- *z-index: 1000;
-}
-
-.open > .dropdown-menu {
- display: block;
-}
-
-.pull-right > .dropdown-menu {
- right: 0;
- left: auto;
-}
-
-.dropup .caret,
-.navbar-fixed-bottom .dropdown .caret {
- border-top: 0;
- border-bottom: 4px solid #000000;
- content: "";
-}
-
-.dropup .dropdown-menu,
-.navbar-fixed-bottom .dropdown .dropdown-menu {
- top: auto;
- bottom: 100%;
- margin-bottom: 1px;
-}
-
-.dropdown-submenu {
- position: relative;
-}
-
-.dropdown-submenu > .dropdown-menu {
- top: 0;
- left: 100%;
- margin-top: -6px;
- margin-left: -1px;
- -webkit-border-radius: 0 6px 6px 6px;
- -moz-border-radius: 0 6px 6px 6px;
- border-radius: 0 6px 6px 6px;
-}
-
-.dropdown-submenu:hover > .dropdown-menu {
- display: block;
-}
-
-.dropup .dropdown-submenu > .dropdown-menu {
- top: auto;
- bottom: 0;
- margin-top: 0;
- margin-bottom: -2px;
- -webkit-border-radius: 5px 5px 5px 0;
- -moz-border-radius: 5px 5px 5px 0;
- border-radius: 5px 5px 5px 0;
-}
-
-.dropdown-submenu > a:after {
- display: block;
- float: right;
- width: 0;
- height: 0;
- margin-top: 5px;
- margin-right: -10px;
- border-color: transparent;
- border-left-color: #cccccc;
- border-style: solid;
- border-width: 5px 0 5px 5px;
- content: " ";
-}
-
-.dropdown-submenu:hover > a:after {
- border-left-color: #ffffff;
-}
-
-.dropdown-submenu.pull-left {
- float: none;
-}
-
-.dropdown-submenu.pull-left > .dropdown-menu {
- left: -100%;
- margin-left: 10px;
- -webkit-border-radius: 6px 0 6px 6px;
- -moz-border-radius: 6px 0 6px 6px;
- border-radius: 6px 0 6px 6px;
-}
-
-.dropdown .dropdown-menu .nav-header {
- padding-right: 20px;
- padding-left: 20px;
-}
-
-.typeahead {
- margin-top: 2px;
- -webkit-border-radius: 4px;
- -moz-border-radius: 4px;
- border-radius: 4px;
-}
-
-.well {
- min-height: 20px;
- padding: 19px;
- margin-bottom: 20px;
- background-color: #f5f5f5;
- border: 1px solid #e3e3e3;
- -webkit-border-radius: 4px;
- -moz-border-radius: 4px;
- border-radius: 4px;
- -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);
- -moz-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);
- box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.05);
-}
-
-.well blockquote {
- border-color: #ddd;
- border-color: rgba(0, 0, 0, 0.15);
-}
-
-.well-large {
- padding: 24px;
- -webkit-border-radius: 6px;
- -moz-border-radius: 6px;
- border-radius: 6px;
-}
-
-.well-small {
- padding: 9px;
- -webkit-border-radius: 3px;
- -moz-border-radius: 3px;
- border-radius: 3px;
-}
-
-.fade {
- opacity: 0;
- -webkit-transition: opacity 0.15s linear;
- -moz-transition: opacity 0.15s linear;
- -o-transition: opacity 0.15s linear;
- transition: opacity 0.15s linear;
-}
-
-.fade.in {
- opacity: 1;
-}
-
-.collapse {
- position: relative;
- height: 0;
- overflow: hidden;
- -webkit-transition: height 0.35s ease;
- -moz-transition: height 0.35s ease;
- -o-transition: height 0.35s ease;
- transition: height 0.35s ease;
-}
-
-.collapse.in {
- height: auto;
-}
-
-.close {
- float: right;
- font-size: 20px;
- font-weight: bold;
- line-height: 20px;
- color: #000000;
- text-shadow: 0 1px 0 #ffffff;
- opacity: 0.2;
- filter: alpha(opacity=20);
-}
-
-.close:hover {
- color: #000000;
- text-decoration: none;
- cursor: pointer;
- opacity: 0.4;
- filter: alpha(opacity=40);
-}
-
-button.close {
- padding: 0;
- cursor: pointer;
- background: transparent;
- border: 0;
- -webkit-appearance: none;
-}
-
-.btn {
- display: inline-block;
- *display: inline;
- padding: 4px 12px;
- margin-bottom: 0;
- *margin-left: .3em;
- font-size: 14px;
- line-height: 20px;
- *line-height: 20px;
- color: #333333;
- text-align: center;
- text-shadow: 0 1px 1px rgba(255, 255, 255, 0.75);
- vertical-align: middle;
- cursor: pointer;
- background-color: #f5f5f5;
- *background-color: #e6e6e6;
- background-image: -moz-linear-gradient(top, #ffffff, #e6e6e6);
- background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#e6e6e6));
- background-image: -webkit-linear-gradient(top, #ffffff, #e6e6e6);
- background-image: -o-linear-gradient(top, #ffffff, #e6e6e6);
- background-image: linear-gradient(to bottom, #ffffff, #e6e6e6);
- background-repeat: repeat-x;
- border: 1px solid #bbbbbb;
- *border: 0;
- border-color: #e6e6e6 #e6e6e6 #bfbfbf;
- border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
- border-bottom-color: #a2a2a2;
- -webkit-border-radius: 4px;
- -moz-border-radius: 4px;
- border-radius: 4px;
- filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe6e6e6', GradientType=0);
- filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
- *zoom: 1;
- -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
- -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
- box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
-}
-
-.btn:hover,
-.btn:active,
-.btn.active,
-.btn.disabled,
-.btn[disabled] {
- color: #333333;
- background-color: #e6e6e6;
- *background-color: #d9d9d9;
-}
-
-.btn:active,
-.btn.active {
- background-color: #cccccc \9;
-}
-
-.btn:first-child {
- *margin-left: 0;
-}
-
-.btn:hover {
- color: #333333;
- text-decoration: none;
- background-color: #e6e6e6;
- *background-color: #d9d9d9;
- /* Buttons in IE7 don't get borders, so darken on hover */
-
- background-position: 0 -15px;
- -webkit-transition: background-position 0.1s linear;
- -moz-transition: background-position 0.1s linear;
- -o-transition: background-position 0.1s linear;
- transition: background-position 0.1s linear;
-}
-
-.btn:focus {
- outline: thin dotted #333;
- outline: 5px auto -webkit-focus-ring-color;
- outline-offset: -2px;
-}
-
-.btn.active,
-.btn:active {
- background-color: #e6e6e6;
- background-color: #d9d9d9 \9;
- background-image: none;
- outline: 0;
- -webkit-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
- -moz-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
- box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
-}
-
-.btn.disabled,
-.btn[disabled] {
- cursor: default;
- background-color: #e6e6e6;
- background-image: none;
- opacity: 0.65;
- filter: alpha(opacity=65);
- -webkit-box-shadow: none;
- -moz-box-shadow: none;
- box-shadow: none;
-}
-
-.btn-large {
- padding: 11px 19px;
- font-size: 17.5px;
- -webkit-border-radius: 6px;
- -moz-border-radius: 6px;
- border-radius: 6px;
-}
-
-.btn-large [class^="icon-"],
-.btn-large [class*=" icon-"] {
- margin-top: 2px;
-}
-
-.btn-small {
- padding: 2px 10px;
- font-size: 11.9px;
- -webkit-border-radius: 3px;
- -moz-border-radius: 3px;
- border-radius: 3px;
-}
-
-.btn-small [class^="icon-"],
-.btn-small [class*=" icon-"] {
- margin-top: 0;
-}
-
-.btn-mini {
- padding: 1px 6px;
- font-size: 10.5px;
- -webkit-border-radius: 3px;
- -moz-border-radius: 3px;
- border-radius: 3px;
-}
-
-.btn-block {
- display: block;
- width: 100%;
- padding-right: 0;
- padding-left: 0;
- -webkit-box-sizing: border-box;
- -moz-box-sizing: border-box;
- box-sizing: border-box;
-}
-
-.btn-block + .btn-block {
- margin-top: 5px;
-}
-
-input[type="submit"].btn-block,
-input[type="reset"].btn-block,
-input[type="button"].btn-block {
- width: 100%;
-}
-
-.btn-primary.active,
-.btn-warning.active,
-.btn-danger.active,
-.btn-success.active,
-.btn-info.active,
-.btn-inverse.active {
- color: rgba(255, 255, 255, 0.75);
-}
-
-.btn {
- border-color: #c5c5c5;
- border-color: rgba(0, 0, 0, 0.15) rgba(0, 0, 0, 0.15) rgba(0, 0, 0, 0.25);
-}
-
-.btn-primary {
- color: #ffffff;
- text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
- background-color: #006dcc;
- *background-color: #0044cc;
- background-image: -moz-linear-gradient(top, #0088cc, #0044cc);
- background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc));
- background-image: -webkit-linear-gradient(top, #0088cc, #0044cc);
- background-image: -o-linear-gradient(top, #0088cc, #0044cc);
- background-image: linear-gradient(to bottom, #0088cc, #0044cc);
- background-repeat: repeat-x;
- border-color: #0044cc #0044cc #002a80;
- border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
- filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0044cc', GradientType=0);
- filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
-}
-
-.btn-primary:hover,
-.btn-primary:active,
-.btn-primary.active,
-.btn-primary.disabled,
-.btn-primary[disabled] {
- color: #ffffff;
- background-color: #0044cc;
- *background-color: #003bb3;
-}
-
-.btn-primary:active,
-.btn-primary.active {
- background-color: #003399 \9;
-}
-
-.btn-warning {
- color: #ffffff;
- text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
- background-color: #faa732;
- *background-color: #f89406;
- background-image: -moz-linear-gradient(top, #fbb450, #f89406);
- background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#fbb450), to(#f89406));
- background-image: -webkit-linear-gradient(top, #fbb450, #f89406);
- background-image: -o-linear-gradient(top, #fbb450, #f89406);
- background-image: linear-gradient(to bottom, #fbb450, #f89406);
- background-repeat: repeat-x;
- border-color: #f89406 #f89406 #ad6704;
- border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
- filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffbb450', endColorstr='#fff89406', GradientType=0);
- filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
-}
-
-.btn-warning:hover,
-.btn-warning:active,
-.btn-warning.active,
-.btn-warning.disabled,
-.btn-warning[disabled] {
- color: #ffffff;
- background-color: #f89406;
- *background-color: #df8505;
-}
-
-.btn-warning:active,
-.btn-warning.active {
- background-color: #c67605 \9;
-}
-
-.btn-danger {
- color: #ffffff;
- text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
- background-color: #da4f49;
- *background-color: #bd362f;
- background-image: -moz-linear-gradient(top, #ee5f5b, #bd362f);
- background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ee5f5b), to(#bd362f));
- background-image: -webkit-linear-gradient(top, #ee5f5b, #bd362f);
- background-image: -o-linear-gradient(top, #ee5f5b, #bd362f);
- background-image: linear-gradient(to bottom, #ee5f5b, #bd362f);
- background-repeat: repeat-x;
- border-color: #bd362f #bd362f #802420;
- border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
- filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffee5f5b', endColorstr='#ffbd362f', GradientType=0);
- filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
-}
-
-.btn-danger:hover,
-.btn-danger:active,
-.btn-danger.active,
-.btn-danger.disabled,
-.btn-danger[disabled] {
- color: #ffffff;
- background-color: #bd362f;
- *background-color: #a9302a;
-}
-
-.btn-danger:active,
-.btn-danger.active {
- background-color: #942a25 \9;
-}
-
-.btn-success {
- color: #ffffff;
- text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
- background-color: #5bb75b;
- *background-color: #51a351;
- background-image: -moz-linear-gradient(top, #62c462, #51a351);
- background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#62c462), to(#51a351));
- background-image: -webkit-linear-gradient(top, #62c462, #51a351);
- background-image: -o-linear-gradient(top, #62c462, #51a351);
- background-image: linear-gradient(to bottom, #62c462, #51a351);
- background-repeat: repeat-x;
- border-color: #51a351 #51a351 #387038;
- border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
- filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff62c462', endColorstr='#ff51a351', GradientType=0);
- filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
-}
-
-.btn-success:hover,
-.btn-success:active,
-.btn-success.active,
-.btn-success.disabled,
-.btn-success[disabled] {
- color: #ffffff;
- background-color: #51a351;
- *background-color: #499249;
-}
-
-.btn-success:active,
-.btn-success.active {
- background-color: #408140 \9;
-}
-
-.btn-info {
- color: #ffffff;
- text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
- background-color: #49afcd;
- *background-color: #2f96b4;
- background-image: -moz-linear-gradient(top, #5bc0de, #2f96b4);
- background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#5bc0de), to(#2f96b4));
- background-image: -webkit-linear-gradient(top, #5bc0de, #2f96b4);
- background-image: -o-linear-gradient(top, #5bc0de, #2f96b4);
- background-image: linear-gradient(to bottom, #5bc0de, #2f96b4);
- background-repeat: repeat-x;
- border-color: #2f96b4 #2f96b4 #1f6377;
- border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
- filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2f96b4', GradientType=0);
- filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
-}
-
-.btn-info:hover,
-.btn-info:active,
-.btn-info.active,
-.btn-info.disabled,
-.btn-info[disabled] {
- color: #ffffff;
- background-color: #2f96b4;
- *background-color: #2a85a0;
-}
-
-.btn-info:active,
-.btn-info.active {
- background-color: #24748c \9;
-}
-
-.btn-inverse {
- color: #ffffff;
- text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
- background-color: #363636;
- *background-color: #222222;
- background-image: -moz-linear-gradient(top, #444444, #222222);
- background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#444444), to(#222222));
- background-image: -webkit-linear-gradient(top, #444444, #222222);
- background-image: -o-linear-gradient(top, #444444, #222222);
- background-image: linear-gradient(to bottom, #444444, #222222);
- background-repeat: repeat-x;
- border-color: #222222 #222222 #000000;
- border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
- filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff444444', endColorstr='#ff222222', GradientType=0);
- filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
-}
-
-.btn-inverse:hover,
-.btn-inverse:active,
-.btn-inverse.active,
-.btn-inverse.disabled,
-.btn-inverse[disabled] {
- color: #ffffff;
- background-color: #222222;
- *background-color: #151515;
-}
-
-.btn-inverse:active,
-.btn-inverse.active {
- background-color: #080808 \9;
-}
-
-button.btn,
-input[type="submit"].btn {
- *padding-top: 3px;
- *padding-bottom: 3px;
-}
-
-button.btn::-moz-focus-inner,
-input[type="submit"].btn::-moz-focus-inner {
- padding: 0;
- border: 0;
-}
-
-button.btn.btn-large,
-input[type="submit"].btn.btn-large {
- *padding-top: 7px;
- *padding-bottom: 7px;
-}
-
-button.btn.btn-small,
-input[type="submit"].btn.btn-small {
- *padding-top: 3px;
- *padding-bottom: 3px;
-}
-
-button.btn.btn-mini,
-input[type="submit"].btn.btn-mini {
- *padding-top: 1px;
- *padding-bottom: 1px;
-}
-
-.btn-link,
-.btn-link:active,
-.btn-link[disabled] {
- background-color: transparent;
- background-image: none;
- -webkit-box-shadow: none;
- -moz-box-shadow: none;
- box-shadow: none;
-}
-
-.btn-link {
- color: #0088cc;
- cursor: pointer;
- border-color: transparent;
- -webkit-border-radius: 0;
- -moz-border-radius: 0;
- border-radius: 0;
-}
-
-.btn-link:hover {
- color: #005580;
- text-decoration: underline;
- background-color: transparent;
-}
-
-.btn-link[disabled]:hover {
- color: #333333;
- text-decoration: none;
-}
-
-.btn-group {
- position: relative;
- display: inline-block;
- *display: inline;
- *margin-left: .3em;
- font-size: 0;
- white-space: nowrap;
- vertical-align: middle;
- *zoom: 1;
-}
-
-.btn-group:first-child {
- *margin-left: 0;
-}
-
-.btn-group + .btn-group {
- margin-left: 5px;
-}
-
-.btn-toolbar {
- margin-top: 10px;
- margin-bottom: 10px;
- font-size: 0;
-}
-
-.btn-toolbar .btn + .btn,
-.btn-toolbar .btn-group + .btn,
-.btn-toolbar .btn + .btn-group {
- margin-left: 5px;
-}
-
-.btn-group > .btn {
- position: relative;
- -webkit-border-radius: 0;
- -moz-border-radius: 0;
- border-radius: 0;
-}
-
-.btn-group > .btn + .btn {
- margin-left: -1px;
-}
-
-.btn-group > .btn,
-.btn-group > .dropdown-menu {
- font-size: 14px;
-}
-
-.btn-group > .btn-mini {
- font-size: 11px;
-}
-
-.btn-group > .btn-small {
- font-size: 12px;
-}
-
-.btn-group > .btn-large {
- font-size: 16px;
-}
-
-.btn-group > .btn:first-child {
- margin-left: 0;
- -webkit-border-bottom-left-radius: 4px;
- border-bottom-left-radius: 4px;
- -webkit-border-top-left-radius: 4px;
- border-top-left-radius: 4px;
- -moz-border-radius-bottomleft: 4px;
- -moz-border-radius-topleft: 4px;
-}
-
-.btn-group > .btn:last-child,
-.btn-group > .dropdown-toggle {
- -webkit-border-top-right-radius: 4px;
- border-top-right-radius: 4px;
- -webkit-border-bottom-right-radius: 4px;
- border-bottom-right-radius: 4px;
- -moz-border-radius-topright: 4px;
- -moz-border-radius-bottomright: 4px;
-}
-
-.btn-group > .btn.large:first-child {
- margin-left: 0;
- -webkit-border-bottom-left-radius: 6px;
- border-bottom-left-radius: 6px;
- -webkit-border-top-left-radius: 6px;
- border-top-left-radius: 6px;
- -moz-border-radius-bottomleft: 6px;
- -moz-border-radius-topleft: 6px;
-}
-
-.btn-group > .btn.large:last-child,
-.btn-group > .large.dropdown-toggle {
- -webkit-border-top-right-radius: 6px;
- border-top-right-radius: 6px;
- -webkit-border-bottom-right-radius: 6px;
- border-bottom-right-radius: 6px;
- -moz-border-radius-topright: 6px;
- -moz-border-radius-bottomright: 6px;
-}
-
-.btn-group > .btn:hover,
-.btn-group > .btn:focus,
-.btn-group > .btn:active,
-.btn-group > .btn.active {
- z-index: 2;
-}
-
-.btn-group .dropdown-toggle:active,
-.btn-group.open .dropdown-toggle {
- outline: 0;
-}
-
-.btn-group > .btn + .dropdown-toggle {
- *padding-top: 5px;
- padding-right: 8px;
- *padding-bottom: 5px;
- padding-left: 8px;
- -webkit-box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.125), inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
- -moz-box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.125), inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
- box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.125), inset 0 1px 0 rgba(255, 255, 255, 0.2), 0 1px 2px rgba(0, 0, 0, 0.05);
-}
-
-.btn-group > .btn-mini + .dropdown-toggle {
- *padding-top: 2px;
- padding-right: 5px;
- *padding-bottom: 2px;
- padding-left: 5px;
-}
-
-.btn-group > .btn-small + .dropdown-toggle {
- *padding-top: 5px;
- *padding-bottom: 4px;
-}
-
-.btn-group > .btn-large + .dropdown-toggle {
- *padding-top: 7px;
- padding-right: 12px;
- *padding-bottom: 7px;
- padding-left: 12px;
-}
-
-.btn-group.open .dropdown-toggle {
- background-image: none;
- -webkit-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
- -moz-box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
- box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), 0 1px 2px rgba(0, 0, 0, 0.05);
-}
-
-.btn-group.open .btn.dropdown-toggle {
- background-color: #e6e6e6;
-}
-
-.btn-group.open .btn-primary.dropdown-toggle {
- background-color: #0044cc;
-}
-
-.btn-group.open .btn-warning.dropdown-toggle {
- background-color: #f89406;
-}
-
-.btn-group.open .btn-danger.dropdown-toggle {
- background-color: #bd362f;
-}
-
-.btn-group.open .btn-success.dropdown-toggle {
- background-color: #51a351;
-}
-
-.btn-group.open .btn-info.dropdown-toggle {
- background-color: #2f96b4;
-}
-
-.btn-group.open .btn-inverse.dropdown-toggle {
- background-color: #222222;
-}
-
-.btn .caret {
- margin-top: 8px;
- margin-left: 0;
-}
-
-.btn-mini .caret,
-.btn-small .caret,
-.btn-large .caret {
- margin-top: 6px;
-}
-
-.btn-large .caret {
- border-top-width: 5px;
- border-right-width: 5px;
- border-left-width: 5px;
-}
-
-.dropup .btn-large .caret {
- border-bottom-width: 5px;
-}
-
-.btn-primary .caret,
-.btn-warning .caret,
-.btn-danger .caret,
-.btn-info .caret,
-.btn-success .caret,
-.btn-inverse .caret {
- border-top-color: #ffffff;
- border-bottom-color: #ffffff;
-}
-
-.btn-group-vertical {
- display: inline-block;
- *display: inline;
- /* IE7 inline-block hack */
-
- *zoom: 1;
-}
-
-.btn-group-vertical .btn {
- display: block;
- float: none;
- width: 100%;
- -webkit-border-radius: 0;
- -moz-border-radius: 0;
- border-radius: 0;
-}
-
-.btn-group-vertical .btn + .btn {
- margin-top: -1px;
- margin-left: 0;
-}
-
-.btn-group-vertical .btn:first-child {
- -webkit-border-radius: 4px 4px 0 0;
- -moz-border-radius: 4px 4px 0 0;
- border-radius: 4px 4px 0 0;
-}
-
-.btn-group-vertical .btn:last-child {
- -webkit-border-radius: 0 0 4px 4px;
- -moz-border-radius: 0 0 4px 4px;
- border-radius: 0 0 4px 4px;
-}
-
-.btn-group-vertical .btn-large:first-child {
- -webkit-border-radius: 6px 6px 0 0;
- -moz-border-radius: 6px 6px 0 0;
- border-radius: 6px 6px 0 0;
-}
-
-.btn-group-vertical .btn-large:last-child {
- -webkit-border-radius: 0 0 6px 6px;
- -moz-border-radius: 0 0 6px 6px;
- border-radius: 0 0 6px 6px;
-}
-
-.alert {
- padding: 8px 35px 8px 14px;
- margin-bottom: 20px;
- color: #c09853;
- text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
- background-color: #fcf8e3;
- border: 1px solid #fbeed5;
- -webkit-border-radius: 4px;
- -moz-border-radius: 4px;
- border-radius: 4px;
-}
-
-.alert h4 {
- margin: 0;
-}
-
-.alert .close {
- position: relative;
- top: -2px;
- right: -21px;
- line-height: 20px;
-}
-
-.alert-success {
- color: #468847;
- background-color: #dff0d8;
- border-color: #d6e9c6;
-}
-
-.alert-danger,
-.alert-error {
- color: #b94a48;
- background-color: #f2dede;
- border-color: #eed3d7;
-}
-
-.alert-info {
- color: #3a87ad;
- background-color: #d9edf7;
- border-color: #bce8f1;
-}
-
-.alert-block {
- padding-top: 14px;
- padding-bottom: 14px;
-}
-
-.alert-block > p,
-.alert-block > ul {
- margin-bottom: 0;
-}
-
-.alert-block p + p {
- margin-top: 5px;
-}
-
-.nav {
- margin-bottom: 20px;
- margin-left: 0;
- list-style: none;
-}
-
-.nav > li > a {
- display: block;
-}
-
-.nav > li > a:hover {
- text-decoration: none;
- background-color: #eeeeee;
-}
-
-.nav > .pull-right {
- float: right;
-}
-
-.nav-header {
- display: block;
- padding: 3px 15px;
- font-size: 11px;
- font-weight: bold;
- line-height: 20px;
- color: #999999;
- text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
- text-transform: uppercase;
-}
-
-.nav li + .nav-header {
- margin-top: 9px;
-}
-
-.nav-list {
- padding-right: 15px;
- padding-left: 15px;
- margin-bottom: 0;
-}
-
-.nav-list > li > a,
-.nav-list .nav-header {
- margin-right: -15px;
- margin-left: -15px;
- text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
-}
-
-.nav-list > li > a {
- padding: 3px 15px;
-}
-
-.nav-list > .active > a,
-.nav-list > .active > a:hover {
- color: #ffffff;
- text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.2);
- background-color: #0088cc;
-}
-
-.nav-list [class^="icon-"],
-.nav-list [class*=" icon-"] {
- margin-right: 2px;
-}
-
-.nav-list .divider {
- *width: 100%;
- height: 1px;
- margin: 9px 1px;
- *margin: -5px 0 5px;
- overflow: hidden;
- background-color: #e5e5e5;
- border-bottom: 1px solid #ffffff;
-}
-
-.nav-tabs,
-.nav-pills {
- *zoom: 1;
-}
-
-.nav-tabs:before,
-.nav-pills:before,
-.nav-tabs:after,
-.nav-pills:after {
- display: table;
- line-height: 0;
- content: "";
-}
-
-.nav-tabs:after,
-.nav-pills:after {
- clear: both;
-}
-
-.nav-tabs > li,
-.nav-pills > li {
- float: left;
-}
-
-.nav-tabs > li > a,
-.nav-pills > li > a {
- padding-right: 12px;
- padding-left: 12px;
- margin-right: 2px;
- line-height: 14px;
-}
-
-.nav-tabs {
- border-bottom: 1px solid #ddd;
-}
-
-.nav-tabs > li {
- margin-bottom: -1px;
-}
-
-.nav-tabs > li > a {
- padding-top: 8px;
- padding-bottom: 8px;
- line-height: 20px;
- border: 1px solid transparent;
- -webkit-border-radius: 4px 4px 0 0;
- -moz-border-radius: 4px 4px 0 0;
- border-radius: 4px 4px 0 0;
-}
-
-.nav-tabs > li > a:hover {
- border-color: #eeeeee #eeeeee #dddddd;
-}
-
-.nav-tabs > .active > a,
-.nav-tabs > .active > a:hover {
- color: #555555;
- cursor: default;
- background-color: #ffffff;
- border: 1px solid #ddd;
- border-bottom-color: transparent;
-}
-
-.nav-pills > li > a {
- padding-top: 8px;
- padding-bottom: 8px;
- margin-top: 2px;
- margin-bottom: 2px;
- -webkit-border-radius: 5px;
- -moz-border-radius: 5px;
- border-radius: 5px;
-}
-
-.nav-pills > .active > a,
-.nav-pills > .active > a:hover {
- color: #ffffff;
- background-color: #0088cc;
-}
-
-.nav-stacked > li {
- float: none;
-}
-
-.nav-stacked > li > a {
- margin-right: 0;
-}
-
-.nav-tabs.nav-stacked {
- border-bottom: 0;
-}
-
-.nav-tabs.nav-stacked > li > a {
- border: 1px solid #ddd;
- -webkit-border-radius: 0;
- -moz-border-radius: 0;
- border-radius: 0;
-}
-
-.nav-tabs.nav-stacked > li:first-child > a {
- -webkit-border-top-right-radius: 4px;
- border-top-right-radius: 4px;
- -webkit-border-top-left-radius: 4px;
- border-top-left-radius: 4px;
- -moz-border-radius-topright: 4px;
- -moz-border-radius-topleft: 4px;
-}
-
-.nav-tabs.nav-stacked > li:last-child > a {
- -webkit-border-bottom-right-radius: 4px;
- border-bottom-right-radius: 4px;
- -webkit-border-bottom-left-radius: 4px;
- border-bottom-left-radius: 4px;
- -moz-border-radius-bottomright: 4px;
- -moz-border-radius-bottomleft: 4px;
-}
-
-.nav-tabs.nav-stacked > li > a:hover {
- z-index: 2;
- border-color: #ddd;
-}
-
-.nav-pills.nav-stacked > li > a {
- margin-bottom: 3px;
-}
-
-.nav-pills.nav-stacked > li:last-child > a {
- margin-bottom: 1px;
-}
-
-.nav-tabs .dropdown-menu {
- -webkit-border-radius: 0 0 6px 6px;
- -moz-border-radius: 0 0 6px 6px;
- border-radius: 0 0 6px 6px;
-}
-
-.nav-pills .dropdown-menu {
- -webkit-border-radius: 6px;
- -moz-border-radius: 6px;
- border-radius: 6px;
-}
-
-.nav .dropdown-toggle .caret {
- margin-top: 6px;
- border-top-color: #0088cc;
- border-bottom-color: #0088cc;
-}
-
-.nav .dropdown-toggle:hover .caret {
- border-top-color: #005580;
- border-bottom-color: #005580;
-}
-
-/* move down carets for tabs */
-
-.nav-tabs .dropdown-toggle .caret {
- margin-top: 8px;
-}
-
-.nav .active .dropdown-toggle .caret {
- border-top-color: #fff;
- border-bottom-color: #fff;
-}
-
-.nav-tabs .active .dropdown-toggle .caret {
- border-top-color: #555555;
- border-bottom-color: #555555;
-}
-
-.nav > .dropdown.active > a:hover {
- cursor: pointer;
-}
-
-.nav-tabs .open .dropdown-toggle,
-.nav-pills .open .dropdown-toggle,
-.nav > li.dropdown.open.active > a:hover {
- color: #ffffff;
- background-color: #999999;
- border-color: #999999;
-}
-
-.nav li.dropdown.open .caret,
-.nav li.dropdown.open.active .caret,
-.nav li.dropdown.open a:hover .caret {
- border-top-color: #ffffff;
- border-bottom-color: #ffffff;
- opacity: 1;
- filter: alpha(opacity=100);
-}
-
-.tabs-stacked .open > a:hover {
- border-color: #999999;
-}
-
-.tabbable {
- *zoom: 1;
-}
-
-.tabbable:before,
-.tabbable:after {
- display: table;
- line-height: 0;
- content: "";
-}
-
-.tabbable:after {
- clear: both;
-}
-
-.tab-content {
- overflow: auto;
-}
-
-.tabs-below > .nav-tabs,
-.tabs-right > .nav-tabs,
-.tabs-left > .nav-tabs {
- border-bottom: 0;
-}
-
-.tab-content > .tab-pane,
-.pill-content > .pill-pane {
- display: none;
-}
-
-.tab-content > .active,
-.pill-content > .active {
- display: block;
-}
-
-.tabs-below > .nav-tabs {
- border-top: 1px solid #ddd;
-}
-
-.tabs-below > .nav-tabs > li {
- margin-top: -1px;
- margin-bottom: 0;
-}
-
-.tabs-below > .nav-tabs > li > a {
- -webkit-border-radius: 0 0 4px 4px;
- -moz-border-radius: 0 0 4px 4px;
- border-radius: 0 0 4px 4px;
-}
-
-.tabs-below > .nav-tabs > li > a:hover {
- border-top-color: #ddd;
- border-bottom-color: transparent;
-}
-
-.tabs-below > .nav-tabs > .active > a,
-.tabs-below > .nav-tabs > .active > a:hover {
- border-color: transparent #ddd #ddd #ddd;
-}
-
-.tabs-left > .nav-tabs > li,
-.tabs-right > .nav-tabs > li {
- float: none;
-}
-
-.tabs-left > .nav-tabs > li > a,
-.tabs-right > .nav-tabs > li > a {
- min-width: 74px;
- margin-right: 0;
- margin-bottom: 3px;
-}
-
-.tabs-left > .nav-tabs {
- float: left;
- margin-right: 19px;
- border-right: 1px solid #ddd;
-}
-
-.tabs-left > .nav-tabs > li > a {
- margin-right: -1px;
- -webkit-border-radius: 4px 0 0 4px;
- -moz-border-radius: 4px 0 0 4px;
- border-radius: 4px 0 0 4px;
-}
-
-.tabs-left > .nav-tabs > li > a:hover {
- border-color: #eeeeee #dddddd #eeeeee #eeeeee;
-}
-
-.tabs-left > .nav-tabs .active > a,
-.tabs-left > .nav-tabs .active > a:hover {
- border-color: #ddd transparent #ddd #ddd;
- *border-right-color: #ffffff;
-}
-
-.tabs-right > .nav-tabs {
- float: right;
- margin-left: 19px;
- border-left: 1px solid #ddd;
-}
-
-.tabs-right > .nav-tabs > li > a {
- margin-left: -1px;
- -webkit-border-radius: 0 4px 4px 0;
- -moz-border-radius: 0 4px 4px 0;
- border-radius: 0 4px 4px 0;
-}
-
-.tabs-right > .nav-tabs > li > a:hover {
- border-color: #eeeeee #eeeeee #eeeeee #dddddd;
-}
-
-.tabs-right > .nav-tabs .active > a,
-.tabs-right > .nav-tabs .active > a:hover {
- border-color: #ddd #ddd #ddd transparent;
- *border-left-color: #ffffff;
-}
-
-.nav > .disabled > a {
- color: #999999;
-}
-
-.nav > .disabled > a:hover {
- text-decoration: none;
- cursor: default;
- background-color: transparent;
-}
-
-.navbar {
- *position: relative;
- *z-index: 2;
- margin-bottom: 20px;
- overflow: visible;
- color: #777777;
-}
-
-.navbar-inner {
- min-height: 40px;
- padding-right: 20px;
- padding-left: 20px;
- background-color: #fafafa;
- background-image: -moz-linear-gradient(top, #ffffff, #f2f2f2);
- background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#f2f2f2));
- background-image: -webkit-linear-gradient(top, #ffffff, #f2f2f2);
- background-image: -o-linear-gradient(top, #ffffff, #f2f2f2);
- background-image: linear-gradient(to bottom, #ffffff, #f2f2f2);
- background-repeat: repeat-x;
- border: 1px solid #d4d4d4;
- -webkit-border-radius: 4px;
- -moz-border-radius: 4px;
- border-radius: 4px;
- filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff2f2f2', GradientType=0);
- *zoom: 1;
- -webkit-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.065);
- -moz-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.065);
- box-shadow: 0 1px 4px rgba(0, 0, 0, 0.065);
-}
-
-.navbar-inner:before,
-.navbar-inner:after {
- display: table;
- line-height: 0;
- content: "";
-}
-
-.navbar-inner:after {
- clear: both;
-}
-
-.navbar .container {
- width: auto;
-}
-
-.nav-collapse.collapse {
- height: auto;
- overflow: visible;
-}
-
-.navbar .brand {
- display: block;
- float: left;
- padding: 10px 20px 10px;
- margin-left: -20px;
- font-size: 20px;
- font-weight: 200;
- color: #777777;
- text-shadow: 0 1px 0 #ffffff;
-}
-
-.navbar .brand:hover {
- text-decoration: none;
-}
-
-.navbar-text {
- margin-bottom: 0;
- line-height: 40px;
-}
-
-.navbar-link {
- color: #777777;
-}
-
-.navbar-link:hover {
- color: #333333;
-}
-
-.navbar .divider-vertical {
- height: 40px;
- margin: 0 9px;
- border-right: 1px solid #ffffff;
- border-left: 1px solid #f2f2f2;
-}
-
-.navbar .btn,
-.navbar .btn-group {
- margin-top: 5px;
-}
-
-.navbar .btn-group .btn,
-.navbar .input-prepend .btn,
-.navbar .input-append .btn {
- margin-top: 0;
-}
-
-.navbar-form {
- margin-bottom: 0;
- *zoom: 1;
-}
-
-.navbar-form:before,
-.navbar-form:after {
- display: table;
- line-height: 0;
- content: "";
-}
-
-.navbar-form:after {
- clear: both;
-}
-
-.navbar-form input,
-.navbar-form select,
-.navbar-form .radio,
-.navbar-form .checkbox {
- margin-top: 5px;
-}
-
-.navbar-form input,
-.navbar-form select,
-.navbar-form .btn {
- display: inline-block;
- margin-bottom: 0;
-}
-
-.navbar-form input[type="image"],
-.navbar-form input[type="checkbox"],
-.navbar-form input[type="radio"] {
- margin-top: 3px;
-}
-
-.navbar-form .input-append,
-.navbar-form .input-prepend {
- margin-top: 6px;
- white-space: nowrap;
-}
-
-.navbar-form .input-append input,
-.navbar-form .input-prepend input {
- margin-top: 0;
-}
-
-.navbar-search {
- position: relative;
- float: left;
- margin-top: 5px;
- margin-bottom: 0;
-}
-
-.navbar-search .search-query {
- padding: 4px 14px;
- margin-bottom: 0;
- font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
- font-size: 13px;
- font-weight: normal;
- line-height: 1;
- -webkit-border-radius: 15px;
- -moz-border-radius: 15px;
- border-radius: 15px;
-}
-
-.navbar-static-top {
- position: static;
- margin-bottom: 0;
-}
-
-.navbar-static-top .navbar-inner {
- -webkit-border-radius: 0;
- -moz-border-radius: 0;
- border-radius: 0;
-}
-
-.navbar-fixed-top,
-.navbar-fixed-bottom {
- position: fixed;
- right: 0;
- left: 0;
- z-index: 1030;
- margin-bottom: 0;
-}
-
-.navbar-fixed-top .navbar-inner,
-.navbar-static-top .navbar-inner {
- border-width: 0 0 1px;
-}
-
-.navbar-fixed-bottom .navbar-inner {
- border-width: 1px 0 0;
-}
-
-.navbar-fixed-top .navbar-inner,
-.navbar-fixed-bottom .navbar-inner {
- padding-right: 0;
- padding-left: 0;
- -webkit-border-radius: 0;
- -moz-border-radius: 0;
- border-radius: 0;
-}
-
-.navbar-static-top .container,
-.navbar-fixed-top .container,
-.navbar-fixed-bottom .container {
- width: 940px;
-}
-
-.navbar-fixed-top {
- top: 0;
-}
-
-.navbar-fixed-top .navbar-inner,
-.navbar-static-top .navbar-inner {
- -webkit-box-shadow: 0 1px 10px rgba(0, 0, 0, 0.1);
- -moz-box-shadow: 0 1px 10px rgba(0, 0, 0, 0.1);
- box-shadow: 0 1px 10px rgba(0, 0, 0, 0.1);
-}
-
-.navbar-fixed-bottom {
- bottom: 0;
-}
-
-.navbar-fixed-bottom .navbar-inner {
- -webkit-box-shadow: 0 -1px 10px rgba(0, 0, 0, 0.1);
- -moz-box-shadow: 0 -1px 10px rgba(0, 0, 0, 0.1);
- box-shadow: 0 -1px 10px rgba(0, 0, 0, 0.1);
-}
-
-.navbar .nav {
- position: relative;
- left: 0;
- display: block;
- float: left;
- margin: 0 10px 0 0;
-}
-
-.navbar .nav.pull-right {
- float: right;
- margin-right: 0;
-}
-
-.navbar .nav > li {
- float: left;
-}
-
-.navbar .nav > li > a {
- float: none;
- padding: 10px 15px 10px;
- color: #777777;
- text-decoration: none;
- text-shadow: 0 1px 0 #ffffff;
-}
-
-.navbar .nav .dropdown-toggle .caret {
- margin-top: 8px;
-}
-
-.navbar .nav > li > a:focus,
-.navbar .nav > li > a:hover {
- color: #333333;
- text-decoration: none;
- background-color: transparent;
-}
-
-.navbar .nav > .active > a,
-.navbar .nav > .active > a:hover,
-.navbar .nav > .active > a:focus {
- color: #555555;
- text-decoration: none;
- background-color: #e5e5e5;
- -webkit-box-shadow: inset 0 3px 8px rgba(0, 0, 0, 0.125);
- -moz-box-shadow: inset 0 3px 8px rgba(0, 0, 0, 0.125);
- box-shadow: inset 0 3px 8px rgba(0, 0, 0, 0.125);
-}
-
-.navbar .btn-navbar {
- display: none;
- float: right;
- padding: 7px 10px;
- margin-right: 5px;
- margin-left: 5px;
- color: #ffffff;
- text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
- background-color: #ededed;
- *background-color: #e5e5e5;
- background-image: -moz-linear-gradient(top, #f2f2f2, #e5e5e5);
- background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#f2f2f2), to(#e5e5e5));
- background-image: -webkit-linear-gradient(top, #f2f2f2, #e5e5e5);
- background-image: -o-linear-gradient(top, #f2f2f2, #e5e5e5);
- background-image: linear-gradient(to bottom, #f2f2f2, #e5e5e5);
- background-repeat: repeat-x;
- border-color: #e5e5e5 #e5e5e5 #bfbfbf;
- border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
- filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2f2f2', endColorstr='#ffe5e5e5', GradientType=0);
- filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
- -webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.075);
- -moz-box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.075);
- box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.1), 0 1px 0 rgba(255, 255, 255, 0.075);
-}
-
-.navbar .btn-navbar:hover,
-.navbar .btn-navbar:active,
-.navbar .btn-navbar.active,
-.navbar .btn-navbar.disabled,
-.navbar .btn-navbar[disabled] {
- color: #ffffff;
- background-color: #e5e5e5;
- *background-color: #d9d9d9;
-}
-
-.navbar .btn-navbar:active,
-.navbar .btn-navbar.active {
- background-color: #cccccc \9;
-}
-
-.navbar .btn-navbar .icon-bar {
- display: block;
- width: 18px;
- height: 2px;
- background-color: #f5f5f5;
- -webkit-border-radius: 1px;
- -moz-border-radius: 1px;
- border-radius: 1px;
- -webkit-box-shadow: 0 1px 0 rgba(0, 0, 0, 0.25);
- -moz-box-shadow: 0 1px 0 rgba(0, 0, 0, 0.25);
- box-shadow: 0 1px 0 rgba(0, 0, 0, 0.25);
-}
-
-.btn-navbar .icon-bar + .icon-bar {
- margin-top: 3px;
-}
-
-.navbar .nav > li > .dropdown-menu:before {
- position: absolute;
- top: -7px;
- left: 9px;
- display: inline-block;
- border-right: 7px solid transparent;
- border-bottom: 7px solid #ccc;
- border-left: 7px solid transparent;
- border-bottom-color: rgba(0, 0, 0, 0.2);
- content: '';
-}
-
-.navbar .nav > li > .dropdown-menu:after {
- position: absolute;
- top: -6px;
- left: 10px;
- display: inline-block;
- border-right: 6px solid transparent;
- border-bottom: 6px solid #ffffff;
- border-left: 6px solid transparent;
- content: '';
-}
-
-.navbar-fixed-bottom .nav > li > .dropdown-menu:before {
- top: auto;
- bottom: -7px;
- border-top: 7px solid #ccc;
- border-bottom: 0;
- border-top-color: rgba(0, 0, 0, 0.2);
-}
-
-.navbar-fixed-bottom .nav > li > .dropdown-menu:after {
- top: auto;
- bottom: -6px;
- border-top: 6px solid #ffffff;
- border-bottom: 0;
-}
-
-.navbar .nav li.dropdown.open > .dropdown-toggle,
-.navbar .nav li.dropdown.active > .dropdown-toggle,
-.navbar .nav li.dropdown.open.active > .dropdown-toggle {
- color: #555555;
- background-color: #e5e5e5;
-}
-
-.navbar .nav li.dropdown > .dropdown-toggle .caret {
- border-top-color: #777777;
- border-bottom-color: #777777;
-}
-
-.navbar .nav li.dropdown.open > .dropdown-toggle .caret,
-.navbar .nav li.dropdown.active > .dropdown-toggle .caret,
-.navbar .nav li.dropdown.open.active > .dropdown-toggle .caret {
- border-top-color: #555555;
- border-bottom-color: #555555;
-}
-
-.navbar .pull-right > li > .dropdown-menu,
-.navbar .nav > li > .dropdown-menu.pull-right {
- right: 0;
- left: auto;
-}
-
-.navbar .pull-right > li > .dropdown-menu:before,
-.navbar .nav > li > .dropdown-menu.pull-right:before {
- right: 12px;
- left: auto;
-}
-
-.navbar .pull-right > li > .dropdown-menu:after,
-.navbar .nav > li > .dropdown-menu.pull-right:after {
- right: 13px;
- left: auto;
-}
-
-.navbar .pull-right > li > .dropdown-menu .dropdown-menu,
-.navbar .nav > li > .dropdown-menu.pull-right .dropdown-menu {
- right: 100%;
- left: auto;
- margin-right: -1px;
- margin-left: 0;
- -webkit-border-radius: 6px 0 6px 6px;
- -moz-border-radius: 6px 0 6px 6px;
- border-radius: 6px 0 6px 6px;
-}
-
-.navbar-inverse {
- color: #999999;
-}
-
-.navbar-inverse .navbar-inner {
- background-color: #1b1b1b;
- background-image: -moz-linear-gradient(top, #222222, #111111);
- background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#222222), to(#111111));
- background-image: -webkit-linear-gradient(top, #222222, #111111);
- background-image: -o-linear-gradient(top, #222222, #111111);
- background-image: linear-gradient(to bottom, #222222, #111111);
- background-repeat: repeat-x;
- border-color: #252525;
- filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff222222', endColorstr='#ff111111', GradientType=0);
-}
-
-.navbar-inverse .brand,
-.navbar-inverse .nav > li > a {
- color: #999999;
- text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
-}
-
-.navbar-inverse .brand:hover,
-.navbar-inverse .nav > li > a:hover {
- color: #ffffff;
-}
-
-.navbar-inverse .nav > li > a:focus,
-.navbar-inverse .nav > li > a:hover {
- color: #ffffff;
- background-color: transparent;
-}
-
-.navbar-inverse .nav .active > a,
-.navbar-inverse .nav .active > a:hover,
-.navbar-inverse .nav .active > a:focus {
- color: #ffffff;
- background-color: #111111;
-}
-
-.navbar-inverse .navbar-link {
- color: #999999;
-}
-
-.navbar-inverse .navbar-link:hover {
- color: #ffffff;
-}
-
-.navbar-inverse .divider-vertical {
- border-right-color: #222222;
- border-left-color: #111111;
-}
-
-.navbar-inverse .nav li.dropdown.open > .dropdown-toggle,
-.navbar-inverse .nav li.dropdown.active > .dropdown-toggle,
-.navbar-inverse .nav li.dropdown.open.active > .dropdown-toggle {
- color: #ffffff;
- background-color: #111111;
-}
-
-.navbar-inverse .nav li.dropdown > .dropdown-toggle .caret {
- border-top-color: #999999;
- border-bottom-color: #999999;
-}
-
-.navbar-inverse .nav li.dropdown.open > .dropdown-toggle .caret,
-.navbar-inverse .nav li.dropdown.active > .dropdown-toggle .caret,
-.navbar-inverse .nav li.dropdown.open.active > .dropdown-toggle .caret {
- border-top-color: #ffffff;
- border-bottom-color: #ffffff;
-}
-
-.navbar-inverse .navbar-search .search-query {
- color: #ffffff;
- background-color: #515151;
- border-color: #111111;
- -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 0 rgba(255, 255, 255, 0.15);
- -moz-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 0 rgba(255, 255, 255, 0.15);
- box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1), 0 1px 0 rgba(255, 255, 255, 0.15);
- -webkit-transition: none;
- -moz-transition: none;
- -o-transition: none;
- transition: none;
-}
-
-.navbar-inverse .navbar-search .search-query:-moz-placeholder {
- color: #cccccc;
-}
-
-.navbar-inverse .navbar-search .search-query:-ms-input-placeholder {
- color: #cccccc;
-}
-
-.navbar-inverse .navbar-search .search-query::-webkit-input-placeholder {
- color: #cccccc;
-}
-
-.navbar-inverse .navbar-search .search-query:focus,
-.navbar-inverse .navbar-search .search-query.focused {
- padding: 5px 15px;
- color: #333333;
- text-shadow: 0 1px 0 #ffffff;
- background-color: #ffffff;
- border: 0;
- outline: 0;
- -webkit-box-shadow: 0 0 3px rgba(0, 0, 0, 0.15);
- -moz-box-shadow: 0 0 3px rgba(0, 0, 0, 0.15);
- box-shadow: 0 0 3px rgba(0, 0, 0, 0.15);
-}
-
-.navbar-inverse .btn-navbar {
- color: #ffffff;
- text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
- background-color: #0e0e0e;
- *background-color: #040404;
- background-image: -moz-linear-gradient(top, #151515, #040404);
- background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#151515), to(#040404));
- background-image: -webkit-linear-gradient(top, #151515, #040404);
- background-image: -o-linear-gradient(top, #151515, #040404);
- background-image: linear-gradient(to bottom, #151515, #040404);
- background-repeat: repeat-x;
- border-color: #040404 #040404 #000000;
- border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
- filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff151515', endColorstr='#ff040404', GradientType=0);
- filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
-}
-
-.navbar-inverse .btn-navbar:hover,
-.navbar-inverse .btn-navbar:active,
-.navbar-inverse .btn-navbar.active,
-.navbar-inverse .btn-navbar.disabled,
-.navbar-inverse .btn-navbar[disabled] {
- color: #ffffff;
- background-color: #040404;
- *background-color: #000000;
-}
-
-.navbar-inverse .btn-navbar:active,
-.navbar-inverse .btn-navbar.active {
- background-color: #000000 \9;
-}
-
-.breadcrumb {
- padding: 8px 15px;
- margin: 0 0 20px;
- list-style: none;
- background-color: #f5f5f5;
- -webkit-border-radius: 4px;
- -moz-border-radius: 4px;
- border-radius: 4px;
-}
-
-.breadcrumb li {
- display: inline-block;
- *display: inline;
- text-shadow: 0 1px 0 #ffffff;
- *zoom: 1;
-}
-
-.breadcrumb .divider {
- padding: 0 5px;
- color: #ccc;
-}
-
-.breadcrumb .active {
- color: #999999;
-}
-
-.pagination {
- margin: 20px 0;
-}
-
-.pagination ul {
- display: inline-block;
- *display: inline;
- margin-bottom: 0;
- margin-left: 0;
- -webkit-border-radius: 4px;
- -moz-border-radius: 4px;
- border-radius: 4px;
- *zoom: 1;
- -webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
- -moz-box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
- box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
-}
-
-.pagination ul > li {
- display: inline;
-}
-
-.pagination ul > li > a,
-.pagination ul > li > span {
- float: left;
- padding: 4px 12px;
- line-height: 20px;
- text-decoration: none;
- background-color: #ffffff;
- border: 1px solid #dddddd;
- border-left-width: 0;
-}
-
-.pagination ul > li > a:hover,
-.pagination ul > .active > a,
-.pagination ul > .active > span {
- background-color: #f5f5f5;
-}
-
-.pagination ul > .active > a,
-.pagination ul > .active > span {
- color: #999999;
- cursor: default;
-}
-
-.pagination ul > .disabled > span,
-.pagination ul > .disabled > a,
-.pagination ul > .disabled > a:hover {
- color: #999999;
- cursor: default;
- background-color: transparent;
-}
-
-.pagination ul > li:first-child > a,
-.pagination ul > li:first-child > span {
- border-left-width: 1px;
- -webkit-border-bottom-left-radius: 4px;
- border-bottom-left-radius: 4px;
- -webkit-border-top-left-radius: 4px;
- border-top-left-radius: 4px;
- -moz-border-radius-bottomleft: 4px;
- -moz-border-radius-topleft: 4px;
-}
-
-.pagination ul > li:last-child > a,
-.pagination ul > li:last-child > span {
- -webkit-border-top-right-radius: 4px;
- border-top-right-radius: 4px;
- -webkit-border-bottom-right-radius: 4px;
- border-bottom-right-radius: 4px;
- -moz-border-radius-topright: 4px;
- -moz-border-radius-bottomright: 4px;
-}
-
-.pagination-centered {
- text-align: center;
-}
-
-.pagination-right {
- text-align: right;
-}
-
-.pagination-large ul > li > a,
-.pagination-large ul > li > span {
- padding: 11px 19px;
- font-size: 17.5px;
-}
-
-.pagination-large ul > li:first-child > a,
-.pagination-large ul > li:first-child > span {
- -webkit-border-bottom-left-radius: 6px;
- border-bottom-left-radius: 6px;
- -webkit-border-top-left-radius: 6px;
- border-top-left-radius: 6px;
- -moz-border-radius-bottomleft: 6px;
- -moz-border-radius-topleft: 6px;
-}
-
-.pagination-large ul > li:last-child > a,
-.pagination-large ul > li:last-child > span {
- -webkit-border-top-right-radius: 6px;
- border-top-right-radius: 6px;
- -webkit-border-bottom-right-radius: 6px;
- border-bottom-right-radius: 6px;
- -moz-border-radius-topright: 6px;
- -moz-border-radius-bottomright: 6px;
-}
-
-.pagination-mini ul > li:first-child > a,
-.pagination-small ul > li:first-child > a,
-.pagination-mini ul > li:first-child > span,
-.pagination-small ul > li:first-child > span {
- -webkit-border-bottom-left-radius: 3px;
- border-bottom-left-radius: 3px;
- -webkit-border-top-left-radius: 3px;
- border-top-left-radius: 3px;
- -moz-border-radius-bottomleft: 3px;
- -moz-border-radius-topleft: 3px;
-}
-
-.pagination-mini ul > li:last-child > a,
-.pagination-small ul > li:last-child > a,
-.pagination-mini ul > li:last-child > span,
-.pagination-small ul > li:last-child > span {
- -webkit-border-top-right-radius: 3px;
- border-top-right-radius: 3px;
- -webkit-border-bottom-right-radius: 3px;
- border-bottom-right-radius: 3px;
- -moz-border-radius-topright: 3px;
- -moz-border-radius-bottomright: 3px;
-}
-
-.pagination-small ul > li > a,
-.pagination-small ul > li > span {
- padding: 2px 10px;
- font-size: 11.9px;
-}
-
-.pagination-mini ul > li > a,
-.pagination-mini ul > li > span {
- padding: 1px 6px;
- font-size: 10.5px;
-}
-
-.pager {
- margin: 20px 0;
- text-align: center;
- list-style: none;
- *zoom: 1;
-}
-
-.pager:before,
-.pager:after {
- display: table;
- line-height: 0;
- content: "";
-}
-
-.pager:after {
- clear: both;
-}
-
-.pager li {
- display: inline;
-}
-
-.pager li > a,
-.pager li > span {
- display: inline-block;
- padding: 5px 14px;
- background-color: #fff;
- border: 1px solid #ddd;
- -webkit-border-radius: 15px;
- -moz-border-radius: 15px;
- border-radius: 15px;
-}
-
-.pager li > a:hover {
- text-decoration: none;
- background-color: #f5f5f5;
-}
-
-.pager .next > a,
-.pager .next > span {
- float: right;
-}
-
-.pager .previous > a,
-.pager .previous > span {
- float: left;
-}
-
-.pager .disabled > a,
-.pager .disabled > a:hover,
-.pager .disabled > span {
- color: #999999;
- cursor: default;
- background-color: #fff;
-}
-
-.modal-backdrop {
- position: fixed;
- top: 0;
- right: 0;
- bottom: 0;
- left: 0;
- z-index: 1040;
- background-color: #000000;
-}
-
-.modal-backdrop.fade {
- opacity: 0;
-}
-
-.modal-backdrop,
-.modal-backdrop.fade.in {
- opacity: 0.8;
- filter: alpha(opacity=80);
-}
-
-.modal {
- position: fixed;
- top: 50%;
- left: 50%;
- z-index: 1050;
- width: 560px;
- margin: -250px 0 0 -280px;
- background-color: #ffffff;
- border: 1px solid #999;
- border: 1px solid rgba(0, 0, 0, 0.3);
- *border: 1px solid #999;
- -webkit-border-radius: 6px;
- -moz-border-radius: 6px;
- border-radius: 6px;
- outline: none;
- -webkit-box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3);
- -moz-box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3);
- box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3);
- -webkit-background-clip: padding-box;
- -moz-background-clip: padding-box;
- background-clip: padding-box;
-}
-
-.modal.fade {
- top: -25%;
- -webkit-transition: opacity 0.3s linear, top 0.3s ease-out;
- -moz-transition: opacity 0.3s linear, top 0.3s ease-out;
- -o-transition: opacity 0.3s linear, top 0.3s ease-out;
- transition: opacity 0.3s linear, top 0.3s ease-out;
-}
-
-.modal.fade.in {
- top: 50%;
-}
-
-.modal-header {
- padding: 9px 15px;
- border-bottom: 1px solid #eee;
-}
-
-.modal-header .close {
- margin-top: 2px;
-}
-
-.modal-header h3 {
- margin: 0;
- line-height: 30px;
-}
-
-.modal-body {
- max-height: 400px;
- padding: 15px;
- overflow-y: auto;
-}
-
-.modal-form {
- margin-bottom: 0;
-}
-
-.modal-footer {
- padding: 14px 15px 15px;
- margin-bottom: 0;
- text-align: right;
- background-color: #f5f5f5;
- border-top: 1px solid #ddd;
- -webkit-border-radius: 0 0 6px 6px;
- -moz-border-radius: 0 0 6px 6px;
- border-radius: 0 0 6px 6px;
- *zoom: 1;
- -webkit-box-shadow: inset 0 1px 0 #ffffff;
- -moz-box-shadow: inset 0 1px 0 #ffffff;
- box-shadow: inset 0 1px 0 #ffffff;
-}
-
-.modal-footer:before,
-.modal-footer:after {
- display: table;
- line-height: 0;
- content: "";
-}
-
-.modal-footer:after {
- clear: both;
-}
-
-.modal-footer .btn + .btn {
- margin-bottom: 0;
- margin-left: 5px;
-}
-
-.modal-footer .btn-group .btn + .btn {
- margin-left: -1px;
-}
-
-.modal-footer .btn-block + .btn-block {
- margin-left: 0;
-}
-
-.tooltip {
- position: absolute;
- z-index: 1030;
- display: block;
- padding: 5px;
- font-size: 11px;
- opacity: 0;
- filter: alpha(opacity=0);
- visibility: visible;
-}
-
-.tooltip.in {
- opacity: 0.8;
- filter: alpha(opacity=80);
-}
-
-.tooltip.top {
- margin-top: -3px;
-}
-
-.tooltip.right {
- margin-left: 3px;
-}
-
-.tooltip.bottom {
- margin-top: 3px;
-}
-
-.tooltip.left {
- margin-left: -3px;
-}
-
-.tooltip-inner {
- max-width: 200px;
- padding: 3px 8px;
- color: #ffffff;
- text-align: center;
- text-decoration: none;
- background-color: #000000;
- -webkit-border-radius: 4px;
- -moz-border-radius: 4px;
- border-radius: 4px;
-}
-
-.tooltip-arrow {
- position: absolute;
- width: 0;
- height: 0;
- border-color: transparent;
- border-style: solid;
-}
-
-.tooltip.top .tooltip-arrow {
- bottom: 0;
- left: 50%;
- margin-left: -5px;
- border-top-color: #000000;
- border-width: 5px 5px 0;
-}
-
-.tooltip.right .tooltip-arrow {
- top: 50%;
- left: 0;
- margin-top: -5px;
- border-right-color: #000000;
- border-width: 5px 5px 5px 0;
-}
-
-.tooltip.left .tooltip-arrow {
- top: 50%;
- right: 0;
- margin-top: -5px;
- border-left-color: #000000;
- border-width: 5px 0 5px 5px;
-}
-
-.tooltip.bottom .tooltip-arrow {
- top: 0;
- left: 50%;
- margin-left: -5px;
- border-bottom-color: #000000;
- border-width: 0 5px 5px;
-}
-
-.popover {
- position: absolute;
- top: 0;
- left: 0;
- z-index: 1010;
- display: none;
- width: 236px;
- padding: 1px;
- background-color: #ffffff;
- border: 1px solid #ccc;
- border: 1px solid rgba(0, 0, 0, 0.2);
- -webkit-border-radius: 6px;
- -moz-border-radius: 6px;
- border-radius: 6px;
- -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
- -moz-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
- box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
- -webkit-background-clip: padding-box;
- -moz-background-clip: padding;
- background-clip: padding-box;
-}
-
-.popover.top {
- margin-top: -10px;
-}
-
-.popover.right {
- margin-left: 10px;
-}
-
-.popover.bottom {
- margin-top: 10px;
-}
-
-.popover.left {
- margin-left: -10px;
-}
-
-.popover-title {
- padding: 8px 14px;
- margin: 0;
- font-size: 14px;
- font-weight: normal;
- line-height: 18px;
- background-color: #f7f7f7;
- border-bottom: 1px solid #ebebeb;
- -webkit-border-radius: 5px 5px 0 0;
- -moz-border-radius: 5px 5px 0 0;
- border-radius: 5px 5px 0 0;
-}
-
-.popover-content {
- padding: 9px 14px;
-}
-
-.popover-content p,
-.popover-content ul,
-.popover-content ol {
- margin-bottom: 0;
-}
-
-.popover .arrow,
-.popover .arrow:after {
- position: absolute;
- display: inline-block;
- width: 0;
- height: 0;
- border-color: transparent;
- border-style: solid;
-}
-
-.popover .arrow:after {
- z-index: -1;
- content: "";
-}
-
-.popover.top .arrow {
- bottom: -10px;
- left: 50%;
- margin-left: -10px;
- border-top-color: #ffffff;
- border-width: 10px 10px 0;
-}
-
-.popover.top .arrow:after {
- bottom: -1px;
- left: -11px;
- border-top-color: rgba(0, 0, 0, 0.25);
- border-width: 11px 11px 0;
-}
-
-.popover.right .arrow {
- top: 50%;
- left: -10px;
- margin-top: -10px;
- border-right-color: #ffffff;
- border-width: 10px 10px 10px 0;
-}
-
-.popover.right .arrow:after {
- bottom: -11px;
- left: -1px;
- border-right-color: rgba(0, 0, 0, 0.25);
- border-width: 11px 11px 11px 0;
-}
-
-.popover.bottom .arrow {
- top: -10px;
- left: 50%;
- margin-left: -10px;
- border-bottom-color: #ffffff;
- border-width: 0 10px 10px;
-}
-
-.popover.bottom .arrow:after {
- top: -1px;
- left: -11px;
- border-bottom-color: rgba(0, 0, 0, 0.25);
- border-width: 0 11px 11px;
-}
-
-.popover.left .arrow {
- top: 50%;
- right: -10px;
- margin-top: -10px;
- border-left-color: #ffffff;
- border-width: 10px 0 10px 10px;
-}
-
-.popover.left .arrow:after {
- right: -1px;
- bottom: -11px;
- border-left-color: rgba(0, 0, 0, 0.25);
- border-width: 11px 0 11px 11px;
-}
-
-.thumbnails {
- margin-left: -20px;
- list-style: none;
- *zoom: 1;
-}
-
-.thumbnails:before,
-.thumbnails:after {
- display: table;
- line-height: 0;
- content: "";
-}
-
-.thumbnails:after {
- clear: both;
-}
-
-.row-fluid .thumbnails {
- margin-left: 0;
-}
-
-.thumbnails > li {
- float: left;
- margin-bottom: 20px;
- margin-left: 20px;
-}
-
-.thumbnail {
- display: block;
- padding: 4px;
- line-height: 20px;
- border: 1px solid #ddd;
- -webkit-border-radius: 4px;
- -moz-border-radius: 4px;
- border-radius: 4px;
- -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.055);
- -moz-box-shadow: 0 1px 3px rgba(0, 0, 0, 0.055);
- box-shadow: 0 1px 3px rgba(0, 0, 0, 0.055);
- -webkit-transition: all 0.2s ease-in-out;
- -moz-transition: all 0.2s ease-in-out;
- -o-transition: all 0.2s ease-in-out;
- transition: all 0.2s ease-in-out;
-}
-
-a.thumbnail:hover {
- border-color: #0088cc;
- -webkit-box-shadow: 0 1px 4px rgba(0, 105, 214, 0.25);
- -moz-box-shadow: 0 1px 4px rgba(0, 105, 214, 0.25);
- box-shadow: 0 1px 4px rgba(0, 105, 214, 0.25);
-}
-
-.thumbnail > img {
- display: block;
- max-width: 100%;
- margin-right: auto;
- margin-left: auto;
-}
-
-.thumbnail .caption {
- padding: 9px;
- color: #555555;
-}
-
-.media,
-.media-body {
- overflow: hidden;
- *overflow: visible;
- zoom: 1;
-}
-
-.media,
-.media .media {
- margin-top: 15px;
-}
-
-.media:first-child {
- margin-top: 0;
-}
-
-.media-object {
- display: block;
-}
-
-.media-heading {
- margin: 0 0 5px;
-}
-
-.media .pull-left {
- margin-right: 10px;
-}
-
-.media .pull-right {
- margin-left: 10px;
-}
-
-.media-list {
- margin-left: 0;
- list-style: none;
-}
-
-.label,
-.badge {
- display: inline-block;
- padding: 2px 4px;
- font-size: 11.844px;
- font-weight: bold;
- line-height: 14px;
- color: #ffffff;
- text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
- white-space: nowrap;
- vertical-align: baseline;
- background-color: #999999;
-}
-
-.label {
- -webkit-border-radius: 3px;
- -moz-border-radius: 3px;
- border-radius: 3px;
-}
-
-.badge {
- padding-right: 9px;
- padding-left: 9px;
- -webkit-border-radius: 9px;
- -moz-border-radius: 9px;
- border-radius: 9px;
-}
-
-a.label:hover,
-a.badge:hover {
- color: #ffffff;
- text-decoration: none;
- cursor: pointer;
-}
-
-.label-important,
-.badge-important {
- background-color: #b94a48;
-}
-
-.label-important[href],
-.badge-important[href] {
- background-color: #953b39;
-}
-
-.label-warning,
-.badge-warning {
- background-color: #f89406;
-}
-
-.label-warning[href],
-.badge-warning[href] {
- background-color: #c67605;
-}
-
-.label-success,
-.badge-success {
- background-color: #468847;
-}
-
-.label-success[href],
-.badge-success[href] {
- background-color: #356635;
-}
-
-.label-info,
-.badge-info {
- background-color: #3a87ad;
-}
-
-.label-info[href],
-.badge-info[href] {
- background-color: #2d6987;
-}
-
-.label-inverse,
-.badge-inverse {
- background-color: #333333;
-}
-
-.label-inverse[href],
-.badge-inverse[href] {
- background-color: #1a1a1a;
-}
-
-.btn .label,
-.btn .badge {
- position: relative;
- top: -1px;
-}
-
-.btn-mini .label,
-.btn-mini .badge {
- top: 0;
-}
-
-@-webkit-keyframes progress-bar-stripes {
- from {
- background-position: 40px 0;
- }
- to {
- background-position: 0 0;
- }
-}
-
-@-moz-keyframes progress-bar-stripes {
- from {
- background-position: 40px 0;
- }
- to {
- background-position: 0 0;
- }
-}
-
-@-ms-keyframes progress-bar-stripes {
- from {
- background-position: 40px 0;
- }
- to {
- background-position: 0 0;
- }
-}
-
-@-o-keyframes progress-bar-stripes {
- from {
- background-position: 0 0;
- }
- to {
- background-position: 40px 0;
- }
-}
-
-@keyframes progress-bar-stripes {
- from {
- background-position: 40px 0;
- }
- to {
- background-position: 0 0;
- }
-}
-
-.progress {
- height: 20px;
- margin-bottom: 20px;
- overflow: hidden;
- background-color: #f7f7f7;
- background-image: -moz-linear-gradient(top, #f5f5f5, #f9f9f9);
- background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#f5f5f5), to(#f9f9f9));
- background-image: -webkit-linear-gradient(top, #f5f5f5, #f9f9f9);
- background-image: -o-linear-gradient(top, #f5f5f5, #f9f9f9);
- background-image: linear-gradient(to bottom, #f5f5f5, #f9f9f9);
- background-repeat: repeat-x;
- -webkit-border-radius: 4px;
- -moz-border-radius: 4px;
- border-radius: 4px;
- filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#fff9f9f9', GradientType=0);
- -webkit-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
- -moz-box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
- box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.1);
-}
-
-.progress .bar {
- float: left;
- width: 0;
- height: 100%;
- font-size: 12px;
- color: #ffffff;
- text-align: center;
- text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
- background-color: #0e90d2;
- background-image: -moz-linear-gradient(top, #149bdf, #0480be);
- background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#149bdf), to(#0480be));
- background-image: -webkit-linear-gradient(top, #149bdf, #0480be);
- background-image: -o-linear-gradient(top, #149bdf, #0480be);
- background-image: linear-gradient(to bottom, #149bdf, #0480be);
- background-repeat: repeat-x;
- filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff149bdf', endColorstr='#ff0480be', GradientType=0);
- -webkit-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);
- -moz-box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);
- box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.15);
- -webkit-box-sizing: border-box;
- -moz-box-sizing: border-box;
- box-sizing: border-box;
- -webkit-transition: width 0.6s ease;
- -moz-transition: width 0.6s ease;
- -o-transition: width 0.6s ease;
- transition: width 0.6s ease;
-}
-
-.progress .bar + .bar {
- -webkit-box-shadow: inset 1px 0 0 rgba(0, 0, 0, 0.15), inset 0 -1px 0 rgba(0, 0, 0, 0.15);
- -moz-box-shadow: inset 1px 0 0 rgba(0, 0, 0, 0.15), inset 0 -1px 0 rgba(0, 0, 0, 0.15);
- box-shadow: inset 1px 0 0 rgba(0, 0, 0, 0.15), inset 0 -1px 0 rgba(0, 0, 0, 0.15);
-}
-
-.progress-striped .bar {
- background-color: #149bdf;
- background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));
- background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
- background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
- background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
- background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
- -webkit-background-size: 40px 40px;
- -moz-background-size: 40px 40px;
- -o-background-size: 40px 40px;
- background-size: 40px 40px;
-}
-
-.progress.active .bar {
- -webkit-animation: progress-bar-stripes 2s linear infinite;
- -moz-animation: progress-bar-stripes 2s linear infinite;
- -ms-animation: progress-bar-stripes 2s linear infinite;
- -o-animation: progress-bar-stripes 2s linear infinite;
- animation: progress-bar-stripes 2s linear infinite;
-}
-
-.progress-danger .bar,
-.progress .bar-danger {
- background-color: #dd514c;
- background-image: -moz-linear-gradient(top, #ee5f5b, #c43c35);
- background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ee5f5b), to(#c43c35));
- background-image: -webkit-linear-gradient(top, #ee5f5b, #c43c35);
- background-image: -o-linear-gradient(top, #ee5f5b, #c43c35);
- background-image: linear-gradient(to bottom, #ee5f5b, #c43c35);
- background-repeat: repeat-x;
- filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffee5f5b', endColorstr='#ffc43c35', GradientType=0);
-}
-
-.progress-danger.progress-striped .bar,
-.progress-striped .bar-danger {
- background-color: #ee5f5b;
- background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));
- background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
- background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
- background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
- background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
-}
-
-.progress-success .bar,
-.progress .bar-success {
- background-color: #5eb95e;
- background-image: -moz-linear-gradient(top, #62c462, #57a957);
- background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#62c462), to(#57a957));
- background-image: -webkit-linear-gradient(top, #62c462, #57a957);
- background-image: -o-linear-gradient(top, #62c462, #57a957);
- background-image: linear-gradient(to bottom, #62c462, #57a957);
- background-repeat: repeat-x;
- filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff62c462', endColorstr='#ff57a957', GradientType=0);
-}
-
-.progress-success.progress-striped .bar,
-.progress-striped .bar-success {
- background-color: #62c462;
- background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));
- background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
- background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
- background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
- background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
-}
-
-.progress-info .bar,
-.progress .bar-info {
- background-color: #4bb1cf;
- background-image: -moz-linear-gradient(top, #5bc0de, #339bb9);
- background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#5bc0de), to(#339bb9));
- background-image: -webkit-linear-gradient(top, #5bc0de, #339bb9);
- background-image: -o-linear-gradient(top, #5bc0de, #339bb9);
- background-image: linear-gradient(to bottom, #5bc0de, #339bb9);
- background-repeat: repeat-x;
- filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff339bb9', GradientType=0);
-}
-
-.progress-info.progress-striped .bar,
-.progress-striped .bar-info {
- background-color: #5bc0de;
- background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));
- background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
- background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
- background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
- background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
-}
-
-.progress-warning .bar,
-.progress .bar-warning {
- background-color: #faa732;
- background-image: -moz-linear-gradient(top, #fbb450, #f89406);
- background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#fbb450), to(#f89406));
- background-image: -webkit-linear-gradient(top, #fbb450, #f89406);
- background-image: -o-linear-gradient(top, #fbb450, #f89406);
- background-image: linear-gradient(to bottom, #fbb450, #f89406);
- background-repeat: repeat-x;
- filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffbb450', endColorstr='#fff89406', GradientType=0);
-}
-
-.progress-warning.progress-striped .bar,
-.progress-striped .bar-warning {
- background-color: #fbb450;
- background-image: -webkit-gradient(linear, 0 100%, 100% 0, color-stop(0.25, rgba(255, 255, 255, 0.15)), color-stop(0.25, transparent), color-stop(0.5, transparent), color-stop(0.5, rgba(255, 255, 255, 0.15)), color-stop(0.75, rgba(255, 255, 255, 0.15)), color-stop(0.75, transparent), to(transparent));
- background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
- background-image: -moz-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
- background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
- background-image: linear-gradient(45deg, rgba(255, 255, 255, 0.15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, 0.15) 50%, rgba(255, 255, 255, 0.15) 75%, transparent 75%, transparent);
-}
-
-.accordion {
- margin-bottom: 20px;
-}
-
-.accordion-group {
- margin-bottom: 2px;
- border: 1px solid #e5e5e5;
- -webkit-border-radius: 4px;
- -moz-border-radius: 4px;
- border-radius: 4px;
-}
-
-.accordion-heading {
- border-bottom: 0;
-}
-
-.accordion-heading .accordion-toggle {
- display: block;
- padding: 8px 15px;
-}
-
-.accordion-toggle {
- cursor: pointer;
-}
-
-.accordion-inner {
- padding: 9px 15px;
- border-top: 1px solid #e5e5e5;
-}
-
-.carousel {
- position: relative;
- margin-bottom: 20px;
- line-height: 1;
-}
-
-.carousel-inner {
- position: relative;
- width: 100%;
- overflow: hidden;
-}
-
-.carousel .item {
- position: relative;
- display: none;
- -webkit-transition: 0.6s ease-in-out left;
- -moz-transition: 0.6s ease-in-out left;
- -o-transition: 0.6s ease-in-out left;
- transition: 0.6s ease-in-out left;
-}
-
-.carousel .item > img {
- display: block;
- line-height: 1;
-}
-
-.carousel .active,
-.carousel .next,
-.carousel .prev {
- display: block;
-}
-
-.carousel .active {
- left: 0;
-}
-
-.carousel .next,
-.carousel .prev {
- position: absolute;
- top: 0;
- width: 100%;
-}
-
-.carousel .next {
- left: 100%;
-}
-
-.carousel .prev {
- left: -100%;
-}
-
-.carousel .next.left,
-.carousel .prev.right {
- left: 0;
-}
-
-.carousel .active.left {
- left: -100%;
-}
-
-.carousel .active.right {
- left: 100%;
-}
-
-.carousel-control {
- position: absolute;
- top: 40%;
- left: 15px;
- width: 40px;
- height: 40px;
- margin-top: -20px;
- font-size: 60px;
- font-weight: 100;
- line-height: 30px;
- color: #ffffff;
- text-align: center;
- background: #222222;
- border: 3px solid #ffffff;
- -webkit-border-radius: 23px;
- -moz-border-radius: 23px;
- border-radius: 23px;
- opacity: 0.5;
- filter: alpha(opacity=50);
-}
-
-.carousel-control.right {
- right: 15px;
- left: auto;
-}
-
-.carousel-control:hover {
- color: #ffffff;
- text-decoration: none;
- opacity: 0.9;
- filter: alpha(opacity=90);
-}
-
-.carousel-caption {
- position: absolute;
- right: 0;
- bottom: 0;
- left: 0;
- padding: 15px;
- background: #333333;
- background: rgba(0, 0, 0, 0.75);
-}
-
-.carousel-caption h4,
-.carousel-caption p {
- line-height: 20px;
- color: #ffffff;
-}
-
-.carousel-caption h4 {
- margin: 0 0 5px;
-}
-
-.carousel-caption p {
- margin-bottom: 0;
-}
-
-.hero-unit {
- padding: 60px;
- margin-bottom: 30px;
- font-size: 18px;
- font-weight: 200;
- line-height: 30px;
- color: inherit;
- background-color: #eeeeee;
- -webkit-border-radius: 6px;
- -moz-border-radius: 6px;
- border-radius: 6px;
-}
-
-.hero-unit h1 {
- margin-bottom: 0;
- font-size: 60px;
- line-height: 1;
- letter-spacing: -1px;
- color: inherit;
-}
-
-.hero-unit li {
- line-height: 30px;
-}
-
-.pull-right {
- float: right;
-}
-
-.pull-left {
- float: left;
-}
-
-.hide {
- display: none;
-}
-
-.show {
- display: block;
-}
-
-.invisible {
- visibility: hidden;
-}
-
-.affix {
- position: fixed;
-}
diff --git a/src/site/resources/css/bootstrap.min.css b/src/site/resources/css/bootstrap.min.css
deleted file mode 100644
index 43e16d7..0000000
--- a/src/site/resources/css/bootstrap.min.css
+++ /dev/null
@@ -1,9 +0,0 @@
-/*!
- * Bootstrap v2.2.1
- *
- * Copyright 2012 Twitter, Inc
- * Licensed under the Apache License v2.0
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Designed and built with all the love in the world @twitter by @mdo and @fat.
- */article,aside,details,figcaption,figure,footer,header,hgroup,nav,section{display:block}audio,canvas,video{display:inline-block;*display:inline;*zoom:1}audio:not([controls]){display:none}html{font-size:100%;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}a:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}a:hover,a:active{outline:0}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-0.5em}sub{bottom:-0.25em}img{width:auto\9;height:auto;max-width:100%;vertical-align:middle;border:0;-ms-interpolation-mode:bicubic}#map_canvas img,.google-maps img{max-width:none}button,input,select,textarea{margin:0;font-size:100%;vertical-align:middle}button,input{*overflow:visible;line-height:normal}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}button,html input[type="button"],input[type="reset"],input[type="submit"]{cursor:pointer;-webkit-appearance:button}input[type="search"]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type="search"]::-webkit-search-decoration,input[type="search"]::-webkit-search-cancel-button{-webkit-appearance:none}textarea{overflow:auto;vertical-align:top}.clearfix{*zoom:1}.clearfix:before,.clearfix:after{display:table;line-height:0;content:""}.clearfix:after{clear:both}.hide-text{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.input-block-level{display:block;width:100%;min-height:30px;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}body{margin:0;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:20px;color:#333;background-color:#fff}a{color:#08c;text-decoration:none}a:hover{color:#005580;text-decoration:underline}.img-rounded{-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.img-polaroid{padding:4px;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);-webkit-box-shadow:0 1px 3px rgba(0,0,0,0.1);-moz-box-shadow:0 1px 3px rgba(0,0,0,0.1);box-shadow:0 1px 3px rgba(0,0,0,0.1)}.img-circle{-webkit-border-radius:500px;-moz-border-radius:500px;border-radius:500px}.row{margin-left:-20px;*zoom:1}.row:before,.row:after{display:table;line-height:0;content:""}.row:after{clear:both}[class*="span"]{float:left;min-height:1px;margin-left:20px}.container,.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:940px}.span12{width:940px}.span11{width:860px}.span10{width:780px}.span9{width:700px}.span8{width:620px}.span7{width:540px}.span6{width:460px}.span5{width:380px}.span4{width:300px}.span3{width:220px}.span2{width:140px}.span1{width:60px}.offset12{margin-left:980px}.offset11{margin-left:900px}.offset10{margin-left:820px}.offset9{margin-left:740px}.offset8{margin-left:660px}.offset7{margin-left:580px}.offset6{margin-left:500px}.offset5{margin-left:420px}.offset4{margin-left:340px}.offset3{margin-left:260px}.offset2{margin-left:180px}.offset1{margin-left:100px}.row-fluid{width:100%;*zoom:1}.row-fluid:before,.row-fluid:after{display:table;line-height:0;content:""}.row-fluid:after{clear:both}.row-fluid [class*="span"]{display:block;float:left;width:100%;min-height:30px;margin-left:2.127659574468085%;*margin-left:2.074468085106383%;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.row-fluid [class*="span"]:first-child{margin-left:0}.row-fluid .controls-row [class*="span"]+[class*="span"]{margin-left:2.127659574468085%}.row-fluid .span12{width:100%;*width:99.94680851063829%}.row-fluid .span11{width:91.48936170212765%;*width:91.43617021276594%}.row-fluid .span10{width:82.97872340425532%;*width:82.92553191489361%}.row-fluid .span9{width:74.46808510638297%;*width:74.41489361702126%}.row-fluid .span8{width:65.95744680851064%;*width:65.90425531914893%}.row-fluid .span7{width:57.44680851063829%;*width:57.39361702127659%}.row-fluid .span6{width:48.93617021276595%;*width:48.88297872340425%}.row-fluid .span5{width:40.42553191489362%;*width:40.37234042553192%}.row-fluid .span4{width:31.914893617021278%;*width:31.861702127659576%}.row-fluid .span3{width:23.404255319148934%;*width:23.351063829787233%}.row-fluid .span2{width:14.893617021276595%;*width:14.840425531914894%}.row-fluid .span1{width:6.382978723404255%;*width:6.329787234042553%}.row-fluid .offset12{margin-left:104.25531914893617%;*margin-left:104.14893617021275%}.row-fluid .offset12:first-child{margin-left:102.12765957446808%;*margin-left:102.02127659574467%}.row-fluid .offset11{margin-left:95.74468085106382%;*margin-left:95.6382978723404%}.row-fluid .offset11:first-child{margin-left:93.61702127659574%;*margin-left:93.51063829787232%}.row-fluid .offset10{margin-left:87.23404255319149%;*margin-left:87.12765957446807%}.row-fluid .offset10:first-child{margin-left:85.1063829787234%;*margin-left:84.99999999999999%}.row-fluid .offset9{margin-left:78.72340425531914%;*margin-left:78.61702127659572%}.row-fluid .offset9:first-child{margin-left:76.59574468085106%;*margin-left:76.48936170212764%}.row-fluid .offset8{margin-left:70.2127659574468%;*margin-left:70.10638297872339%}.row-fluid .offset8:first-child{margin-left:68.08510638297872%;*margin-left:67.9787234042553%}.row-fluid .offset7{margin-left:61.70212765957446%;*margin-left:61.59574468085106%}.row-fluid .offset7:first-child{margin-left:59.574468085106375%;*margin-left:59.46808510638297%}.row-fluid .offset6{margin-left:53.191489361702125%;*margin-left:53.085106382978715%}.row-fluid .offset6:first-child{margin-left:51.063829787234035%;*margin-left:50.95744680851063%}.row-fluid .offset5{margin-left:44.68085106382979%;*margin-left:44.57446808510638%}.row-fluid .offset5:first-child{margin-left:42.5531914893617%;*margin-left:42.4468085106383%}.row-fluid .offset4{margin-left:36.170212765957444%;*margin-left:36.06382978723405%}.row-fluid .offset4:first-child{margin-left:34.04255319148936%;*margin-left:33.93617021276596%}.row-fluid .offset3{margin-left:27.659574468085104%;*margin-left:27.5531914893617%}.row-fluid .offset3:first-child{margin-left:25.53191489361702%;*margin-left:25.425531914893618%}.row-fluid .offset2{margin-left:19.148936170212764%;*margin-left:19.04255319148936%}.row-fluid .offset2:first-child{margin-left:17.02127659574468%;*margin-left:16.914893617021278%}.row-fluid .offset1{margin-left:10.638297872340425%;*margin-left:10.53191489361702%}.row-fluid .offset1:first-child{margin-left:8.51063829787234%;*margin-left:8.404255319148938%}[class*="span"].hide,.row-fluid [class*="span"].hide{display:none}[class*="span"].pull-right,.row-fluid [class*="span"].pull-right{float:right}.container{margin-right:auto;margin-left:auto;*zoom:1}.container:before,.container:after{display:table;line-height:0;content:""}.container:after{clear:both}.container-fluid{padding-right:20px;padding-left:20px;*zoom:1}.container-fluid:before,.container-fluid:after{display:table;line-height:0;content:""}.container-fluid:after{clear:both}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:21px;font-weight:200;line-height:30px}small{font-size:85%}strong{font-weight:bold}em{font-style:italic}cite{font-style:normal}.muted{color:#999}.text-warning{color:#c09853}a.text-warning:hover{color:#a47e3c}.text-error{color:#b94a48}a.text-error:hover{color:#953b39}.text-info{color:#3a87ad}a.text-info:hover{color:#2d6987}.text-success{color:#468847}a.text-success:hover{color:#356635}h1,h2,h3,h4,h5,h6{margin:10px 0;font-family:inherit;font-weight:bold;line-height:20px;color:inherit;text-rendering:optimizelegibility}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small{font-weight:normal;line-height:1;color:#999}h1,h2,h3{line-height:40px}h1{font-size:38.5px}h2{font-size:31.5px}h3{font-size:24.5px}h4{font-size:17.5px}h5{font-size:14px}h6{font-size:11.9px}h1 small{font-size:24.5px}h2 small{font-size:17.5px}h3 small{font-size:14px}h4 small{font-size:14px}.page-header{padding-bottom:9px;margin:20px 0 30px;border-bottom:1px solid #eee}ul,ol{padding:0;margin:0 0 10px 25px}ul ul,ul ol,ol ol,ol ul{margin-bottom:0}li{line-height:20px}ul.unstyled,ol.unstyled{margin-left:0;list-style:none}dl{margin-bottom:20px}dt,dd{line-height:20px}dt{font-weight:bold}dd{margin-left:10px}.dl-horizontal{*zoom:1}.dl-horizontal:before,.dl-horizontal:after{display:table;line-height:0;content:""}.dl-horizontal:after{clear:both}.dl-horizontal dt{float:left;width:160px;overflow:hidden;clear:left;text-align:right;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}hr{margin:20px 0;border:0;border-top:1px solid #eee;border-bottom:1px solid #fff}abbr[title],abbr[data-original-title]{cursor:help;border-bottom:1px dotted #999}abbr.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:0 0 0 15px;margin:0 0 20px;border-left:5px solid #eee}blockquote p{margin-bottom:0;font-size:16px;font-weight:300;line-height:25px}blockquote small{display:block;line-height:20px;color:#999}blockquote small:before{content:'\2014 \00A0'}blockquote.pull-right{float:right;padding-right:15px;padding-left:0;border-right:5px solid #eee;border-left:0}blockquote.pull-right p,blockquote.pull-right small{text-align:right}blockquote.pull-right small:before{content:''}blockquote.pull-right small:after{content:'\00A0 \2014'}q:before,q:after,blockquote:before,blockquote:after{content:""}address{display:block;margin-bottom:20px;font-style:normal;line-height:20px}code,pre{padding:0 3px 2px;font-family:Monaco,Menlo,Consolas,"Courier New",monospace;font-size:12px;color:#333;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}code{padding:2px 4px;color:#d14;background-color:#f7f7f9;border:1px solid #e1e1e8}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:20px;word-break:break-all;word-wrap:break-word;white-space:pre;white-space:pre-wrap;background-color:#f5f5f5;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.15);-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}pre.prettyprint{margin-bottom:20px}pre code{padding:0;color:inherit;background-color:transparent;border:0}.pre-scrollable{max-height:340px;overflow-y:scroll}form{margin:0 0 20px}fieldset{padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:40px;color:#333;border:0;border-bottom:1px solid #e5e5e5}legend small{font-size:15px;color:#999}label,input,button,select,textarea{font-size:14px;font-weight:normal;line-height:20px}input,button,select,textarea{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif}label{display:block;margin-bottom:5px}select,textarea,input[type="text"],input[type="password"],input[type="datetime"],input[type="datetime-local"],input[type="date"],input[type="month"],input[type="time"],input[type="week"],input[type="number"],input[type="email"],input[type="url"],input[type="search"],input[type="tel"],input[type="color"],.uneditable-input{display:inline-block;height:20px;padding:4px 6px;margin-bottom:10px;font-size:14px;line-height:20px;color:#555;vertical-align:middle;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}input,textarea,.uneditable-input{width:206px}textarea{height:auto}textarea,input[type="text"],input[type="password"],input[type="datetime"],input[type="datetime-local"],input[type="date"],input[type="month"],input[type="time"],input[type="week"],input[type="number"],input[type="email"],input[type="url"],input[type="search"],input[type="tel"],input[type="color"],.uneditable-input{background-color:#fff;border:1px solid #ccc;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-webkit-transition:border linear .2s,box-shadow linear .2s;-moz-transition:border linear .2s,box-shadow linear .2s;-o-transition:border linear .2s,box-shadow linear .2s;transition:border linear .2s,box-shadow linear .2s}textarea:focus,input[type="text"]:focus,input[type="password"]:focus,input[type="datetime"]:focus,input[type="datetime-local"]:focus,input[type="date"]:focus,input[type="month"]:focus,input[type="time"]:focus,input[type="week"]:focus,input[type="number"]:focus,input[type="email"]:focus,input[type="url"]:focus,input[type="search"]:focus,input[type="tel"]:focus,input[type="color"]:focus,.uneditable-input:focus{border-color:rgba(82,168,236,0.8);outline:0;outline:thin dotted \9;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 8px rgba(82,168,236,0.6)}input[type="radio"],input[type="checkbox"]{margin:4px 0 0;margin-top:1px \9;*margin-top:0;line-height:normal;cursor:pointer}input[type="file"],input[type="image"],input[type="submit"],input[type="reset"],input[type="button"],input[type="radio"],input[type="checkbox"]{width:auto}select,input[type="file"]{height:30px;*margin-top:4px;line-height:30px}select{width:220px;background-color:#fff;border:1px solid #ccc}select[multiple],select[size]{height:auto}select:focus,input[type="file"]:focus,input[type="radio"]:focus,input[type="checkbox"]:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.uneditable-input,.uneditable-textarea{color:#999;cursor:not-allowed;background-color:#fcfcfc;border-color:#ccc;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.025);-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,0.025);box-shadow:inset 0 1px 2px rgba(0,0,0,0.025)}.uneditable-input{overflow:hidden;white-space:nowrap}.uneditable-textarea{width:auto;height:auto}input:-moz-placeholder,textarea:-moz-placeholder{color:#999}input:-ms-input-placeholder,textarea:-ms-input-placeholder{color:#999}input::-webkit-input-placeholder,textarea::-webkit-input-placeholder{color:#999}.radio,.checkbox{min-height:20px;padding-left:20px}.radio input[type="radio"],.checkbox input[type="checkbox"]{float:left;margin-left:-20px}.controls>.radio:first-child,.controls>.checkbox:first-child{padding-top:5px}.radio.inline,.checkbox.inline{display:inline-block;padding-top:5px;margin-bottom:0;vertical-align:middle}.radio.inline+.radio.inline,.checkbox.inline+.checkbox.inline{margin-left:10px}.input-mini{width:60px}.input-small{width:90px}.input-medium{width:150px}.input-large{width:210px}.input-xlarge{width:270px}.input-xxlarge{width:530px}input[class*="span"],select[class*="span"],textarea[class*="span"],.uneditable-input[class*="span"],.row-fluid input[class*="span"],.row-fluid select[class*="span"],.row-fluid textarea[class*="span"],.row-fluid .uneditable-input[class*="span"]{float:none;margin-left:0}.input-append input[class*="span"],.input-append .uneditable-input[class*="span"],.input-prepend input[class*="span"],.input-prepend .uneditable-input[class*="span"],.row-fluid input[class*="span"],.row-fluid select[class*="span"],.row-fluid textarea[class*="span"],.row-fluid .uneditable-input[class*="span"],.row-fluid .input-prepend [class*="span"],.row-fluid .input-append [class*="span"]{display:inline-block}input,textarea,.uneditable-input{margin-left:0}.controls-row [class*="span"]+[class*="span"]{margin-left:20px}input.span12,textarea.span12,.uneditable-input.span12{width:926px}input.span11,textarea.span11,.uneditable-input.span11{width:846px}input.span10,textarea.span10,.uneditable-input.span10{width:766px}input.span9,textarea.span9,.uneditable-input.span9{width:686px}input.span8,textarea.span8,.uneditable-input.span8{width:606px}input.span7,textarea.span7,.uneditable-input.span7{width:526px}input.span6,textarea.span6,.uneditable-input.span6{width:446px}input.span5,textarea.span5,.uneditable-input.span5{width:366px}input.span4,textarea.span4,.uneditable-input.span4{width:286px}input.span3,textarea.span3,.uneditable-input.span3{width:206px}input.span2,textarea.span2,.uneditable-input.span2{width:126px}input.span1,textarea.span1,.uneditable-input.span1{width:46px}.controls-row{*zoom:1}.controls-row:before,.controls-row:after{display:table;line-height:0;content:""}.controls-row:after{clear:both}.controls-row [class*="span"],.row-fluid .controls-row [class*="span"]{float:left}.controls-row .checkbox[class*="span"],.controls-row .radio[class*="span"]{padding-top:5px}input[disabled],select[disabled],textarea[disabled],input[readonly],select[readonly],textarea[readonly]{cursor:not-allowed;background-color:#eee}input[type="radio"][disabled],input[type="checkbox"][disabled],input[type="radio"][readonly],input[type="checkbox"][readonly]{background-color:transparent}.control-group.warning>label,.control-group.warning .help-block,.control-group.warning .help-inline{color:#c09853}.control-group.warning .checkbox,.control-group.warning .radio,.control-group.warning input,.control-group.warning select,.control-group.warning textarea{color:#c09853}.control-group.warning input,.control-group.warning select,.control-group.warning textarea{border-color:#c09853;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.control-group.warning input:focus,.control-group.warning select:focus,.control-group.warning textarea:focus{border-color:#a47e3c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #dbc59e;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #dbc59e;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #dbc59e}.control-group.warning .input-prepend .add-on,.control-group.warning .input-append .add-on{color:#c09853;background-color:#fcf8e3;border-color:#c09853}.control-group.error>label,.control-group.error .help-block,.control-group.error .help-inline{color:#b94a48}.control-group.error .checkbox,.control-group.error .radio,.control-group.error input,.control-group.error select,.control-group.error textarea{color:#b94a48}.control-group.error input,.control-group.error select,.control-group.error textarea{border-color:#b94a48;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.control-group.error input:focus,.control-group.error select:focus,.control-group.error textarea:focus{border-color:#953b39;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #d59392;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #d59392;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #d59392}.control-group.error .input-prepend .add-on,.control-group.error .input-append .add-on{color:#b94a48;background-color:#f2dede;border-color:#b94a48}.control-group.success>label,.control-group.success .help-block,.control-group.success .help-inline{color:#468847}.control-group.success .checkbox,.control-group.success .radio,.control-group.success input,.control-group.success select,.control-group.success textarea{color:#468847}.control-group.success input,.control-group.success select,.control-group.success textarea{border-color:#468847;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.control-group.success input:focus,.control-group.success select:focus,.control-group.success textarea:focus{border-color:#356635;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7aba7b;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7aba7b;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7aba7b}.control-group.success .input-prepend .add-on,.control-group.success .input-append .add-on{color:#468847;background-color:#dff0d8;border-color:#468847}.control-group.info>label,.control-group.info .help-block,.control-group.info .help-inline{color:#3a87ad}.control-group.info .checkbox,.control-group.info .radio,.control-group.info input,.control-group.info select,.control-group.info textarea{color:#3a87ad}.control-group.info input,.control-group.info select,.control-group.info textarea{border-color:#3a87ad;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075);box-shadow:inset 0 1px 1px rgba(0,0,0,0.075)}.control-group.info input:focus,.control-group.info select:focus,.control-group.info textarea:focus{border-color:#2d6987;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7ab5d3;-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7ab5d3;box-shadow:inset 0 1px 1px rgba(0,0,0,0.075),0 0 6px #7ab5d3}.control-group.info .input-prepend .add-on,.control-group.info .input-append .add-on{color:#3a87ad;background-color:#d9edf7;border-color:#3a87ad}input:focus:required:invalid,textarea:focus:required:invalid,select:focus:required:invalid{color:#b94a48;border-color:#ee5f5b}input:focus:required:invalid:focus,textarea:focus:required:invalid:focus,select:focus:required:invalid:focus{border-color:#e9322d;-webkit-box-shadow:0 0 6px #f8b9b7;-moz-box-shadow:0 0 6px #f8b9b7;box-shadow:0 0 6px #f8b9b7}.form-actions{padding:19px 20px 20px;margin-top:20px;margin-bottom:20px;background-color:#f5f5f5;border-top:1px solid #e5e5e5;*zoom:1}.form-actions:before,.form-actions:after{display:table;line-height:0;content:""}.form-actions:after{clear:both}.help-block,.help-inline{color:#595959}.help-block{display:block;margin-bottom:10px}.help-inline{display:inline-block;*display:inline;padding-left:5px;vertical-align:middle;*zoom:1}.input-append,.input-prepend{margin-bottom:5px;font-size:0;white-space:nowrap}.input-append input,.input-prepend input,.input-append select,.input-prepend select,.input-append .uneditable-input,.input-prepend .uneditable-input,.input-append .dropdown-menu,.input-prepend .dropdown-menu{font-size:14px}.input-append input,.input-prepend input,.input-append select,.input-prepend select,.input-append .uneditable-input,.input-prepend .uneditable-input{position:relative;margin-bottom:0;*margin-left:0;vertical-align:top;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-append input:focus,.input-prepend input:focus,.input-append select:focus,.input-prepend select:focus,.input-append .uneditable-input:focus,.input-prepend .uneditable-input:focus{z-index:2}.input-append .add-on,.input-prepend .add-on{display:inline-block;width:auto;height:20px;min-width:16px;padding:4px 5px;font-size:14px;font-weight:normal;line-height:20px;text-align:center;text-shadow:0 1px 0 #fff;background-color:#eee;border:1px solid #ccc}.input-append .add-on,.input-prepend .add-on,.input-append .btn,.input-prepend .btn{vertical-align:top;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.input-append .active,.input-prepend .active{background-color:#a9dba9;border-color:#46a546}.input-prepend .add-on,.input-prepend .btn{margin-right:-1px}.input-prepend .add-on:first-child,.input-prepend .btn:first-child{-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.input-append input,.input-append select,.input-append .uneditable-input{-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.input-append input+.btn-group .btn,.input-append select+.btn-group .btn,.input-append .uneditable-input+.btn-group .btn{-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-append .add-on,.input-append .btn,.input-append .btn-group{margin-left:-1px}.input-append .add-on:last-child,.input-append .btn:last-child{-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-prepend.input-append input,.input-prepend.input-append select,.input-prepend.input-append .uneditable-input{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.input-prepend.input-append input+.btn-group .btn,.input-prepend.input-append select+.btn-group .btn,.input-prepend.input-append .uneditable-input+.btn-group .btn{-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-prepend.input-append .add-on:first-child,.input-prepend.input-append .btn:first-child{margin-right:-1px;-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.input-prepend.input-append .add-on:last-child,.input-prepend.input-append .btn:last-child{margin-left:-1px;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.input-prepend.input-append .btn-group:first-child{margin-left:0}input.search-query{padding-right:14px;padding-right:4px \9;padding-left:14px;padding-left:4px \9;margin-bottom:0;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px}.form-search .input-append .search-query,.form-search .input-prepend .search-query{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.form-search .input-append .search-query{-webkit-border-radius:14px 0 0 14px;-moz-border-radius:14px 0 0 14px;border-radius:14px 0 0 14px}.form-search .input-append .btn{-webkit-border-radius:0 14px 14px 0;-moz-border-radius:0 14px 14px 0;border-radius:0 14px 14px 0}.form-search .input-prepend .search-query{-webkit-border-radius:0 14px 14px 0;-moz-border-radius:0 14px 14px 0;border-radius:0 14px 14px 0}.form-search .input-prepend .btn{-webkit-border-radius:14px 0 0 14px;-moz-border-radius:14px 0 0 14px;border-radius:14px 0 0 14px}.form-search input,.form-inline input,.form-horizontal input,.form-search textarea,.form-inline textarea,.form-horizontal textarea,.form-search select,.form-inline select,.form-horizontal select,.form-search .help-inline,.form-inline .help-inline,.form-horizontal .help-inline,.form-search .uneditable-input,.form-inline .uneditable-input,.form-horizontal .uneditable-input,.form-search .input-prepend,.form-inline .input-prepend,.form-horizontal .input-prepend,.form-search .input-append,.form-inline .input-append,.form-horizontal .input-append{display:inline-block;*display:inline;margin-bottom:0;vertical-align:middle;*zoom:1}.form-search .hide,.form-inline .hide,.form-horizontal .hide{display:none}.form-search label,.form-inline label,.form-search .btn-group,.form-inline .btn-group{display:inline-block}.form-search .input-append,.form-inline .input-append,.form-search .input-prepend,.form-inline .input-prepend{margin-bottom:0}.form-search .radio,.form-search .checkbox,.form-inline .radio,.form-inline .checkbox{padding-left:0;margin-bottom:0;vertical-align:middle}.form-search .radio input[type="radio"],.form-search .checkbox input[type="checkbox"],.form-inline .radio input[type="radio"],.form-inline .checkbox input[type="checkbox"]{float:left;margin-right:3px;margin-left:0}.control-group{margin-bottom:10px}legend+.control-group{margin-top:20px;-webkit-margin-top-collapse:separate}.form-horizontal .control-group{margin-bottom:20px;*zoom:1}.form-horizontal .control-group:before,.form-horizontal .control-group:after{display:table;line-height:0;content:""}.form-horizontal .control-group:after{clear:both}.form-horizontal .control-label{float:left;width:160px;padding-top:5px;text-align:right}.form-horizontal .controls{*display:inline-block;*padding-left:20px;margin-left:180px;*margin-left:0}.form-horizontal .controls:first-child{*padding-left:180px}.form-horizontal .help-block{margin-bottom:0}.form-horizontal input+.help-block,.form-horizontal select+.help-block,.form-horizontal textarea+.help-block{margin-top:10px}.form-horizontal .form-actions{padding-left:180px}table{max-width:100%;background-color:transparent;border-collapse:collapse;border-spacing:0}.table{width:100%;margin-bottom:20px}.table th,.table td{padding:8px;line-height:20px;text-align:left;vertical-align:top;border-top:1px solid #ddd}.table th{font-weight:bold}.table thead th{vertical-align:bottom}.table caption+thead tr:first-child th,.table caption+thead tr:first-child td,.table colgroup+thead tr:first-child th,.table colgroup+thead tr:first-child td,.table thead:first-child tr:first-child th,.table thead:first-child tr:first-child td{border-top:0}.table tbody+tbody{border-top:2px solid #ddd}.table-condensed th,.table-condensed td{padding:4px 5px}.table-bordered{border:1px solid #ddd;border-collapse:separate;*border-collapse:collapse;border-left:0;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.table-bordered th,.table-bordered td{border-left:1px solid #ddd}.table-bordered caption+thead tr:first-child th,.table-bordered caption+tbody tr:first-child th,.table-bordered caption+tbody tr:first-child td,.table-bordered colgroup+thead tr:first-child th,.table-bordered colgroup+tbody tr:first-child th,.table-bordered colgroup+tbody tr:first-child td,.table-bordered thead:first-child tr:first-child th,.table-bordered tbody:first-child tr:first-child th,.table-bordered tbody:first-child tr:first-child td{border-top:0}.table-bordered thead:first-child tr:first-child th:first-child,.table-bordered tbody:first-child tr:first-child td:first-child{-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-topleft:4px}.table-bordered thead:first-child tr:first-child th:last-child,.table-bordered tbody:first-child tr:first-child td:last-child{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-moz-border-radius-topright:4px}.table-bordered thead:last-child tr:last-child th:first-child,.table-bordered tbody:last-child tr:last-child td:first-child,.table-bordered tfoot:last-child tr:last-child td:first-child{-webkit-border-radius:0 0 0 4px;-moz-border-radius:0 0 0 4px;border-radius:0 0 0 4px;-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-moz-border-radius-bottomleft:4px}.table-bordered thead:last-child tr:last-child th:last-child,.table-bordered tbody:last-child tr:last-child td:last-child,.table-bordered tfoot:last-child tr:last-child td:last-child{-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-moz-border-radius-bottomright:4px}.table-bordered caption+thead tr:first-child th:first-child,.table-bordered caption+tbody tr:first-child td:first-child,.table-bordered colgroup+thead tr:first-child th:first-child,.table-bordered colgroup+tbody tr:first-child td:first-child{-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-topleft:4px}.table-bordered caption+thead tr:first-child th:last-child,.table-bordered caption+tbody tr:first-child td:last-child,.table-bordered colgroup+thead tr:first-child th:last-child,.table-bordered colgroup+tbody tr:first-child td:last-child{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-moz-border-radius-topright:4px}.table-striped tbody tr:nth-child(odd) td,.table-striped tbody tr:nth-child(odd) th{background-color:#f9f9f9}.table-hover tbody tr:hover td,.table-hover tbody tr:hover th{background-color:#f5f5f5}table td[class*="span"],table th[class*="span"],.row-fluid table td[class*="span"],.row-fluid table th[class*="span"]{display:table-cell;float:none;margin-left:0}.table td.span1,.table th.span1{float:none;width:44px;margin-left:0}.table td.span2,.table th.span2{float:none;width:124px;margin-left:0}.table td.span3,.table th.span3{float:none;width:204px;margin-left:0}.table td.span4,.table th.span4{float:none;width:284px;margin-left:0}.table td.span5,.table th.span5{float:none;width:364px;margin-left:0}.table td.span6,.table th.span6{float:none;width:444px;margin-left:0}.table td.span7,.table th.span7{float:none;width:524px;margin-left:0}.table td.span8,.table th.span8{float:none;width:604px;margin-left:0}.table td.span9,.table th.span9{float:none;width:684px;margin-left:0}.table td.span10,.table th.span10{float:none;width:764px;margin-left:0}.table td.span11,.table th.span11{float:none;width:844px;margin-left:0}.table td.span12,.table th.span12{float:none;width:924px;margin-left:0}.table tbody tr.success td{background-color:#dff0d8}.table tbody tr.error td{background-color:#f2dede}.table tbody tr.warning td{background-color:#fcf8e3}.table tbody tr.info td{background-color:#d9edf7}.table-hover tbody tr.success:hover td{background-color:#d0e9c6}.table-hover tbody tr.error:hover td{background-color:#ebcccc}.table-hover tbody tr.warning:hover td{background-color:#faf2cc}.table-hover tbody tr.info:hover td{background-color:#c4e3f3}[class^="icon-"],[class*=" icon-"]{display:inline-block;width:14px;height:14px;margin-top:1px;*margin-right:.3em;line-height:14px;vertical-align:text-top;background-image:url("../img/glyphicons-halflings.png");background-position:14px 14px;background-repeat:no-repeat}.icon-white,.nav-pills>.active>a>[class^="icon-"],.nav-pills>.active>a>[class*=" icon-"],.nav-list>.active>a>[class^="icon-"],.nav-list>.active>a>[class*=" icon-"],.navbar-inverse .nav>.active>a>[class^="icon-"],.navbar-inverse .nav>.active>a>[class*=" icon-"],.dropdown-menu>li>a:hover>[class^="icon-"],.dropdown-menu>li>a:hover>[class*=" icon-"],.dropdown-menu>.active>a>[class^="icon-"],.dropdown-menu>.active>a>[class*=" icon-"],.dropdown-submenu:hover>a>[class^="icon-"],.dropdown-submenu:hover>a>[class*=" icon-"]{background-image:url("../img/glyphicons-halflings-white.png")}.icon-glass{background-position:0 0}.icon-music{background-position:-24px 0}.icon-search{background-position:-48px 0}.icon-envelope{background-position:-72px 0}.icon-heart{background-position:-96px 0}.icon-star{background-position:-120px 0}.icon-star-empty{background-position:-144px 0}.icon-user{background-position:-168px 0}.icon-film{background-position:-192px 0}.icon-th-large{background-position:-216px 0}.icon-th{background-position:-240px 0}.icon-th-list{background-position:-264px 0}.icon-ok{background-position:-288px 0}.icon-remove{background-position:-312px 0}.icon-zoom-in{background-position:-336px 0}.icon-zoom-out{background-position:-360px 0}.icon-off{background-position:-384px 0}.icon-signal{background-position:-408px 0}.icon-cog{background-position:-432px 0}.icon-trash{background-position:-456px 0}.icon-home{background-position:0 -24px}.icon-file{background-position:-24px -24px}.icon-time{background-position:-48px -24px}.icon-road{background-position:-72px -24px}.icon-download-alt{background-position:-96px -24px}.icon-download{background-position:-120px -24px}.icon-upload{background-position:-144px -24px}.icon-inbox{background-position:-168px -24px}.icon-play-circle{background-position:-192px -24px}.icon-repeat{background-position:-216px -24px}.icon-refresh{background-position:-240px -24px}.icon-list-alt{background-position:-264px -24px}.icon-lock{background-position:-287px -24px}.icon-flag{background-position:-312px -24px}.icon-headphones{background-position:-336px -24px}.icon-volume-off{background-position:-360px -24px}.icon-volume-down{background-position:-384px -24px}.icon-volume-up{background-position:-408px -24px}.icon-qrcode{background-position:-432px -24px}.icon-barcode{background-position:-456px -24px}.icon-tag{background-position:0 -48px}.icon-tags{background-position:-25px -48px}.icon-book{background-position:-48px -48px}.icon-bookmark{background-position:-72px -48px}.icon-print{background-position:-96px -48px}.icon-camera{background-position:-120px -48px}.icon-font{background-position:-144px -48px}.icon-bold{background-position:-167px -48px}.icon-italic{background-position:-192px -48px}.icon-text-height{background-position:-216px -48px}.icon-text-width{background-position:-240px -48px}.icon-align-left{background-position:-264px -48px}.icon-align-center{background-position:-288px -48px}.icon-align-right{background-position:-312px -48px}.icon-align-justify{background-position:-336px -48px}.icon-list{background-position:-360px -48px}.icon-indent-left{background-position:-384px -48px}.icon-indent-right{background-position:-408px -48px}.icon-facetime-video{background-position:-432px -48px}.icon-picture{background-position:-456px -48px}.icon-pencil{background-position:0 -72px}.icon-map-marker{background-position:-24px -72px}.icon-adjust{background-position:-48px -72px}.icon-tint{background-position:-72px -72px}.icon-edit{background-position:-96px -72px}.icon-share{background-position:-120px -72px}.icon-check{background-position:-144px -72px}.icon-move{background-position:-168px -72px}.icon-step-backward{background-position:-192px -72px}.icon-fast-backward{background-position:-216px -72px}.icon-backward{background-position:-240px -72px}.icon-play{background-position:-264px -72px}.icon-pause{background-position:-288px -72px}.icon-stop{background-position:-312px -72px}.icon-forward{background-position:-336px -72px}.icon-fast-forward{background-position:-360px -72px}.icon-step-forward{background-position:-384px -72px}.icon-eject{background-position:-408px -72px}.icon-chevron-left{background-position:-432px -72px}.icon-chevron-right{background-position:-456px -72px}.icon-plus-sign{background-position:0 -96px}.icon-minus-sign{background-position:-24px -96px}.icon-remove-sign{background-position:-48px -96px}.icon-ok-sign{background-position:-72px -96px}.icon-question-sign{background-position:-96px -96px}.icon-info-sign{background-position:-120px -96px}.icon-screenshot{background-position:-144px -96px}.icon-remove-circle{background-position:-168px -96px}.icon-ok-circle{background-position:-192px -96px}.icon-ban-circle{background-position:-216px -96px}.icon-arrow-left{background-position:-240px -96px}.icon-arrow-right{background-position:-264px -96px}.icon-arrow-up{background-position:-289px -96px}.icon-arrow-down{background-position:-312px -96px}.icon-share-alt{background-position:-336px -96px}.icon-resize-full{background-position:-360px -96px}.icon-resize-small{background-position:-384px -96px}.icon-plus{background-position:-408px -96px}.icon-minus{background-position:-433px -96px}.icon-asterisk{background-position:-456px -96px}.icon-exclamation-sign{background-position:0 -120px}.icon-gift{background-position:-24px -120px}.icon-leaf{background-position:-48px -120px}.icon-fire{background-position:-72px -120px}.icon-eye-open{background-position:-96px -120px}.icon-eye-close{background-position:-120px -120px}.icon-warning-sign{background-position:-144px -120px}.icon-plane{background-position:-168px -120px}.icon-calendar{background-position:-192px -120px}.icon-random{width:16px;background-position:-216px -120px}.icon-comment{background-position:-240px -120px}.icon-magnet{background-position:-264px -120px}.icon-chevron-up{background-position:-288px -120px}.icon-chevron-down{background-position:-313px -119px}.icon-retweet{background-position:-336px -120px}.icon-shopping-cart{background-position:-360px -120px}.icon-folder-close{background-position:-384px -120px}.icon-folder-open{width:16px;background-position:-408px -120px}.icon-resize-vertical{background-position:-432px -119px}.icon-resize-horizontal{background-position:-456px -118px}.icon-hdd{background-position:0 -144px}.icon-bullhorn{background-position:-24px -144px}.icon-bell{background-position:-48px -144px}.icon-certificate{background-position:-72px -144px}.icon-thumbs-up{background-position:-96px -144px}.icon-thumbs-down{background-position:-120px -144px}.icon-hand-right{background-position:-144px -144px}.icon-hand-left{background-position:-168px -144px}.icon-hand-up{background-position:-192px -144px}.icon-hand-down{background-position:-216px -144px}.icon-circle-arrow-right{background-position:-240px -144px}.icon-circle-arrow-left{background-position:-264px -144px}.icon-circle-arrow-up{background-position:-288px -144px}.icon-circle-arrow-down{background-position:-312px -144px}.icon-globe{background-position:-336px -144px}.icon-wrench{background-position:-360px -144px}.icon-tasks{background-position:-384px -144px}.icon-filter{background-position:-408px -144px}.icon-briefcase{background-position:-432px -144px}.icon-fullscreen{background-position:-456px -144px}.dropup,.dropdown{position:relative}.dropdown-toggle{*margin-bottom:-3px}.dropdown-toggle:active,.open .dropdown-toggle{outline:0}.caret{display:inline-block;width:0;height:0;vertical-align:top;border-top:4px solid #000;border-right:4px solid transparent;border-left:4px solid transparent;content:""}.dropdown .caret{margin-top:8px;margin-left:2px}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;list-style:none;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);*border-right-width:2px;*border-bottom-width:2px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,0.2);-moz-box-shadow:0 5px 10px rgba(0,0,0,0.2);box-shadow:0 5px 10px rgba(0,0,0,0.2);-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{*width:100%;height:1px;margin:9px 1px;*margin:-5px 0 5px;overflow:hidden;background-color:#e5e5e5;border-bottom:1px solid #fff}.dropdown-menu li>a{display:block;padding:3px 20px;clear:both;font-weight:normal;line-height:20px;color:#333;white-space:nowrap}.dropdown-menu li>a:hover,.dropdown-menu li>a:focus,.dropdown-submenu:hover>a{color:#fff;text-decoration:none;background-color:#0081c2;background-image:-moz-linear-gradient(top,#08c,#0077b3);background-image:-webkit-gradient(linear,0 0,0 100%,from(#08c),to(#0077b3));background-image:-webkit-linear-gradient(top,#08c,#0077b3);background-image:-o-linear-gradient(top,#08c,#0077b3);background-image:linear-gradient(to bottom,#08c,#0077b3);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc',endColorstr='#ff0077b3',GradientType=0)}.dropdown-menu .active>a,.dropdown-menu .active>a:hover{color:#333;text-decoration:none;background-color:#0081c2;background-image:-moz-linear-gradient(top,#08c,#0077b3);background-image:-webkit-gradient(linear,0 0,0 100%,from(#08c),to(#0077b3));background-image:-webkit-linear-gradient(top,#08c,#0077b3);background-image:-o-linear-gradient(top,#08c,#0077b3);background-image:linear-gradient(to bottom,#08c,#0077b3);background-repeat:repeat-x;outline:0;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc',endColorstr='#ff0077b3',GradientType=0)}.dropdown-menu .disabled>a,.dropdown-menu .disabled>a:hover{color:#999}.dropdown-menu .disabled>a:hover{text-decoration:none;cursor:default;background-color:transparent;background-image:none}.open{*z-index:1000}.open>.dropdown-menu{display:block}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{border-top:0;border-bottom:4px solid #000;content:""}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:1px}.dropdown-submenu{position:relative}.dropdown-submenu>.dropdown-menu{top:0;left:100%;margin-top:-6px;margin-left:-1px;-webkit-border-radius:0 6px 6px 6px;-moz-border-radius:0 6px 6px 6px;border-radius:0 6px 6px 6px}.dropdown-submenu:hover>.dropdown-menu{display:block}.dropup .dropdown-submenu>.dropdown-menu{top:auto;bottom:0;margin-top:0;margin-bottom:-2px;-webkit-border-radius:5px 5px 5px 0;-moz-border-radius:5px 5px 5px 0;border-radius:5px 5px 5px 0}.dropdown-submenu>a:after{display:block;float:right;width:0;height:0;margin-top:5px;margin-right:-10px;border-color:transparent;border-left-color:#ccc;border-style:solid;border-width:5px 0 5px 5px;content:" "}.dropdown-submenu:hover>a:after{border-left-color:#fff}.dropdown-submenu.pull-left{float:none}.dropdown-submenu.pull-left>.dropdown-menu{left:-100%;margin-left:10px;-webkit-border-radius:6px 0 6px 6px;-moz-border-radius:6px 0 6px 6px;border-radius:6px 0 6px 6px}.dropdown .dropdown-menu .nav-header{padding-right:20px;padding-left:20px}.typeahead{margin-top:2px;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 1px rgba(0,0,0,0.05);box-shadow:inset 0 1px 1px rgba(0,0,0,0.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,0.15)}.well-large{padding:24px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.well-small{padding:9px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.fade{opacity:0;-webkit-transition:opacity .15s linear;-moz-transition:opacity .15s linear;-o-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{position:relative;height:0;overflow:hidden;-webkit-transition:height .35s ease;-moz-transition:height .35s ease;-o-transition:height .35s ease;transition:height .35s ease}.collapse.in{height:auto}.close{float:right;font-size:20px;font-weight:bold;line-height:20px;color:#000;text-shadow:0 1px 0 #fff;opacity:.2;filter:alpha(opacity=20)}.close:hover{color:#000;text-decoration:none;cursor:pointer;opacity:.4;filter:alpha(opacity=40)}button.close{padding:0;cursor:pointer;background:transparent;border:0;-webkit-appearance:none}.btn{display:inline-block;*display:inline;padding:4px 12px;margin-bottom:0;*margin-left:.3em;font-size:14px;line-height:20px;*line-height:20px;color:#333;text-align:center;text-shadow:0 1px 1px rgba(255,255,255,0.75);vertical-align:middle;cursor:pointer;background-color:#f5f5f5;*background-color:#e6e6e6;background-image:-moz-linear-gradient(top,#fff,#e6e6e6);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fff),to(#e6e6e6));background-image:-webkit-linear-gradient(top,#fff,#e6e6e6);background-image:-o-linear-gradient(top,#fff,#e6e6e6);background-image:linear-gradient(to bottom,#fff,#e6e6e6);background-repeat:repeat-x;border:1px solid #bbb;*border:0;border-color:#e6e6e6 #e6e6e6 #bfbfbf;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);border-bottom-color:#a2a2a2;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff',endColorstr='#ffe6e6e6',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);*zoom:1;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05)}.btn:hover,.btn:active,.btn.active,.btn.disabled,.btn[disabled]{color:#333;background-color:#e6e6e6;*background-color:#d9d9d9}.btn:active,.btn.active{background-color:#ccc \9}.btn:first-child{*margin-left:0}.btn:hover{color:#333;text-decoration:none;background-color:#e6e6e6;*background-color:#d9d9d9;background-position:0 -15px;-webkit-transition:background-position .1s linear;-moz-transition:background-position .1s linear;-o-transition:background-position .1s linear;transition:background-position .1s linear}.btn:focus{outline:thin dotted #333;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn.active,.btn:active{background-color:#e6e6e6;background-color:#d9d9d9 \9;background-image:none;outline:0;-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05)}.btn.disabled,.btn[disabled]{cursor:default;background-color:#e6e6e6;background-image:none;opacity:.65;filter:alpha(opacity=65);-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.btn-large{padding:11px 19px;font-size:17.5px;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.btn-large [class^="icon-"],.btn-large [class*=" icon-"]{margin-top:2px}.btn-small{padding:2px 10px;font-size:11.9px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.btn-small [class^="icon-"],.btn-small [class*=" icon-"]{margin-top:0}.btn-mini{padding:1px 6px;font-size:10.5px;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.btn-block{display:block;width:100%;padding-right:0;padding-left:0;-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.btn-block+.btn-block{margin-top:5px}input[type="submit"].btn-block,input[type="reset"].btn-block,input[type="button"].btn-block{width:100%}.btn-primary.active,.btn-warning.active,.btn-danger.active,.btn-success.active,.btn-info.active,.btn-inverse.active{color:rgba(255,255,255,0.75)}.btn{border-color:#c5c5c5;border-color:rgba(0,0,0,0.15) rgba(0,0,0,0.15) rgba(0,0,0,0.25)}.btn-primary{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#006dcc;*background-color:#04c;background-image:-moz-linear-gradient(top,#08c,#04c);background-image:-webkit-gradient(linear,0 0,0 100%,from(#08c),to(#04c));background-image:-webkit-linear-gradient(top,#08c,#04c);background-image:-o-linear-gradient(top,#08c,#04c);background-image:linear-gradient(to bottom,#08c,#04c);background-repeat:repeat-x;border-color:#04c #04c #002a80;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc',endColorstr='#ff0044cc',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-primary:hover,.btn-primary:active,.btn-primary.active,.btn-primary.disabled,.btn-primary[disabled]{color:#fff;background-color:#04c;*background-color:#003bb3}.btn-primary:active,.btn-primary.active{background-color:#039 \9}.btn-warning{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#faa732;*background-color:#f89406;background-image:-moz-linear-gradient(top,#fbb450,#f89406);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fbb450),to(#f89406));background-image:-webkit-linear-gradient(top,#fbb450,#f89406);background-image:-o-linear-gradient(top,#fbb450,#f89406);background-image:linear-gradient(to bottom,#fbb450,#f89406);background-repeat:repeat-x;border-color:#f89406 #f89406 #ad6704;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffbb450',endColorstr='#fff89406',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-warning:hover,.btn-warning:active,.btn-warning.active,.btn-warning.disabled,.btn-warning[disabled]{color:#fff;background-color:#f89406;*background-color:#df8505}.btn-warning:active,.btn-warning.active{background-color:#c67605 \9}.btn-danger{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#da4f49;*background-color:#bd362f;background-image:-moz-linear-gradient(top,#ee5f5b,#bd362f);background-image:-webkit-gradient(linear,0 0,0 100%,from(#ee5f5b),to(#bd362f));background-image:-webkit-linear-gradient(top,#ee5f5b,#bd362f);background-image:-o-linear-gradient(top,#ee5f5b,#bd362f);background-image:linear-gradient(to bottom,#ee5f5b,#bd362f);background-repeat:repeat-x;border-color:#bd362f #bd362f #802420;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffee5f5b',endColorstr='#ffbd362f',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-danger:hover,.btn-danger:active,.btn-danger.active,.btn-danger.disabled,.btn-danger[disabled]{color:#fff;background-color:#bd362f;*background-color:#a9302a}.btn-danger:active,.btn-danger.active{background-color:#942a25 \9}.btn-success{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#5bb75b;*background-color:#51a351;background-image:-moz-linear-gradient(top,#62c462,#51a351);background-image:-webkit-gradient(linear,0 0,0 100%,from(#62c462),to(#51a351));background-image:-webkit-linear-gradient(top,#62c462,#51a351);background-image:-o-linear-gradient(top,#62c462,#51a351);background-image:linear-gradient(to bottom,#62c462,#51a351);background-repeat:repeat-x;border-color:#51a351 #51a351 #387038;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff62c462',endColorstr='#ff51a351',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-success:hover,.btn-success:active,.btn-success.active,.btn-success.disabled,.btn-success[disabled]{color:#fff;background-color:#51a351;*background-color:#499249}.btn-success:active,.btn-success.active{background-color:#408140 \9}.btn-info{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#49afcd;*background-color:#2f96b4;background-image:-moz-linear-gradient(top,#5bc0de,#2f96b4);background-image:-webkit-gradient(linear,0 0,0 100%,from(#5bc0de),to(#2f96b4));background-image:-webkit-linear-gradient(top,#5bc0de,#2f96b4);background-image:-o-linear-gradient(top,#5bc0de,#2f96b4);background-image:linear-gradient(to bottom,#5bc0de,#2f96b4);background-repeat:repeat-x;border-color:#2f96b4 #2f96b4 #1f6377;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de',endColorstr='#ff2f96b4',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-info:hover,.btn-info:active,.btn-info.active,.btn-info.disabled,.btn-info[disabled]{color:#fff;background-color:#2f96b4;*background-color:#2a85a0}.btn-info:active,.btn-info.active{background-color:#24748c \9}.btn-inverse{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#363636;*background-color:#222;background-image:-moz-linear-gradient(top,#444,#222);background-image:-webkit-gradient(linear,0 0,0 100%,from(#444),to(#222));background-image:-webkit-linear-gradient(top,#444,#222);background-image:-o-linear-gradient(top,#444,#222);background-image:linear-gradient(to bottom,#444,#222);background-repeat:repeat-x;border-color:#222 #222 #000;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff444444',endColorstr='#ff222222',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.btn-inverse:hover,.btn-inverse:active,.btn-inverse.active,.btn-inverse.disabled,.btn-inverse[disabled]{color:#fff;background-color:#222;*background-color:#151515}.btn-inverse:active,.btn-inverse.active{background-color:#080808 \9}button.btn,input[type="submit"].btn{*padding-top:3px;*padding-bottom:3px}button.btn::-moz-focus-inner,input[type="submit"].btn::-moz-focus-inner{padding:0;border:0}button.btn.btn-large,input[type="submit"].btn.btn-large{*padding-top:7px;*padding-bottom:7px}button.btn.btn-small,input[type="submit"].btn.btn-small{*padding-top:3px;*padding-bottom:3px}button.btn.btn-mini,input[type="submit"].btn.btn-mini{*padding-top:1px;*padding-bottom:1px}.btn-link,.btn-link:active,.btn-link[disabled]{background-color:transparent;background-image:none;-webkit-box-shadow:none;-moz-box-shadow:none;box-shadow:none}.btn-link{color:#08c;cursor:pointer;border-color:transparent;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-link:hover{color:#005580;text-decoration:underline;background-color:transparent}.btn-link[disabled]:hover{color:#333;text-decoration:none}.btn-group{position:relative;display:inline-block;*display:inline;*margin-left:.3em;font-size:0;white-space:nowrap;vertical-align:middle;*zoom:1}.btn-group:first-child{*margin-left:0}.btn-group+.btn-group{margin-left:5px}.btn-toolbar{margin-top:10px;margin-bottom:10px;font-size:0}.btn-toolbar .btn+.btn,.btn-toolbar .btn-group+.btn,.btn-toolbar .btn+.btn-group{margin-left:5px}.btn-group>.btn{position:relative;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-group>.btn+.btn{margin-left:-1px}.btn-group>.btn,.btn-group>.dropdown-menu{font-size:14px}.btn-group>.btn-mini{font-size:11px}.btn-group>.btn-small{font-size:12px}.btn-group>.btn-large{font-size:16px}.btn-group>.btn:first-child{margin-left:0;-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-bottomleft:4px;-moz-border-radius-topleft:4px}.btn-group>.btn:last-child,.btn-group>.dropdown-toggle{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-moz-border-radius-topright:4px;-moz-border-radius-bottomright:4px}.btn-group>.btn.large:first-child{margin-left:0;-webkit-border-bottom-left-radius:6px;border-bottom-left-radius:6px;-webkit-border-top-left-radius:6px;border-top-left-radius:6px;-moz-border-radius-bottomleft:6px;-moz-border-radius-topleft:6px}.btn-group>.btn.large:last-child,.btn-group>.large.dropdown-toggle{-webkit-border-top-right-radius:6px;border-top-right-radius:6px;-webkit-border-bottom-right-radius:6px;border-bottom-right-radius:6px;-moz-border-radius-topright:6px;-moz-border-radius-bottomright:6px}.btn-group>.btn:hover,.btn-group>.btn:focus,.btn-group>.btn:active,.btn-group>.btn.active{z-index:2}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{*padding-top:5px;padding-right:8px;*padding-bottom:5px;padding-left:8px;-webkit-box-shadow:inset 1px 0 0 rgba(255,255,255,0.125),inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 1px 0 0 rgba(255,255,255,0.125),inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 1px 0 0 rgba(255,255,255,0.125),inset 0 1px 0 rgba(255,255,255,0.2),0 1px 2px rgba(0,0,0,0.05)}.btn-group>.btn-mini+.dropdown-toggle{*padding-top:2px;padding-right:5px;*padding-bottom:2px;padding-left:5px}.btn-group>.btn-small+.dropdown-toggle{*padding-top:5px;*padding-bottom:4px}.btn-group>.btn-large+.dropdown-toggle{*padding-top:7px;padding-right:12px;*padding-bottom:7px;padding-left:12px}.btn-group.open .dropdown-toggle{background-image:none;-webkit-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05);box-shadow:inset 0 2px 4px rgba(0,0,0,0.15),0 1px 2px rgba(0,0,0,0.05)}.btn-group.open .btn.dropdown-toggle{background-color:#e6e6e6}.btn-group.open .btn-primary.dropdown-toggle{background-color:#04c}.btn-group.open .btn-warning.dropdown-toggle{background-color:#f89406}.btn-group.open .btn-danger.dropdown-toggle{background-color:#bd362f}.btn-group.open .btn-success.dropdown-toggle{background-color:#51a351}.btn-group.open .btn-info.dropdown-toggle{background-color:#2f96b4}.btn-group.open .btn-inverse.dropdown-toggle{background-color:#222}.btn .caret{margin-top:8px;margin-left:0}.btn-mini .caret,.btn-small .caret,.btn-large .caret{margin-top:6px}.btn-large .caret{border-top-width:5px;border-right-width:5px;border-left-width:5px}.dropup .btn-large .caret{border-bottom-width:5px}.btn-primary .caret,.btn-warning .caret,.btn-danger .caret,.btn-info .caret,.btn-success .caret,.btn-inverse .caret{border-top-color:#fff;border-bottom-color:#fff}.btn-group-vertical{display:inline-block;*display:inline;*zoom:1}.btn-group-vertical .btn{display:block;float:none;width:100%;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.btn-group-vertical .btn+.btn{margin-top:-1px;margin-left:0}.btn-group-vertical .btn:first-child{-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0}.btn-group-vertical .btn:last-child{-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px}.btn-group-vertical .btn-large:first-child{-webkit-border-radius:6px 6px 0 0;-moz-border-radius:6px 6px 0 0;border-radius:6px 6px 0 0}.btn-group-vertical .btn-large:last-child{-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px}.alert{padding:8px 35px 8px 14px;margin-bottom:20px;color:#c09853;text-shadow:0 1px 0 rgba(255,255,255,0.5);background-color:#fcf8e3;border:1px solid #fbeed5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.alert h4{margin:0}.alert .close{position:relative;top:-2px;right:-21px;line-height:20px}.alert-success{color:#468847;background-color:#dff0d8;border-color:#d6e9c6}.alert-danger,.alert-error{color:#b94a48;background-color:#f2dede;border-color:#eed3d7}.alert-info{color:#3a87ad;background-color:#d9edf7;border-color:#bce8f1}.alert-block{padding-top:14px;padding-bottom:14px}.alert-block>p,.alert-block>ul{margin-bottom:0}.alert-block p+p{margin-top:5px}.nav{margin-bottom:20px;margin-left:0;list-style:none}.nav>li>a{display:block}.nav>li>a:hover{text-decoration:none;background-color:#eee}.nav>.pull-right{float:right}.nav-header{display:block;padding:3px 15px;font-size:11px;font-weight:bold;line-height:20px;color:#999;text-shadow:0 1px 0 rgba(255,255,255,0.5);text-transform:uppercase}.nav li+.nav-header{margin-top:9px}.nav-list{padding-right:15px;padding-left:15px;margin-bottom:0}.nav-list>li>a,.nav-list .nav-header{margin-right:-15px;margin-left:-15px;text-shadow:0 1px 0 rgba(255,255,255,0.5)}.nav-list>li>a{padding:3px 15px}.nav-list>.active>a,.nav-list>.active>a:hover{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.2);background-color:#08c}.nav-list [class^="icon-"],.nav-list [class*=" icon-"]{margin-right:2px}.nav-list .divider{*width:100%;height:1px;margin:9px 1px;*margin:-5px 0 5px;overflow:hidden;background-color:#e5e5e5;border-bottom:1px solid #fff}.nav-tabs,.nav-pills{*zoom:1}.nav-tabs:before,.nav-pills:before,.nav-tabs:after,.nav-pills:after{display:table;line-height:0;content:""}.nav-tabs:after,.nav-pills:after{clear:both}.nav-tabs>li,.nav-pills>li{float:left}.nav-tabs>li>a,.nav-pills>li>a{padding-right:12px;padding-left:12px;margin-right:2px;line-height:14px}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{margin-bottom:-1px}.nav-tabs>li>a{padding-top:8px;padding-bottom:8px;line-height:20px;border:1px solid transparent;-webkit-border-radius:4px 4px 0 0;-moz-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover{border-color:#eee #eee #ddd}.nav-tabs>.active>a,.nav-tabs>.active>a:hover{color:#555;cursor:default;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent}.nav-pills>li>a{padding-top:8px;padding-bottom:8px;margin-top:2px;margin-bottom:2px;-webkit-border-radius:5px;-moz-border-radius:5px;border-radius:5px}.nav-pills>.active>a,.nav-pills>.active>a:hover{color:#fff;background-color:#08c}.nav-stacked>li{float:none}.nav-stacked>li>a{margin-right:0}.nav-tabs.nav-stacked{border-bottom:0}.nav-tabs.nav-stacked>li>a{border:1px solid #ddd;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.nav-tabs.nav-stacked>li:first-child>a{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-topright:4px;-moz-border-radius-topleft:4px}.nav-tabs.nav-stacked>li:last-child>a{-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-moz-border-radius-bottomright:4px;-moz-border-radius-bottomleft:4px}.nav-tabs.nav-stacked>li>a:hover{z-index:2;border-color:#ddd}.nav-pills.nav-stacked>li>a{margin-bottom:3px}.nav-pills.nav-stacked>li:last-child>a{margin-bottom:1px}.nav-tabs .dropdown-menu{-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px}.nav-pills .dropdown-menu{-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.nav .dropdown-toggle .caret{margin-top:6px;border-top-color:#08c;border-bottom-color:#08c}.nav .dropdown-toggle:hover .caret{border-top-color:#005580;border-bottom-color:#005580}.nav-tabs .dropdown-toggle .caret{margin-top:8px}.nav .active .dropdown-toggle .caret{border-top-color:#fff;border-bottom-color:#fff}.nav-tabs .active .dropdown-toggle .caret{border-top-color:#555;border-bottom-color:#555}.nav>.dropdown.active>a:hover{cursor:pointer}.nav-tabs .open .dropdown-toggle,.nav-pills .open .dropdown-toggle,.nav>li.dropdown.open.active>a:hover{color:#fff;background-color:#999;border-color:#999}.nav li.dropdown.open .caret,.nav li.dropdown.open.active .caret,.nav li.dropdown.open a:hover .caret{border-top-color:#fff;border-bottom-color:#fff;opacity:1;filter:alpha(opacity=100)}.tabs-stacked .open>a:hover{border-color:#999}.tabbable{*zoom:1}.tabbable:before,.tabbable:after{display:table;line-height:0;content:""}.tabbable:after{clear:both}.tab-content{overflow:auto}.tabs-below>.nav-tabs,.tabs-right>.nav-tabs,.tabs-left>.nav-tabs{border-bottom:0}.tab-content>.tab-pane,.pill-content>.pill-pane{display:none}.tab-content>.active,.pill-content>.active{display:block}.tabs-below>.nav-tabs{border-top:1px solid #ddd}.tabs-below>.nav-tabs>li{margin-top:-1px;margin-bottom:0}.tabs-below>.nav-tabs>li>a{-webkit-border-radius:0 0 4px 4px;-moz-border-radius:0 0 4px 4px;border-radius:0 0 4px 4px}.tabs-below>.nav-tabs>li>a:hover{border-top-color:#ddd;border-bottom-color:transparent}.tabs-below>.nav-tabs>.active>a,.tabs-below>.nav-tabs>.active>a:hover{border-color:transparent #ddd #ddd #ddd}.tabs-left>.nav-tabs>li,.tabs-right>.nav-tabs>li{float:none}.tabs-left>.nav-tabs>li>a,.tabs-right>.nav-tabs>li>a{min-width:74px;margin-right:0;margin-bottom:3px}.tabs-left>.nav-tabs{float:left;margin-right:19px;border-right:1px solid #ddd}.tabs-left>.nav-tabs>li>a{margin-right:-1px;-webkit-border-radius:4px 0 0 4px;-moz-border-radius:4px 0 0 4px;border-radius:4px 0 0 4px}.tabs-left>.nav-tabs>li>a:hover{border-color:#eee #ddd #eee #eee}.tabs-left>.nav-tabs .active>a,.tabs-left>.nav-tabs .active>a:hover{border-color:#ddd transparent #ddd #ddd;*border-right-color:#fff}.tabs-right>.nav-tabs{float:right;margin-left:19px;border-left:1px solid #ddd}.tabs-right>.nav-tabs>li>a{margin-left:-1px;-webkit-border-radius:0 4px 4px 0;-moz-border-radius:0 4px 4px 0;border-radius:0 4px 4px 0}.tabs-right>.nav-tabs>li>a:hover{border-color:#eee #eee #eee #ddd}.tabs-right>.nav-tabs .active>a,.tabs-right>.nav-tabs .active>a:hover{border-color:#ddd #ddd #ddd transparent;*border-left-color:#fff}.nav>.disabled>a{color:#999}.nav>.disabled>a:hover{text-decoration:none;cursor:default;background-color:transparent}.navbar{*position:relative;*z-index:2;margin-bottom:20px;overflow:visible;color:#777}.navbar-inner{min-height:40px;padding-right:20px;padding-left:20px;background-color:#fafafa;background-image:-moz-linear-gradient(top,#fff,#f2f2f2);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fff),to(#f2f2f2));background-image:-webkit-linear-gradient(top,#fff,#f2f2f2);background-image:-o-linear-gradient(top,#fff,#f2f2f2);background-image:linear-gradient(to bottom,#fff,#f2f2f2);background-repeat:repeat-x;border:1px solid #d4d4d4;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff',endColorstr='#fff2f2f2',GradientType=0);*zoom:1;-webkit-box-shadow:0 1px 4px rgba(0,0,0,0.065);-moz-box-shadow:0 1px 4px rgba(0,0,0,0.065);box-shadow:0 1px 4px rgba(0,0,0,0.065)}.navbar-inner:before,.navbar-inner:after{display:table;line-height:0;content:""}.navbar-inner:after{clear:both}.navbar .container{width:auto}.nav-collapse.collapse{height:auto;overflow:visible}.navbar .brand{display:block;float:left;padding:10px 20px 10px;margin-left:-20px;font-size:20px;font-weight:200;color:#777;text-shadow:0 1px 0 #fff}.navbar .brand:hover{text-decoration:none}.navbar-text{margin-bottom:0;line-height:40px}.navbar-link{color:#777}.navbar-link:hover{color:#333}.navbar .divider-vertical{height:40px;margin:0 9px;border-right:1px solid #fff;border-left:1px solid #f2f2f2}.navbar .btn,.navbar .btn-group{margin-top:5px}.navbar .btn-group .btn,.navbar .input-prepend .btn,.navbar .input-append .btn{margin-top:0}.navbar-form{margin-bottom:0;*zoom:1}.navbar-form:before,.navbar-form:after{display:table;line-height:0;content:""}.navbar-form:after{clear:both}.navbar-form input,.navbar-form select,.navbar-form .radio,.navbar-form .checkbox{margin-top:5px}.navbar-form input,.navbar-form select,.navbar-form .btn{display:inline-block;margin-bottom:0}.navbar-form input[type="image"],.navbar-form input[type="checkbox"],.navbar-form input[type="radio"]{margin-top:3px}.navbar-form .input-append,.navbar-form .input-prepend{margin-top:6px;white-space:nowrap}.navbar-form .input-append input,.navbar-form .input-prepend input{margin-top:0}.navbar-search{position:relative;float:left;margin-top:5px;margin-bottom:0}.navbar-search .search-query{padding:4px 14px;margin-bottom:0;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:13px;font-weight:normal;line-height:1;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px}.navbar-static-top{position:static;margin-bottom:0}.navbar-static-top .navbar-inner{-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.navbar-fixed-top,.navbar-fixed-bottom{position:fixed;right:0;left:0;z-index:1030;margin-bottom:0}.navbar-fixed-top .navbar-inner,.navbar-static-top .navbar-inner{border-width:0 0 1px}.navbar-fixed-bottom .navbar-inner{border-width:1px 0 0}.navbar-fixed-top .navbar-inner,.navbar-fixed-bottom .navbar-inner{padding-right:0;padding-left:0;-webkit-border-radius:0;-moz-border-radius:0;border-radius:0}.navbar-static-top .container,.navbar-fixed-top .container,.navbar-fixed-bottom .container{width:940px}.navbar-fixed-top{top:0}.navbar-fixed-top .navbar-inner,.navbar-static-top .navbar-inner{-webkit-box-shadow:0 1px 10px rgba(0,0,0,0.1);-moz-box-shadow:0 1px 10px rgba(0,0,0,0.1);box-shadow:0 1px 10px rgba(0,0,0,0.1)}.navbar-fixed-bottom{bottom:0}.navbar-fixed-bottom .navbar-inner{-webkit-box-shadow:0 -1px 10px rgba(0,0,0,0.1);-moz-box-shadow:0 -1px 10px rgba(0,0,0,0.1);box-shadow:0 -1px 10px rgba(0,0,0,0.1)}.navbar .nav{position:relative;left:0;display:block;float:left;margin:0 10px 0 0}.navbar .nav.pull-right{float:right;margin-right:0}.navbar .nav>li{float:left}.navbar .nav>li>a{float:none;padding:10px 15px 10px;color:#777;text-decoration:none;text-shadow:0 1px 0 #fff}.navbar .nav .dropdown-toggle .caret{margin-top:8px}.navbar .nav>li>a:focus,.navbar .nav>li>a:hover{color:#333;text-decoration:none;background-color:transparent}.navbar .nav>.active>a,.navbar .nav>.active>a:hover,.navbar .nav>.active>a:focus{color:#555;text-decoration:none;background-color:#e5e5e5;-webkit-box-shadow:inset 0 3px 8px rgba(0,0,0,0.125);-moz-box-shadow:inset 0 3px 8px rgba(0,0,0,0.125);box-shadow:inset 0 3px 8px rgba(0,0,0,0.125)}.navbar .btn-navbar{display:none;float:right;padding:7px 10px;margin-right:5px;margin-left:5px;color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#ededed;*background-color:#e5e5e5;background-image:-moz-linear-gradient(top,#f2f2f2,#e5e5e5);background-image:-webkit-gradient(linear,0 0,0 100%,from(#f2f2f2),to(#e5e5e5));background-image:-webkit-linear-gradient(top,#f2f2f2,#e5e5e5);background-image:-o-linear-gradient(top,#f2f2f2,#e5e5e5);background-image:linear-gradient(to bottom,#f2f2f2,#e5e5e5);background-repeat:repeat-x;border-color:#e5e5e5 #e5e5e5 #bfbfbf;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2f2f2',endColorstr='#ffe5e5e5',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.075);-moz-box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.075);box-shadow:inset 0 1px 0 rgba(255,255,255,0.1),0 1px 0 rgba(255,255,255,0.075)}.navbar .btn-navbar:hover,.navbar .btn-navbar:active,.navbar .btn-navbar.active,.navbar .btn-navbar.disabled,.navbar .btn-navbar[disabled]{color:#fff;background-color:#e5e5e5;*background-color:#d9d9d9}.navbar .btn-navbar:active,.navbar .btn-navbar.active{background-color:#ccc \9}.navbar .btn-navbar .icon-bar{display:block;width:18px;height:2px;background-color:#f5f5f5;-webkit-border-radius:1px;-moz-border-radius:1px;border-radius:1px;-webkit-box-shadow:0 1px 0 rgba(0,0,0,0.25);-moz-box-shadow:0 1px 0 rgba(0,0,0,0.25);box-shadow:0 1px 0 rgba(0,0,0,0.25)}.btn-navbar .icon-bar+.icon-bar{margin-top:3px}.navbar .nav>li>.dropdown-menu:before{position:absolute;top:-7px;left:9px;display:inline-block;border-right:7px solid transparent;border-bottom:7px solid #ccc;border-left:7px solid transparent;border-bottom-color:rgba(0,0,0,0.2);content:''}.navbar .nav>li>.dropdown-menu:after{position:absolute;top:-6px;left:10px;display:inline-block;border-right:6px solid transparent;border-bottom:6px solid #fff;border-left:6px solid transparent;content:''}.navbar-fixed-bottom .nav>li>.dropdown-menu:before{top:auto;bottom:-7px;border-top:7px solid #ccc;border-bottom:0;border-top-color:rgba(0,0,0,0.2)}.navbar-fixed-bottom .nav>li>.dropdown-menu:after{top:auto;bottom:-6px;border-top:6px solid #fff;border-bottom:0}.navbar .nav li.dropdown.open>.dropdown-toggle,.navbar .nav li.dropdown.active>.dropdown-toggle,.navbar .nav li.dropdown.open.active>.dropdown-toggle{color:#555;background-color:#e5e5e5}.navbar .nav li.dropdown>.dropdown-toggle .caret{border-top-color:#777;border-bottom-color:#777}.navbar .nav li.dropdown.open>.dropdown-toggle .caret,.navbar .nav li.dropdown.active>.dropdown-toggle .caret,.navbar .nav li.dropdown.open.active>.dropdown-toggle .caret{border-top-color:#555;border-bottom-color:#555}.navbar .pull-right>li>.dropdown-menu,.navbar .nav>li>.dropdown-menu.pull-right{right:0;left:auto}.navbar .pull-right>li>.dropdown-menu:before,.navbar .nav>li>.dropdown-menu.pull-right:before{right:12px;left:auto}.navbar .pull-right>li>.dropdown-menu:after,.navbar .nav>li>.dropdown-menu.pull-right:after{right:13px;left:auto}.navbar .pull-right>li>.dropdown-menu .dropdown-menu,.navbar .nav>li>.dropdown-menu.pull-right .dropdown-menu{right:100%;left:auto;margin-right:-1px;margin-left:0;-webkit-border-radius:6px 0 6px 6px;-moz-border-radius:6px 0 6px 6px;border-radius:6px 0 6px 6px}.navbar-inverse{color:#999}.navbar-inverse .navbar-inner{background-color:#1b1b1b;background-image:-moz-linear-gradient(top,#222,#111);background-image:-webkit-gradient(linear,0 0,0 100%,from(#222),to(#111));background-image:-webkit-linear-gradient(top,#222,#111);background-image:-o-linear-gradient(top,#222,#111);background-image:linear-gradient(to bottom,#222,#111);background-repeat:repeat-x;border-color:#252525;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff222222',endColorstr='#ff111111',GradientType=0)}.navbar-inverse .brand,.navbar-inverse .nav>li>a{color:#999;text-shadow:0 -1px 0 rgba(0,0,0,0.25)}.navbar-inverse .brand:hover,.navbar-inverse .nav>li>a:hover{color:#fff}.navbar-inverse .nav>li>a:focus,.navbar-inverse .nav>li>a:hover{color:#fff;background-color:transparent}.navbar-inverse .nav .active>a,.navbar-inverse .nav .active>a:hover,.navbar-inverse .nav .active>a:focus{color:#fff;background-color:#111}.navbar-inverse .navbar-link{color:#999}.navbar-inverse .navbar-link:hover{color:#fff}.navbar-inverse .divider-vertical{border-right-color:#222;border-left-color:#111}.navbar-inverse .nav li.dropdown.open>.dropdown-toggle,.navbar-inverse .nav li.dropdown.active>.dropdown-toggle,.navbar-inverse .nav li.dropdown.open.active>.dropdown-toggle{color:#fff;background-color:#111}.navbar-inverse .nav li.dropdown>.dropdown-toggle .caret{border-top-color:#999;border-bottom-color:#999}.navbar-inverse .nav li.dropdown.open>.dropdown-toggle .caret,.navbar-inverse .nav li.dropdown.active>.dropdown-toggle .caret,.navbar-inverse .nav li.dropdown.open.active>.dropdown-toggle .caret{border-top-color:#fff;border-bottom-color:#fff}.navbar-inverse .navbar-search .search-query{color:#fff;background-color:#515151;border-color:#111;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1),0 1px 0 rgba(255,255,255,0.15);-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1),0 1px 0 rgba(255,255,255,0.15);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1),0 1px 0 rgba(255,255,255,0.15);-webkit-transition:none;-moz-transition:none;-o-transition:none;transition:none}.navbar-inverse .navbar-search .search-query:-moz-placeholder{color:#ccc}.navbar-inverse .navbar-search .search-query:-ms-input-placeholder{color:#ccc}.navbar-inverse .navbar-search .search-query::-webkit-input-placeholder{color:#ccc}.navbar-inverse .navbar-search .search-query:focus,.navbar-inverse .navbar-search .search-query.focused{padding:5px 15px;color:#333;text-shadow:0 1px 0 #fff;background-color:#fff;border:0;outline:0;-webkit-box-shadow:0 0 3px rgba(0,0,0,0.15);-moz-box-shadow:0 0 3px rgba(0,0,0,0.15);box-shadow:0 0 3px rgba(0,0,0,0.15)}.navbar-inverse .btn-navbar{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#0e0e0e;*background-color:#040404;background-image:-moz-linear-gradient(top,#151515,#040404);background-image:-webkit-gradient(linear,0 0,0 100%,from(#151515),to(#040404));background-image:-webkit-linear-gradient(top,#151515,#040404);background-image:-o-linear-gradient(top,#151515,#040404);background-image:linear-gradient(to bottom,#151515,#040404);background-repeat:repeat-x;border-color:#040404 #040404 #000;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.25);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff151515',endColorstr='#ff040404',GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false)}.navbar-inverse .btn-navbar:hover,.navbar-inverse .btn-navbar:active,.navbar-inverse .btn-navbar.active,.navbar-inverse .btn-navbar.disabled,.navbar-inverse .btn-navbar[disabled]{color:#fff;background-color:#040404;*background-color:#000}.navbar-inverse .btn-navbar:active,.navbar-inverse .btn-navbar.active{background-color:#000 \9}.breadcrumb{padding:8px 15px;margin:0 0 20px;list-style:none;background-color:#f5f5f5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.breadcrumb li{display:inline-block;*display:inline;text-shadow:0 1px 0 #fff;*zoom:1}.breadcrumb .divider{padding:0 5px;color:#ccc}.breadcrumb .active{color:#999}.pagination{margin:20px 0}.pagination ul{display:inline-block;*display:inline;margin-bottom:0;margin-left:0;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;*zoom:1;-webkit-box-shadow:0 1px 2px rgba(0,0,0,0.05);-moz-box-shadow:0 1px 2px rgba(0,0,0,0.05);box-shadow:0 1px 2px rgba(0,0,0,0.05)}.pagination ul>li{display:inline}.pagination ul>li>a,.pagination ul>li>span{float:left;padding:4px 12px;line-height:20px;text-decoration:none;background-color:#fff;border:1px solid #ddd;border-left-width:0}.pagination ul>li>a:hover,.pagination ul>.active>a,.pagination ul>.active>span{background-color:#f5f5f5}.pagination ul>.active>a,.pagination ul>.active>span{color:#999;cursor:default}.pagination ul>.disabled>span,.pagination ul>.disabled>a,.pagination ul>.disabled>a:hover{color:#999;cursor:default;background-color:transparent}.pagination ul>li:first-child>a,.pagination ul>li:first-child>span{border-left-width:1px;-webkit-border-bottom-left-radius:4px;border-bottom-left-radius:4px;-webkit-border-top-left-radius:4px;border-top-left-radius:4px;-moz-border-radius-bottomleft:4px;-moz-border-radius-topleft:4px}.pagination ul>li:last-child>a,.pagination ul>li:last-child>span{-webkit-border-top-right-radius:4px;border-top-right-radius:4px;-webkit-border-bottom-right-radius:4px;border-bottom-right-radius:4px;-moz-border-radius-topright:4px;-moz-border-radius-bottomright:4px}.pagination-centered{text-align:center}.pagination-right{text-align:right}.pagination-large ul>li>a,.pagination-large ul>li>span{padding:11px 19px;font-size:17.5px}.pagination-large ul>li:first-child>a,.pagination-large ul>li:first-child>span{-webkit-border-bottom-left-radius:6px;border-bottom-left-radius:6px;-webkit-border-top-left-radius:6px;border-top-left-radius:6px;-moz-border-radius-bottomleft:6px;-moz-border-radius-topleft:6px}.pagination-large ul>li:last-child>a,.pagination-large ul>li:last-child>span{-webkit-border-top-right-radius:6px;border-top-right-radius:6px;-webkit-border-bottom-right-radius:6px;border-bottom-right-radius:6px;-moz-border-radius-topright:6px;-moz-border-radius-bottomright:6px}.pagination-mini ul>li:first-child>a,.pagination-small ul>li:first-child>a,.pagination-mini ul>li:first-child>span,.pagination-small ul>li:first-child>span{-webkit-border-bottom-left-radius:3px;border-bottom-left-radius:3px;-webkit-border-top-left-radius:3px;border-top-left-radius:3px;-moz-border-radius-bottomleft:3px;-moz-border-radius-topleft:3px}.pagination-mini ul>li:last-child>a,.pagination-small ul>li:last-child>a,.pagination-mini ul>li:last-child>span,.pagination-small ul>li:last-child>span{-webkit-border-top-right-radius:3px;border-top-right-radius:3px;-webkit-border-bottom-right-radius:3px;border-bottom-right-radius:3px;-moz-border-radius-topright:3px;-moz-border-radius-bottomright:3px}.pagination-small ul>li>a,.pagination-small ul>li>span{padding:2px 10px;font-size:11.9px}.pagination-mini ul>li>a,.pagination-mini ul>li>span{padding:1px 6px;font-size:10.5px}.pager{margin:20px 0;text-align:center;list-style:none;*zoom:1}.pager:before,.pager:after{display:table;line-height:0;content:""}.pager:after{clear:both}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;-webkit-border-radius:15px;-moz-border-radius:15px;border-radius:15px}.pager li>a:hover{text-decoration:none;background-color:#f5f5f5}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:hover,.pager .disabled>span{color:#999;cursor:default;background-color:#fff}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000}.modal-backdrop.fade{opacity:0}.modal-backdrop,.modal-backdrop.fade.in{opacity:.8;filter:alpha(opacity=80)}.modal{position:fixed;top:50%;left:50%;z-index:1050;width:560px;margin:-250px 0 0 -280px;background-color:#fff;border:1px solid #999;border:1px solid rgba(0,0,0,0.3);*border:1px solid #999;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;outline:0;-webkit-box-shadow:0 3px 7px rgba(0,0,0,0.3);-moz-box-shadow:0 3px 7px rgba(0,0,0,0.3);box-shadow:0 3px 7px rgba(0,0,0,0.3);-webkit-background-clip:padding-box;-moz-background-clip:padding-box;background-clip:padding-box}.modal.fade{top:-25%;-webkit-transition:opacity .3s linear,top .3s ease-out;-moz-transition:opacity .3s linear,top .3s ease-out;-o-transition:opacity .3s linear,top .3s ease-out;transition:opacity .3s linear,top .3s ease-out}.modal.fade.in{top:50%}.modal-header{padding:9px 15px;border-bottom:1px solid #eee}.modal-header .close{margin-top:2px}.modal-header h3{margin:0;line-height:30px}.modal-body{max-height:400px;padding:15px;overflow-y:auto}.modal-form{margin-bottom:0}.modal-footer{padding:14px 15px 15px;margin-bottom:0;text-align:right;background-color:#f5f5f5;border-top:1px solid #ddd;-webkit-border-radius:0 0 6px 6px;-moz-border-radius:0 0 6px 6px;border-radius:0 0 6px 6px;*zoom:1;-webkit-box-shadow:inset 0 1px 0 #fff;-moz-box-shadow:inset 0 1px 0 #fff;box-shadow:inset 0 1px 0 #fff}.modal-footer:before,.modal-footer:after{display:table;line-height:0;content:""}.modal-footer:after{clear:both}.modal-footer .btn+.btn{margin-bottom:0;margin-left:5px}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}.tooltip{position:absolute;z-index:1030;display:block;padding:5px;font-size:11px;opacity:0;filter:alpha(opacity=0);visibility:visible}.tooltip.in{opacity:.8;filter:alpha(opacity=80)}.tooltip.top{margin-top:-3px}.tooltip.right{margin-left:3px}.tooltip.bottom{margin-top:3px}.tooltip.left{margin-left:-3px}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;text-decoration:none;background-color:#000;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-top-color:#000;border-width:5px 5px 0}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-right-color:#000;border-width:5px 5px 5px 0}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-left-color:#000;border-width:5px 0 5px 5px}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-bottom-color:#000;border-width:0 5px 5px}.popover{position:absolute;top:0;left:0;z-index:1010;display:none;width:236px;padding:1px;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,0.2);-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,0.2);-moz-box-shadow:0 5px 10px rgba(0,0,0,0.2);box-shadow:0 5px 10px rgba(0,0,0,0.2);-webkit-background-clip:padding-box;-moz-background-clip:padding;background-clip:padding-box}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{padding:8px 14px;margin:0;font-size:14px;font-weight:normal;line-height:18px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;-webkit-border-radius:5px 5px 0 0;-moz-border-radius:5px 5px 0 0;border-radius:5px 5px 0 0}.popover-content{padding:9px 14px}.popover-content p,.popover-content ul,.popover-content ol{margin-bottom:0}.popover .arrow,.popover .arrow:after{position:absolute;display:inline-block;width:0;height:0;border-color:transparent;border-style:solid}.popover .arrow:after{z-index:-1;content:""}.popover.top .arrow{bottom:-10px;left:50%;margin-left:-10px;border-top-color:#fff;border-width:10px 10px 0}.popover.top .arrow:after{bottom:-1px;left:-11px;border-top-color:rgba(0,0,0,0.25);border-width:11px 11px 0}.popover.right .arrow{top:50%;left:-10px;margin-top:-10px;border-right-color:#fff;border-width:10px 10px 10px 0}.popover.right .arrow:after{bottom:-11px;left:-1px;border-right-color:rgba(0,0,0,0.25);border-width:11px 11px 11px 0}.popover.bottom .arrow{top:-10px;left:50%;margin-left:-10px;border-bottom-color:#fff;border-width:0 10px 10px}.popover.bottom .arrow:after{top:-1px;left:-11px;border-bottom-color:rgba(0,0,0,0.25);border-width:0 11px 11px}.popover.left .arrow{top:50%;right:-10px;margin-top:-10px;border-left-color:#fff;border-width:10px 0 10px 10px}.popover.left .arrow:after{right:-1px;bottom:-11px;border-left-color:rgba(0,0,0,0.25);border-width:11px 0 11px 11px}.thumbnails{margin-left:-20px;list-style:none;*zoom:1}.thumbnails:before,.thumbnails:after{display:table;line-height:0;content:""}.thumbnails:after{clear:both}.row-fluid .thumbnails{margin-left:0}.thumbnails>li{float:left;margin-bottom:20px;margin-left:20px}.thumbnail{display:block;padding:4px;line-height:20px;border:1px solid #ddd;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;-webkit-box-shadow:0 1px 3px rgba(0,0,0,0.055);-moz-box-shadow:0 1px 3px rgba(0,0,0,0.055);box-shadow:0 1px 3px rgba(0,0,0,0.055);-webkit-transition:all .2s ease-in-out;-moz-transition:all .2s ease-in-out;-o-transition:all .2s ease-in-out;transition:all .2s ease-in-out}a.thumbnail:hover{border-color:#08c;-webkit-box-shadow:0 1px 4px rgba(0,105,214,0.25);-moz-box-shadow:0 1px 4px rgba(0,105,214,0.25);box-shadow:0 1px 4px rgba(0,105,214,0.25)}.thumbnail>img{display:block;max-width:100%;margin-right:auto;margin-left:auto}.thumbnail .caption{padding:9px;color:#555}.media,.media-body{overflow:hidden;*overflow:visible;zoom:1}.media,.media .media{margin-top:15px}.media:first-child{margin-top:0}.media-object{display:block}.media-heading{margin:0 0 5px}.media .pull-left{margin-right:10px}.media .pull-right{margin-left:10px}.media-list{margin-left:0;list-style:none}.label,.badge{display:inline-block;padding:2px 4px;font-size:11.844px;font-weight:bold;line-height:14px;color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.25);white-space:nowrap;vertical-align:baseline;background-color:#999}.label{-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px}.badge{padding-right:9px;padding-left:9px;-webkit-border-radius:9px;-moz-border-radius:9px;border-radius:9px}a.label:hover,a.badge:hover{color:#fff;text-decoration:none;cursor:pointer}.label-important,.badge-important{background-color:#b94a48}.label-important[href],.badge-important[href]{background-color:#953b39}.label-warning,.badge-warning{background-color:#f89406}.label-warning[href],.badge-warning[href]{background-color:#c67605}.label-success,.badge-success{background-color:#468847}.label-success[href],.badge-success[href]{background-color:#356635}.label-info,.badge-info{background-color:#3a87ad}.label-info[href],.badge-info[href]{background-color:#2d6987}.label-inverse,.badge-inverse{background-color:#333}.label-inverse[href],.badge-inverse[href]{background-color:#1a1a1a}.btn .label,.btn .badge{position:relative;top:-1px}.btn-mini .label,.btn-mini .badge{top:0}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-moz-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-ms-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@-o-keyframes progress-bar-stripes{from{background-position:0 0}to{background-position:40px 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{height:20px;margin-bottom:20px;overflow:hidden;background-color:#f7f7f7;background-image:-moz-linear-gradient(top,#f5f5f5,#f9f9f9);background-image:-webkit-gradient(linear,0 0,0 100%,from(#f5f5f5),to(#f9f9f9));background-image:-webkit-linear-gradient(top,#f5f5f5,#f9f9f9);background-image:-o-linear-gradient(top,#f5f5f5,#f9f9f9);background-image:linear-gradient(to bottom,#f5f5f5,#f9f9f9);background-repeat:repeat-x;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5',endColorstr='#fff9f9f9',GradientType=0);-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);-moz-box-shadow:inset 0 1px 2px rgba(0,0,0,0.1);box-shadow:inset 0 1px 2px rgba(0,0,0,0.1)}.progress .bar{float:left;width:0;height:100%;font-size:12px;color:#fff;text-align:center;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-color:#0e90d2;background-image:-moz-linear-gradient(top,#149bdf,#0480be);background-image:-webkit-gradient(linear,0 0,0 100%,from(#149bdf),to(#0480be));background-image:-webkit-linear-gradient(top,#149bdf,#0480be);background-image:-o-linear-gradient(top,#149bdf,#0480be);background-image:linear-gradient(to bottom,#149bdf,#0480be);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff149bdf',endColorstr='#ff0480be',GradientType=0);-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-moz-box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,0.15);-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;-webkit-transition:width .6s ease;-moz-transition:width .6s ease;-o-transition:width .6s ease;transition:width .6s ease}.progress .bar+.bar{-webkit-box-shadow:inset 1px 0 0 rgba(0,0,0,0.15),inset 0 -1px 0 rgba(0,0,0,0.15);-moz-box-shadow:inset 1px 0 0 rgba(0,0,0,0.15),inset 0 -1px 0 rgba(0,0,0,0.15);box-shadow:inset 1px 0 0 rgba(0,0,0,0.15),inset 0 -1px 0 rgba(0,0,0,0.15)}.progress-striped .bar{background-color:#149bdf;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);-webkit-background-size:40px 40px;-moz-background-size:40px 40px;-o-background-size:40px 40px;background-size:40px 40px}.progress.active .bar{-webkit-animation:progress-bar-stripes 2s linear infinite;-moz-animation:progress-bar-stripes 2s linear infinite;-ms-animation:progress-bar-stripes 2s linear infinite;-o-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-danger .bar,.progress .bar-danger{background-color:#dd514c;background-image:-moz-linear-gradient(top,#ee5f5b,#c43c35);background-image:-webkit-gradient(linear,0 0,0 100%,from(#ee5f5b),to(#c43c35));background-image:-webkit-linear-gradient(top,#ee5f5b,#c43c35);background-image:-o-linear-gradient(top,#ee5f5b,#c43c35);background-image:linear-gradient(to bottom,#ee5f5b,#c43c35);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffee5f5b',endColorstr='#ffc43c35',GradientType=0)}.progress-danger.progress-striped .bar,.progress-striped .bar-danger{background-color:#ee5f5b;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.progress-success .bar,.progress .bar-success{background-color:#5eb95e;background-image:-moz-linear-gradient(top,#62c462,#57a957);background-image:-webkit-gradient(linear,0 0,0 100%,from(#62c462),to(#57a957));background-image:-webkit-linear-gradient(top,#62c462,#57a957);background-image:-o-linear-gradient(top,#62c462,#57a957);background-image:linear-gradient(to bottom,#62c462,#57a957);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff62c462',endColorstr='#ff57a957',GradientType=0)}.progress-success.progress-striped .bar,.progress-striped .bar-success{background-color:#62c462;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.progress-info .bar,.progress .bar-info{background-color:#4bb1cf;background-image:-moz-linear-gradient(top,#5bc0de,#339bb9);background-image:-webkit-gradient(linear,0 0,0 100%,from(#5bc0de),to(#339bb9));background-image:-webkit-linear-gradient(top,#5bc0de,#339bb9);background-image:-o-linear-gradient(top,#5bc0de,#339bb9);background-image:linear-gradient(to bottom,#5bc0de,#339bb9);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de',endColorstr='#ff339bb9',GradientType=0)}.progress-info.progress-striped .bar,.progress-striped .bar-info{background-color:#5bc0de;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.progress-warning .bar,.progress .bar-warning{background-color:#faa732;background-image:-moz-linear-gradient(top,#fbb450,#f89406);background-image:-webkit-gradient(linear,0 0,0 100%,from(#fbb450),to(#f89406));background-image:-webkit-linear-gradient(top,#fbb450,#f89406);background-image:-o-linear-gradient(top,#fbb450,#f89406);background-image:linear-gradient(to bottom,#fbb450,#f89406);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffbb450',endColorstr='#fff89406',GradientType=0)}.progress-warning.progress-striped .bar,.progress-striped .bar-warning{background-color:#fbb450;background-image:-webkit-gradient(linear,0 100%,100% 0,color-stop(0.25,rgba(255,255,255,0.15)),color-stop(0.25,transparent),color-stop(0.5,transparent),color-stop(0.5,rgba(255,255,255,0.15)),color-stop(0.75,rgba(255,255,255,0.15)),color-stop(0.75,transparent),to(transparent));background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-moz-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,0.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,0.15) 50%,rgba(255,255,255,0.15) 75%,transparent 75%,transparent)}.accordion{margin-bottom:20px}.accordion-group{margin-bottom:2px;border:1px solid #e5e5e5;-webkit-border-radius:4px;-moz-border-radius:4px;border-radius:4px}.accordion-heading{border-bottom:0}.accordion-heading .accordion-toggle{display:block;padding:8px 15px}.accordion-toggle{cursor:pointer}.accordion-inner{padding:9px 15px;border-top:1px solid #e5e5e5}.carousel{position:relative;margin-bottom:20px;line-height:1}.carousel-inner{position:relative;width:100%;overflow:hidden}.carousel .item{position:relative;display:none;-webkit-transition:.6s ease-in-out left;-moz-transition:.6s ease-in-out left;-o-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel .item>img{display:block;line-height:1}.carousel .active,.carousel .next,.carousel .prev{display:block}.carousel .active{left:0}.carousel .next,.carousel .prev{position:absolute;top:0;width:100%}.carousel .next{left:100%}.carousel .prev{left:-100%}.carousel .next.left,.carousel .prev.right{left:0}.carousel .active.left{left:-100%}.carousel .active.right{left:100%}.carousel-control{position:absolute;top:40%;left:15px;width:40px;height:40px;margin-top:-20px;font-size:60px;font-weight:100;line-height:30px;color:#fff;text-align:center;background:#222;border:3px solid #fff;-webkit-border-radius:23px;-moz-border-radius:23px;border-radius:23px;opacity:.5;filter:alpha(opacity=50)}.carousel-control.right{right:15px;left:auto}.carousel-control:hover{color:#fff;text-decoration:none;opacity:.9;filter:alpha(opacity=90)}.carousel-caption{position:absolute;right:0;bottom:0;left:0;padding:15px;background:#333;background:rgba(0,0,0,0.75)}.carousel-caption h4,.carousel-caption p{line-height:20px;color:#fff}.carousel-caption h4{margin:0 0 5px}.carousel-caption p{margin-bottom:0}.hero-unit{padding:60px;margin-bottom:30px;font-size:18px;font-weight:200;line-height:30px;color:inherit;background-color:#eee;-webkit-border-radius:6px;-moz-border-radius:6px;border-radius:6px}.hero-unit h1{margin-bottom:0;font-size:60px;line-height:1;letter-spacing:-1px;color:inherit}.hero-unit li{line-height:30px}.pull-right{float:right}.pull-left{float:left}.hide{display:none}.show{display:block}.invisible{visibility:hidden}.affix{position:fixed}
diff --git a/src/site/resources/css/github.css b/src/site/resources/css/github.css
deleted file mode 100644
index de1d2bb..0000000
--- a/src/site/resources/css/github.css
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
-
- github.com style (c) Vasily Polovnyov <vast@whiteants.net>
-
- Licensed to the Apache Software Foundation (ASF) under one or more
- contributor license agreements. See the NOTICE file distributed with
- this work for additional information regarding copyright ownership.
- The ASF licenses this file to You under the Apache License, Version 2.0
- (the "License"); you may not use this file except in compliance with
- the License. You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
-*/
-
-.hljs {
- display: block;
- overflow-x: auto;
- padding: 0.5em;
- color: #333;
- background: #f5f5f5;
-}
-
-.hljs-comment,
-.hljs-quote {
- color: #998;
- font-style: italic;
-}
-
-.hljs-keyword,
-.hljs-selector-tag,
-.hljs-subst {
- color: #333;
- font-weight: bold;
-}
-
-.hljs-number,
-.hljs-literal,
-.hljs-variable,
-.hljs-template-variable,
-.hljs-tag .hljs-attr {
- color: #008080;
-}
-
-.hljs-string,
-.hljs-doctag {
- color: #d14;
-}
-
-.hljs-title,
-.hljs-section,
-.hljs-selector-id {
- color: #900;
- font-weight: bold;
-}
-
-.hljs-subst {
- font-weight: normal;
-}
-
-.hljs-type,
-.hljs-class .hljs-title {
- color: #458;
- font-weight: bold;
-}
-
-.hljs-tag,
-.hljs-name,
-.hljs-attribute {
- color: #000080;
- font-weight: normal;
-}
-
-.hljs-regexp,
-.hljs-link {
- color: #009926;
-}
-
-.hljs-symbol,
-.hljs-bullet {
- color: #990073;
-}
-
-.hljs-built_in,
-.hljs-builtin-name {
- color: #0086b3;
-}
-
-.hljs-meta {
- color: #999;
- font-weight: bold;
-}
-
-.hljs-deletion {
- background: #fdd;
-}
-
-.hljs-addition {
- background: #dfd;
-}
-
-.hljs-emphasis {
- font-style: italic;
-}
-
-.hljs-strong {
- font-weight: bold;
-}
diff --git a/src/site/resources/css/site.css b/src/site/resources/css/site.css
deleted file mode 100644
index 5bda65a..0000000
--- a/src/site/resources/css/site.css
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- Licensed to the Apache Software Foundation (ASF) under one or more
- contributor license agreements. See the NOTICE file distributed with
- this work for additional information regarding copyright ownership.
- The ASF licenses this file to You under the Apache License, Version 2.0
- (the "License"); you may not use this file except in compliance with
- the License. You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
-*/
-
-div.clear { clear:both; visibility: hidden; }
-div.clear hr { display: none; }
-
-/* Tweaks to the bootstrap theme
---------------------------------- */
-li { line-height: 20px; }
-code { color: #4e648e }
-a code { color: #0088cc }
-dt { margin: 15px 0 5px 0; font-size: 1.2em }
-
-.big-red { font-weight: bold; color: #D14 }
-.big-green { font-weight: bold; color: green }
-
-.layout-table { width: 100%; }
-.sidebar { width: 250px; vertical-align: top; }
-.content { padding-left: 20px; vertical-align: top; }
-
-.sidebar-nav { padding: 9px 0; }
-
-.logo-left { margin: 10px; float: left; height: 100px}
-.logo-center {margin-left: auto; margin-right: auto; display: block; width: 20%}
-.logo-right { margin: 5px; float: right; height: 100px}
-
-.navbar .nav { margin-left: 40px; }
-
-.nav-list { margin-bottom: 15px; }
-.nav-list li { line-height: 16px; }
-.nav-list li.nav-header { color: #333; }
-.nav-list li.nav-header i { margin-right: 5px; }
-
-.nav-list li a { background: no-repeat 16px 9px; padding-left: 34px; }
-.nav-list li.collapsed > a { background-image: url(../images/collapsed.gif) }
-.nav-list li.expanded > a { background-image: url(../images/expanded.gif) }
-
-.nav-list li.expanded ul { list-style: none; margin-left: 0; }
-.nav-list li.expanded li a { display: block; padding: 3px 15px 3px 45px; margin-left: -15px; margin-right: -15px; }
-.nav-list li.expanded li a:hover { text-decoration: none; background-color: #eee; }
-.nav-list li.expanded li.active a { background-color: #08C; color: white }
-
-.nav.nav-tabs { margin-bottom: 8px; }
-
-.content .section { margin-top: 20px; }
-.content .section:first-child { margin-top: 0; }
-.section h2 { margin-bottom: 10px; }
-.section h3 { margin-bottom: 10px; }
-.section h4 { margin-bottom: 10px; }
-
-.footer { background-color: whitesmoke; padding: 15px; margin-top: 15px; text-align: center; border-top: 1px solid #eee; }
-.footer p { font-size: 12px; margin: 0 }
-
-.table-not-wide { width: inherit;}
-.alert-heading { display: block; font-size: 14px; margin-bottom: 6px; font-weight: bold; }
-
-/* Additional styles.
------------------------*/
-
-/* add film icons to youtube and vimeo links (.icon-film) */
-a[href^='https://www.youtube.com/']::before,
-a[href^='https://vimeo.com/']::before {
- display: inline-block;
- width: 14px;
- height: 14px;
- margin-top: 1px;
- margin-right: .3em;
- line-height: 14px;
- vertical-align: text-top;
- content: '';
- /* .icon-film */
- background: url("../img/glyphicons-halflings.png") no-repeat -192px 0;
-}
diff --git a/src/site/resources/glyphicons-halflings-2-1.zip b/src/site/resources/glyphicons-halflings-2-1.zip
new file mode 100644
index 0000000..974885b
--- /dev/null
+++ b/src/site/resources/glyphicons-halflings-2-1.zip
Binary files differ
diff --git a/src/site/resources/images/LocationPerf.png b/src/site/resources/images/LocationPerf.png
new file mode 100644
index 0000000..1a232ef
--- /dev/null
+++ b/src/site/resources/images/LocationPerf.png
Binary files differ
diff --git a/src/site/resources/images/kibana.png b/src/site/resources/images/kibana.png
new file mode 100644
index 0000000..3b15693
--- /dev/null
+++ b/src/site/resources/images/kibana.png
Binary files differ
diff --git a/src/site/resources/img/glyphicons/book.png b/src/site/resources/img/glyphicons/book.png
new file mode 100644
index 0000000..5fcb05e
--- /dev/null
+++ b/src/site/resources/img/glyphicons/book.png
Binary files differ
diff --git a/src/site/resources/img/glyphicons/cog.png b/src/site/resources/img/glyphicons/cog.png
new file mode 100644
index 0000000..572b300
--- /dev/null
+++ b/src/site/resources/img/glyphicons/cog.png
Binary files differ
diff --git a/src/site/resources/img/glyphicons/home.png b/src/site/resources/img/glyphicons/home.png
new file mode 100644
index 0000000..11f59e9
--- /dev/null
+++ b/src/site/resources/img/glyphicons/home.png
Binary files differ
diff --git a/src/site/resources/img/glyphicons/info.png b/src/site/resources/img/glyphicons/info.png
new file mode 100644
index 0000000..833cdd5
--- /dev/null
+++ b/src/site/resources/img/glyphicons/info.png
Binary files differ
diff --git a/src/site/resources/img/glyphicons/layers.png b/src/site/resources/img/glyphicons/layers.png
new file mode 100644
index 0000000..d157049
--- /dev/null
+++ b/src/site/resources/img/glyphicons/layers.png
Binary files differ
diff --git a/src/site/resources/img/glyphicons/link.png b/src/site/resources/img/glyphicons/link.png
new file mode 100644
index 0000000..18cf1ec
--- /dev/null
+++ b/src/site/resources/img/glyphicons/link.png
Binary files differ
diff --git a/src/site/resources/img/glyphicons/pencil.png b/src/site/resources/img/glyphicons/pencil.png
new file mode 100644
index 0000000..1f5df55
--- /dev/null
+++ b/src/site/resources/img/glyphicons/pencil.png
Binary files differ
diff --git a/src/site/resources/img/glyphicons/tag.png b/src/site/resources/img/glyphicons/tag.png
new file mode 100644
index 0000000..e189be4
--- /dev/null
+++ b/src/site/resources/img/glyphicons/tag.png
Binary files differ
diff --git a/src/site/resources/js/bootstrap.js b/src/site/resources/js/bootstrap.js
deleted file mode 100644
index 7f303eb..0000000
--- a/src/site/resources/js/bootstrap.js
+++ /dev/null
@@ -1,2027 +0,0 @@
-/* ===================================================
- * bootstrap-transition.js v2.1.0
- * http://twitter.github.com/bootstrap/javascript.html#transitions
- * ===================================================
- * Copyright 2012 Twitter, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- * ========================================================== */
-
-
-!function ($) {
-
- $(function () {
-
- "use strict"; // jshint ;_;
-
-
- /* CSS TRANSITION SUPPORT (http://www.modernizr.com/)
- * ======================================================= */
-
- $.support.transition = (function () {
-
- var transitionEnd = (function () {
-
- var el = document.createElement('bootstrap')
- , transEndEventNames = {
- 'WebkitTransition' : 'webkitTransitionEnd'
- , 'MozTransition' : 'transitionend'
- , 'OTransition' : 'oTransitionEnd otransitionend'
- , 'transition' : 'transitionend'
- }
- , name
-
- for (name in transEndEventNames){
- if (el.style[name] !== undefined) {
- return transEndEventNames[name]
- }
- }
-
- }())
-
- return transitionEnd && {
- end: transitionEnd
- }
-
- })()
-
- })
-
-}(window.jQuery);/* ==========================================================
- * bootstrap-alert.js v2.1.0
- * http://twitter.github.com/bootstrap/javascript.html#alerts
- * ==========================================================
- * Copyright 2012 Twitter, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- * ========================================================== */
-
-
-!function ($) {
-
- "use strict"; // jshint ;_;
-
-
- /* ALERT CLASS DEFINITION
- * ====================== */
-
- var dismiss = '[data-dismiss="alert"]'
- , Alert = function (el) {
- $(el).on('click', dismiss, this.close)
- }
-
- Alert.prototype.close = function (e) {
- var $this = $(this)
- , selector = $this.attr('data-target')
- , $parent
-
- if (!selector) {
- selector = $this.attr('href')
- selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7
- }
-
- $parent = $(selector)
-
- e && e.preventDefault()
-
- $parent.length || ($parent = $this.hasClass('alert') ? $this : $this.parent())
-
- $parent.trigger(e = $.Event('close'))
-
- if (e.isDefaultPrevented()) return
-
- $parent.removeClass('in')
-
- function removeElement() {
- $parent
- .trigger('closed')
- .remove()
- }
-
- $.support.transition && $parent.hasClass('fade') ?
- $parent.on($.support.transition.end, removeElement) :
- removeElement()
- }
-
-
- /* ALERT PLUGIN DEFINITION
- * ======================= */
-
- $.fn.alert = function (option) {
- return this.each(function () {
- var $this = $(this)
- , data = $this.data('alert')
- if (!data) $this.data('alert', (data = new Alert(this)))
- if (typeof option == 'string') data[option].call($this)
- })
- }
-
- $.fn.alert.Constructor = Alert
-
-
- /* ALERT DATA-API
- * ============== */
-
- $(function () {
- $('body').on('click.alert.data-api', dismiss, Alert.prototype.close)
- })
-
-}(window.jQuery);/* ============================================================
- * bootstrap-button.js v2.1.0
- * http://twitter.github.com/bootstrap/javascript.html#buttons
- * ============================================================
- * Copyright 2012 Twitter, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- * ============================================================ */
-
-
-!function ($) {
-
- "use strict"; // jshint ;_;
-
-
- /* BUTTON PUBLIC CLASS DEFINITION
- * ============================== */
-
- var Button = function (element, options) {
- this.$element = $(element)
- this.options = $.extend({}, $.fn.button.defaults, options)
- }
-
- Button.prototype.setState = function (state) {
- var d = 'disabled'
- , $el = this.$element
- , data = $el.data()
- , val = $el.is('input') ? 'val' : 'html'
-
- state = state + 'Text'
- data.resetText || $el.data('resetText', $el[val]())
-
- $el[val](data[state] || this.options[state])
-
- // push to event loop to allow forms to submit
- setTimeout(function () {
- state == 'loadingText' ?
- $el.addClass(d).attr(d, d) :
- $el.removeClass(d).removeAttr(d)
- }, 0)
- }
-
- Button.prototype.toggle = function () {
- var $parent = this.$element.parent('[data-toggle="buttons-radio"]')
-
- $parent && $parent
- .find('.active')
- .removeClass('active')
-
- this.$element.toggleClass('active')
- }
-
-
- /* BUTTON PLUGIN DEFINITION
- * ======================== */
-
- $.fn.button = function (option) {
- return this.each(function () {
- var $this = $(this)
- , data = $this.data('button')
- , options = typeof option == 'object' && option
- if (!data) $this.data('button', (data = new Button(this, options)))
- if (option == 'toggle') data.toggle()
- else if (option) data.setState(option)
- })
- }
-
- $.fn.button.defaults = {
- loadingText: 'loading...'
- }
-
- $.fn.button.Constructor = Button
-
-
- /* BUTTON DATA-API
- * =============== */
-
- $(function () {
- $('body').on('click.button.data-api', '[data-toggle^=button]', function ( e ) {
- var $btn = $(e.target)
- if (!$btn.hasClass('btn')) $btn = $btn.closest('.btn')
- $btn.button('toggle')
- })
- })
-
-}(window.jQuery);/* ==========================================================
- * bootstrap-carousel.js v2.1.0
- * http://twitter.github.com/bootstrap/javascript.html#carousel
- * ==========================================================
- * Copyright 2012 Twitter, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- * ========================================================== */
-
-
-!function ($) {
-
- "use strict"; // jshint ;_;
-
-
- /* CAROUSEL CLASS DEFINITION
- * ========================= */
-
- var Carousel = function (element, options) {
- this.$element = $(element)
- this.options = options
- this.options.slide && this.slide(this.options.slide)
- this.options.pause == 'hover' && this.$element
- .on('mouseenter', $.proxy(this.pause, this))
- .on('mouseleave', $.proxy(this.cycle, this))
- }
-
- Carousel.prototype = {
-
- cycle: function (e) {
- if (!e) this.paused = false
- this.options.interval
- && !this.paused
- && (this.interval = setInterval($.proxy(this.next, this), this.options.interval))
- return this
- }
-
- , to: function (pos) {
- var $active = this.$element.find('.item.active')
- , children = $active.parent().children()
- , activePos = children.index($active)
- , that = this
-
- if (pos > (children.length - 1) || pos < 0) return
-
- if (this.sliding) {
- return this.$element.one('slid', function () {
- that.to(pos)
- })
- }
-
- if (activePos == pos) {
- return this.pause().cycle()
- }
-
- return this.slide(pos > activePos ? 'next' : 'prev', $(children[pos]))
- }
-
- , pause: function (e) {
- if (!e) this.paused = true
- if (this.$element.find('.next, .prev').length && $.support.transition.end) {
- this.$element.trigger($.support.transition.end)
- this.cycle()
- }
- clearInterval(this.interval)
- this.interval = null
- return this
- }
-
- , next: function () {
- if (this.sliding) return
- return this.slide('next')
- }
-
- , prev: function () {
- if (this.sliding) return
- return this.slide('prev')
- }
-
- , slide: function (type, next) {
- var $active = this.$element.find('.item.active')
- , $next = next || $active[type]()
- , isCycling = this.interval
- , direction = type == 'next' ? 'left' : 'right'
- , fallback = type == 'next' ? 'first' : 'last'
- , that = this
- , e = $.Event('slide', {
- relatedTarget: $next[0]
- })
-
- this.sliding = true
-
- isCycling && this.pause()
-
- $next = $next.length ? $next : this.$element.find('.item')[fallback]()
-
- if ($next.hasClass('active')) return
-
- if ($.support.transition && this.$element.hasClass('slide')) {
- this.$element.trigger(e)
- if (e.isDefaultPrevented()) return
- $next.addClass(type)
- $next[0].offsetWidth // force reflow
- $active.addClass(direction)
- $next.addClass(direction)
- this.$element.one($.support.transition.end, function () {
- $next.removeClass([type, direction].join(' ')).addClass('active')
- $active.removeClass(['active', direction].join(' '))
- that.sliding = false
- setTimeout(function () { that.$element.trigger('slid') }, 0)
- })
- } else {
- this.$element.trigger(e)
- if (e.isDefaultPrevented()) return
- $active.removeClass('active')
- $next.addClass('active')
- this.sliding = false
- this.$element.trigger('slid')
- }
-
- isCycling && this.cycle()
-
- return this
- }
-
- }
-
-
- /* CAROUSEL PLUGIN DEFINITION
- * ========================== */
-
- $.fn.carousel = function (option) {
- return this.each(function () {
- var $this = $(this)
- , data = $this.data('carousel')
- , options = $.extend({}, $.fn.carousel.defaults, typeof option == 'object' && option)
- , action = typeof option == 'string' ? option : options.slide
- if (!data) $this.data('carousel', (data = new Carousel(this, options)))
- if (typeof option == 'number') data.to(option)
- else if (action) data[action]()
- else if (options.interval) data.cycle()
- })
- }
-
- $.fn.carousel.defaults = {
- interval: 5000
- , pause: 'hover'
- }
-
- $.fn.carousel.Constructor = Carousel
-
-
- /* CAROUSEL DATA-API
- * ================= */
-
- $(function () {
- $('body').on('click.carousel.data-api', '[data-slide]', function ( e ) {
- var $this = $(this), href
- , $target = $($this.attr('data-target') || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) //strip for ie7
- , options = !$target.data('modal') && $.extend({}, $target.data(), $this.data())
- $target.carousel(options)
- e.preventDefault()
- })
- })
-
-}(window.jQuery);/* =============================================================
- * bootstrap-collapse.js v2.1.0
- * http://twitter.github.com/bootstrap/javascript.html#collapse
- * =============================================================
- * Copyright 2012 Twitter, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- * ============================================================ */
-
-
-!function ($) {
-
- "use strict"; // jshint ;_;
-
-
- /* COLLAPSE PUBLIC CLASS DEFINITION
- * ================================ */
-
- var Collapse = function (element, options) {
- this.$element = $(element)
- this.options = $.extend({}, $.fn.collapse.defaults, options)
-
- if (this.options.parent) {
- this.$parent = $(this.options.parent)
- }
-
- this.options.toggle && this.toggle()
- }
-
- Collapse.prototype = {
-
- constructor: Collapse
-
- , dimension: function () {
- var hasWidth = this.$element.hasClass('width')
- return hasWidth ? 'width' : 'height'
- }
-
- , show: function () {
- var dimension
- , scroll
- , actives
- , hasData
-
- if (this.transitioning) return
-
- dimension = this.dimension()
- scroll = $.camelCase(['scroll', dimension].join('-'))
- actives = this.$parent && this.$parent.find('> .accordion-group > .in')
-
- if (actives && actives.length) {
- hasData = actives.data('collapse')
- if (hasData && hasData.transitioning) return
- actives.collapse('hide')
- hasData || actives.data('collapse', null)
- }
-
- this.$element[dimension](0)
- this.transition('addClass', $.Event('show'), 'shown')
- $.support.transition && this.$element[dimension](this.$element[0][scroll])
- }
-
- , hide: function () {
- var dimension
- if (this.transitioning) return
- dimension = this.dimension()
- this.reset(this.$element[dimension]())
- this.transition('removeClass', $.Event('hide'), 'hidden')
- this.$element[dimension](0)
- }
-
- , reset: function (size) {
- var dimension = this.dimension()
-
- this.$element
- .removeClass('collapse')
- [dimension](size || 'auto')
- [0].offsetWidth
-
- this.$element[size !== null ? 'addClass' : 'removeClass']('collapse')
-
- return this
- }
-
- , transition: function (method, startEvent, completeEvent) {
- var that = this
- , complete = function () {
- if (startEvent.type == 'show') that.reset()
- that.transitioning = 0
- that.$element.trigger(completeEvent)
- }
-
- this.$element.trigger(startEvent)
-
- if (startEvent.isDefaultPrevented()) return
-
- this.transitioning = 1
-
- this.$element[method]('in')
-
- $.support.transition && this.$element.hasClass('collapse') ?
- this.$element.one($.support.transition.end, complete) :
- complete()
- }
-
- , toggle: function () {
- this[this.$element.hasClass('in') ? 'hide' : 'show']()
- }
-
- }
-
-
- /* COLLAPSIBLE PLUGIN DEFINITION
- * ============================== */
-
- $.fn.collapse = function (option) {
- return this.each(function () {
- var $this = $(this)
- , data = $this.data('collapse')
- , options = typeof option == 'object' && option
- if (!data) $this.data('collapse', (data = new Collapse(this, options)))
- if (typeof option == 'string') data[option]()
- })
- }
-
- $.fn.collapse.defaults = {
- toggle: true
- }
-
- $.fn.collapse.Constructor = Collapse
-
-
- /* COLLAPSIBLE DATA-API
- * ==================== */
-
- $(function () {
- $('body').on('click.collapse.data-api', '[data-toggle=collapse]', function (e) {
- var $this = $(this), href
- , target = $this.attr('data-target')
- || e.preventDefault()
- || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') //strip for ie7
- , option = $(target).data('collapse') ? 'toggle' : $this.data()
- $this[$(target).hasClass('in') ? 'addClass' : 'removeClass']('collapsed')
- $(target).collapse(option)
- })
- })
-
-}(window.jQuery);/* ============================================================
- * bootstrap-dropdown.js v2.1.0
- * http://twitter.github.com/bootstrap/javascript.html#dropdowns
- * ============================================================
- * Copyright 2012 Twitter, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- * ============================================================ */
-
-
-!function ($) {
-
- "use strict"; // jshint ;_;
-
-
- /* DROPDOWN CLASS DEFINITION
- * ========================= */
-
- var toggle = '[data-toggle=dropdown]'
- , Dropdown = function (element) {
- var $el = $(element).on('click.dropdown.data-api', this.toggle)
- $('html').on('click.dropdown.data-api', function () {
- $el.parent().removeClass('open')
- })
- }
-
- Dropdown.prototype = {
-
- constructor: Dropdown
-
- , toggle: function (e) {
- var $this = $(this)
- , $parent
- , isActive
-
- if ($this.is('.disabled, :disabled')) return
-
- $parent = getParent($this)
-
- isActive = $parent.hasClass('open')
-
- clearMenus()
-
- if (!isActive) {
- $parent.toggleClass('open')
- $this.focus()
- }
-
- return false
- }
-
- , keydown: function (e) {
- var $this
- , $items
- , $active
- , $parent
- , isActive
- , index
-
- if (!/(38|40|27)/.test(e.keyCode)) return
-
- $this = $(this)
-
- e.preventDefault()
- e.stopPropagation()
-
- if ($this.is('.disabled, :disabled')) return
-
- $parent = getParent($this)
-
- isActive = $parent.hasClass('open')
-
- if (!isActive || (isActive && e.keyCode == 27)) return $this.click()
-
- $items = $('[role=menu] li:not(.divider) a', $parent)
-
- if (!$items.length) return
-
- index = $items.index($items.filter(':focus'))
-
- if (e.keyCode == 38 && index > 0) index-- // up
- if (e.keyCode == 40 && index < $items.length - 1) index++ // down
- if (!~index) index = 0
-
- $items
- .eq(index)
- .focus()
- }
-
- }
-
- function clearMenus() {
- getParent($(toggle))
- .removeClass('open')
- }
-
- function getParent($this) {
- var selector = $this.attr('data-target')
- , $parent
-
- if (!selector) {
- selector = $this.attr('href')
- selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7
- }
-
- $parent = $(selector)
- $parent.length || ($parent = $this.parent())
-
- return $parent
- }
-
-
- /* DROPDOWN PLUGIN DEFINITION
- * ========================== */
-
- $.fn.dropdown = function (option) {
- return this.each(function () {
- var $this = $(this)
- , data = $this.data('dropdown')
- if (!data) $this.data('dropdown', (data = new Dropdown(this)))
- if (typeof option == 'string') data[option].call($this)
- })
- }
-
- $.fn.dropdown.Constructor = Dropdown
-
-
- /* APPLY TO STANDARD DROPDOWN ELEMENTS
- * =================================== */
-
- $(function () {
- $('html')
- .on('click.dropdown.data-api touchstart.dropdown.data-api', clearMenus)
- $('body')
- .on('click.dropdown touchstart.dropdown.data-api', '.dropdown', function (e) { e.stopPropagation() })
- .on('click.dropdown.data-api touchstart.dropdown.data-api' , toggle, Dropdown.prototype.toggle)
- .on('keydown.dropdown.data-api touchstart.dropdown.data-api', toggle + ', [role=menu]' , Dropdown.prototype.keydown)
- })
-
-}(window.jQuery);/* =========================================================
- * bootstrap-modal.js v2.1.0
- * http://twitter.github.com/bootstrap/javascript.html#modals
- * =========================================================
- * Copyright 2012 Twitter, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- * ========================================================= */
-
-
-!function ($) {
-
- "use strict"; // jshint ;_;
-
-
- /* MODAL CLASS DEFINITION
- * ====================== */
-
- var Modal = function (element, options) {
- this.options = options
- this.$element = $(element)
- .delegate('[data-dismiss="modal"]', 'click.dismiss.modal', $.proxy(this.hide, this))
- this.options.remote && this.$element.find('.modal-body').load(this.options.remote)
- }
-
- Modal.prototype = {
-
- constructor: Modal
-
- , toggle: function () {
- return this[!this.isShown ? 'show' : 'hide']()
- }
-
- , show: function () {
- var that = this
- , e = $.Event('show')
-
- this.$element.trigger(e)
-
- if (this.isShown || e.isDefaultPrevented()) return
-
- $('body').addClass('modal-open')
-
- this.isShown = true
-
- this.escape()
-
- this.backdrop(function () {
- var transition = $.support.transition && that.$element.hasClass('fade')
-
- if (!that.$element.parent().length) {
- that.$element.appendTo(document.body) //don't move modals dom position
- }
-
- that.$element
- .show()
-
- if (transition) {
- that.$element[0].offsetWidth // force reflow
- }
-
- that.$element
- .addClass('in')
- .attr('aria-hidden', false)
- .focus()
-
- that.enforceFocus()
-
- transition ?
- that.$element.one($.support.transition.end, function () { that.$element.trigger('shown') }) :
- that.$element.trigger('shown')
-
- })
- }
-
- , hide: function (e) {
- e && e.preventDefault()
-
- var that = this
-
- e = $.Event('hide')
-
- this.$element.trigger(e)
-
- if (!this.isShown || e.isDefaultPrevented()) return
-
- this.isShown = false
-
- $('body').removeClass('modal-open')
-
- this.escape()
-
- $(document).off('focusin.modal')
-
- this.$element
- .removeClass('in')
- .attr('aria-hidden', true)
-
- $.support.transition && this.$element.hasClass('fade') ?
- this.hideWithTransition() :
- this.hideModal()
- }
-
- , enforceFocus: function () {
- var that = this
- $(document).on('focusin.modal', function (e) {
- if (that.$element[0] !== e.target && !that.$element.has(e.target).length) {
- that.$element.focus()
- }
- })
- }
-
- , escape: function () {
- var that = this
- if (this.isShown && this.options.keyboard) {
- this.$element.on('keyup.dismiss.modal', function ( e ) {
- e.which == 27 && that.hide()
- })
- } else if (!this.isShown) {
- this.$element.off('keyup.dismiss.modal')
- }
- }
-
- , hideWithTransition: function () {
- var that = this
- , timeout = setTimeout(function () {
- that.$element.off($.support.transition.end)
- that.hideModal()
- }, 500)
-
- this.$element.one($.support.transition.end, function () {
- clearTimeout(timeout)
- that.hideModal()
- })
- }
-
- , hideModal: function (that) {
- this.$element
- .hide()
- .trigger('hidden')
-
- this.backdrop()
- }
-
- , removeBackdrop: function () {
- this.$backdrop.remove()
- this.$backdrop = null
- }
-
- , backdrop: function (callback) {
- var that = this
- , animate = this.$element.hasClass('fade') ? 'fade' : ''
-
- if (this.isShown && this.options.backdrop) {
- var doAnimate = $.support.transition && animate
-
- this.$backdrop = $('<div class="modal-backdrop ' + animate + '" />')
- .appendTo(document.body)
-
- if (this.options.backdrop != 'static') {
- this.$backdrop.click($.proxy(this.hide, this))
- }
-
- if (doAnimate) this.$backdrop[0].offsetWidth // force reflow
-
- this.$backdrop.addClass('in')
-
- doAnimate ?
- this.$backdrop.one($.support.transition.end, callback) :
- callback()
-
- } else if (!this.isShown && this.$backdrop) {
- this.$backdrop.removeClass('in')
-
- $.support.transition && this.$element.hasClass('fade')?
- this.$backdrop.one($.support.transition.end, $.proxy(this.removeBackdrop, this)) :
- this.removeBackdrop()
-
- } else if (callback) {
- callback()
- }
- }
- }
-
-
- /* MODAL PLUGIN DEFINITION
- * ======================= */
-
- $.fn.modal = function (option) {
- return this.each(function () {
- var $this = $(this)
- , data = $this.data('modal')
- , options = $.extend({}, $.fn.modal.defaults, $this.data(), typeof option == 'object' && option)
- if (!data) $this.data('modal', (data = new Modal(this, options)))
- if (typeof option == 'string') data[option]()
- else if (options.show) data.show()
- })
- }
-
- $.fn.modal.defaults = {
- backdrop: true
- , keyboard: true
- , show: true
- }
-
- $.fn.modal.Constructor = Modal
-
-
- /* MODAL DATA-API
- * ============== */
-
- $(function () {
- $('body').on('click.modal.data-api', '[data-toggle="modal"]', function ( e ) {
- var $this = $(this)
- , href = $this.attr('href')
- , $target = $($this.attr('data-target') || (href && href.replace(/.*(?=#[^\s]+$)/, ''))) //strip for ie7
- , option = $target.data('modal') ? 'toggle' : $.extend({ remote: !/#/.test(href) && href }, $target.data(), $this.data())
-
- e.preventDefault()
-
- $target
- .modal(option)
- .one('hide', function () {
- $this.focus()
- })
- })
- })
-
-}(window.jQuery);/* ===========================================================
- * bootstrap-tooltip.js v2.1.0
- * http://twitter.github.com/bootstrap/javascript.html#tooltips
- * Inspired by the original jQuery.tipsy by Jason Frame
- * ===========================================================
- * Copyright 2012 Twitter, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- * ========================================================== */
-
-
-!function ($) {
-
- "use strict"; // jshint ;_;
-
-
- /* TOOLTIP PUBLIC CLASS DEFINITION
- * =============================== */
-
- var Tooltip = function (element, options) {
- this.init('tooltip', element, options)
- }
-
- Tooltip.prototype = {
-
- constructor: Tooltip
-
- , init: function (type, element, options) {
- var eventIn
- , eventOut
-
- this.type = type
- this.$element = $(element)
- this.options = this.getOptions(options)
- this.enabled = true
-
- if (this.options.trigger == 'click') {
- this.$element.on('click.' + this.type, this.options.selector, $.proxy(this.toggle, this))
- } else if (this.options.trigger != 'manual') {
- eventIn = this.options.trigger == 'hover' ? 'mouseenter' : 'focus'
- eventOut = this.options.trigger == 'hover' ? 'mouseleave' : 'blur'
- this.$element.on(eventIn + '.' + this.type, this.options.selector, $.proxy(this.enter, this))
- this.$element.on(eventOut + '.' + this.type, this.options.selector, $.proxy(this.leave, this))
- }
-
- this.options.selector ?
- (this._options = $.extend({}, this.options, { trigger: 'manual', selector: '' })) :
- this.fixTitle()
- }
-
- , getOptions: function (options) {
- options = $.extend({}, $.fn[this.type].defaults, options, this.$element.data())
-
- if (options.delay && typeof options.delay == 'number') {
- options.delay = {
- show: options.delay
- , hide: options.delay
- }
- }
-
- return options
- }
-
- , enter: function (e) {
- var self = $(e.currentTarget)[this.type](this._options).data(this.type)
-
- if (!self.options.delay || !self.options.delay.show) return self.show()
-
- clearTimeout(this.timeout)
- self.hoverState = 'in'
- this.timeout = setTimeout(function() {
- if (self.hoverState == 'in') self.show()
- }, self.options.delay.show)
- }
-
- , leave: function (e) {
- var self = $(e.currentTarget)[this.type](this._options).data(this.type)
-
- if (this.timeout) clearTimeout(this.timeout)
- if (!self.options.delay || !self.options.delay.hide) return self.hide()
-
- self.hoverState = 'out'
- this.timeout = setTimeout(function() {
- if (self.hoverState == 'out') self.hide()
- }, self.options.delay.hide)
- }
-
- , show: function () {
- var $tip
- , inside
- , pos
- , actualWidth
- , actualHeight
- , placement
- , tp
-
- if (this.hasContent() && this.enabled) {
- $tip = this.tip()
- this.setContent()
-
- if (this.options.animation) {
- $tip.addClass('fade')
- }
-
- placement = typeof this.options.placement == 'function' ?
- this.options.placement.call(this, $tip[0], this.$element[0]) :
- this.options.placement
-
- inside = /in/.test(placement)
-
- $tip
- .remove()
- .css({ top: 0, left: 0, display: 'block' })
- .appendTo(inside ? this.$element : document.body)
-
- pos = this.getPosition(inside)
-
- actualWidth = $tip[0].offsetWidth
- actualHeight = $tip[0].offsetHeight
-
- switch (inside ? placement.split(' ')[1] : placement) {
- case 'bottom':
- tp = {top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2}
- break
- case 'top':
- tp = {top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2}
- break
- case 'left':
- tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth}
- break
- case 'right':
- tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width}
- break
- }
-
- $tip
- .css(tp)
- .addClass(placement)
- .addClass('in')
- }
- }
-
- , setContent: function () {
- var $tip = this.tip()
- , title = this.getTitle()
-
- $tip.find('.tooltip-inner')[this.options.html ? 'html' : 'text'](title)
- $tip.removeClass('fade in top bottom left right')
- }
-
- , hide: function () {
- var that = this
- , $tip = this.tip()
-
- $tip.removeClass('in')
-
- function removeWithAnimation() {
- var timeout = setTimeout(function () {
- $tip.off($.support.transition.end).remove()
- }, 500)
-
- $tip.one($.support.transition.end, function () {
- clearTimeout(timeout)
- $tip.remove()
- })
- }
-
- $.support.transition && this.$tip.hasClass('fade') ?
- removeWithAnimation() :
- $tip.remove()
-
- return this
- }
-
- , fixTitle: function () {
- var $e = this.$element
- if ($e.attr('title') || typeof($e.attr('data-original-title')) != 'string') {
- $e.attr('data-original-title', $e.attr('title') || '').removeAttr('title')
- }
- }
-
- , hasContent: function () {
- return this.getTitle()
- }
-
- , getPosition: function (inside) {
- return $.extend({}, (inside ? {top: 0, left: 0} : this.$element.offset()), {
- width: this.$element[0].offsetWidth
- , height: this.$element[0].offsetHeight
- })
- }
-
- , getTitle: function () {
- var title
- , $e = this.$element
- , o = this.options
-
- title = $e.attr('data-original-title')
- || (typeof o.title == 'function' ? o.title.call($e[0]) : o.title)
-
- return title
- }
-
- , tip: function () {
- return this.$tip = this.$tip || $(this.options.template)
- }
-
- , validate: function () {
- if (!this.$element[0].parentNode) {
- this.hide()
- this.$element = null
- this.options = null
- }
- }
-
- , enable: function () {
- this.enabled = true
- }
-
- , disable: function () {
- this.enabled = false
- }
-
- , toggleEnabled: function () {
- this.enabled = !this.enabled
- }
-
- , toggle: function () {
- this[this.tip().hasClass('in') ? 'hide' : 'show']()
- }
-
- , destroy: function () {
- this.hide().$element.off('.' + this.type).removeData(this.type)
- }
-
- }
-
-
- /* TOOLTIP PLUGIN DEFINITION
- * ========================= */
-
- $.fn.tooltip = function ( option ) {
- return this.each(function () {
- var $this = $(this)
- , data = $this.data('tooltip')
- , options = typeof option == 'object' && option
- if (!data) $this.data('tooltip', (data = new Tooltip(this, options)))
- if (typeof option == 'string') data[option]()
- })
- }
-
- $.fn.tooltip.Constructor = Tooltip
-
- $.fn.tooltip.defaults = {
- animation: true
- , placement: 'top'
- , selector: false
- , template: '<div class="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>'
- , trigger: 'hover'
- , title: ''
- , delay: 0
- , html: true
- }
-
-}(window.jQuery);
-/* ===========================================================
- * bootstrap-popover.js v2.1.0
- * http://twitter.github.com/bootstrap/javascript.html#popovers
- * ===========================================================
- * Copyright 2012 Twitter, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- * =========================================================== */
-
-
-!function ($) {
-
- "use strict"; // jshint ;_;
-
-
- /* POPOVER PUBLIC CLASS DEFINITION
- * =============================== */
-
- var Popover = function (element, options) {
- this.init('popover', element, options)
- }
-
-
- /* NOTE: POPOVER EXTENDS BOOTSTRAP-TOOLTIP.js
- ========================================== */
-
- Popover.prototype = $.extend({}, $.fn.tooltip.Constructor.prototype, {
-
- constructor: Popover
-
- , setContent: function () {
- var $tip = this.tip()
- , title = this.getTitle()
- , content = this.getContent()
-
- $tip.find('.popover-title')[this.options.html ? 'html' : 'text'](title)
- $tip.find('.popover-content > *')[this.options.html ? 'html' : 'text'](content)
-
- $tip.removeClass('fade top bottom left right in')
- }
-
- , hasContent: function () {
- return this.getTitle() || this.getContent()
- }
-
- , getContent: function () {
- var content
- , $e = this.$element
- , o = this.options
-
- content = $e.attr('data-content')
- || (typeof o.content == 'function' ? o.content.call($e[0]) : o.content)
-
- return content
- }
-
- , tip: function () {
- if (!this.$tip) {
- this.$tip = $(this.options.template)
- }
- return this.$tip
- }
-
- , destroy: function () {
- this.hide().$element.off('.' + this.type).removeData(this.type)
- }
-
- })
-
-
- /* POPOVER PLUGIN DEFINITION
- * ======================= */
-
- $.fn.popover = function (option) {
- return this.each(function () {
- var $this = $(this)
- , data = $this.data('popover')
- , options = typeof option == 'object' && option
- if (!data) $this.data('popover', (data = new Popover(this, options)))
- if (typeof option == 'string') data[option]()
- })
- }
-
- $.fn.popover.Constructor = Popover
-
- $.fn.popover.defaults = $.extend({} , $.fn.tooltip.defaults, {
- placement: 'right'
- , trigger: 'click'
- , content: ''
- , template: '<div class="popover"><div class="arrow"></div><div class="popover-inner"><h3 class="popover-title"></h3><div class="popover-content"><p></p></div></div></div>'
- })
-
-}(window.jQuery);/* =============================================================
- * bootstrap-scrollspy.js v2.1.0
- * http://twitter.github.com/bootstrap/javascript.html#scrollspy
- * =============================================================
- * Copyright 2012 Twitter, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- * ============================================================== */
-
-
-!function ($) {
-
- "use strict"; // jshint ;_;
-
-
- /* SCROLLSPY CLASS DEFINITION
- * ========================== */
-
- function ScrollSpy(element, options) {
- var process = $.proxy(this.process, this)
- , $element = $(element).is('body') ? $(window) : $(element)
- , href
- this.options = $.extend({}, $.fn.scrollspy.defaults, options)
- this.$scrollElement = $element.on('scroll.scroll-spy.data-api', process)
- this.selector = (this.options.target
- || ((href = $(element).attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '')) //strip for ie7
- || '') + ' .nav li > a'
- this.$body = $('body')
- this.refresh()
- this.process()
- }
-
- ScrollSpy.prototype = {
-
- constructor: ScrollSpy
-
- , refresh: function () {
- var self = this
- , $targets
-
- this.offsets = $([])
- this.targets = $([])
-
- $targets = this.$body
- .find(this.selector)
- .map(function () {
- var $el = $(this)
- , href = $el.data('target') || $el.attr('href')
- , $href = /^#\w/.test(href) && $(href)
- return ( $href
- && $href.length
- && [[ $href.position().top, href ]] ) || null
- })
- .sort(function (a, b) { return a[0] - b[0] })
- .each(function () {
- self.offsets.push(this[0])
- self.targets.push(this[1])
- })
- }
-
- , process: function () {
- var scrollTop = this.$scrollElement.scrollTop() + this.options.offset
- , scrollHeight = this.$scrollElement[0].scrollHeight || this.$body[0].scrollHeight
- , maxScroll = scrollHeight - this.$scrollElement.height()
- , offsets = this.offsets
- , targets = this.targets
- , activeTarget = this.activeTarget
- , i
-
- if (scrollTop >= maxScroll) {
- return activeTarget != (i = targets.last()[0])
- && this.activate ( i )
- }
-
- for (i = offsets.length; i--;) {
- activeTarget != targets[i]
- && scrollTop >= offsets[i]
- && (!offsets[i + 1] || scrollTop <= offsets[i + 1])
- && this.activate( targets[i] )
- }
- }
-
- , activate: function (target) {
- var active
- , selector
-
- this.activeTarget = target
-
- $(this.selector)
- .parent('.active')
- .removeClass('active')
-
- selector = this.selector
- + '[data-target="' + target + '"],'
- + this.selector + '[href="' + target + '"]'
-
- active = $(selector)
- .parent('li')
- .addClass('active')
-
- if (active.parent('.dropdown-menu').length) {
- active = active.closest('li.dropdown').addClass('active')
- }
-
- active.trigger('activate')
- }
-
- }
-
-
- /* SCROLLSPY PLUGIN DEFINITION
- * =========================== */
-
- $.fn.scrollspy = function (option) {
- return this.each(function () {
- var $this = $(this)
- , data = $this.data('scrollspy')
- , options = typeof option == 'object' && option
- if (!data) $this.data('scrollspy', (data = new ScrollSpy(this, options)))
- if (typeof option == 'string') data[option]()
- })
- }
-
- $.fn.scrollspy.Constructor = ScrollSpy
-
- $.fn.scrollspy.defaults = {
- offset: 10
- }
-
-
- /* SCROLLSPY DATA-API
- * ================== */
-
- $(window).on('load', function () {
- $('[data-spy="scroll"]').each(function () {
- var $spy = $(this)
- $spy.scrollspy($spy.data())
- })
- })
-
-}(window.jQuery);/* ========================================================
- * bootstrap-tab.js v2.1.0
- * http://twitter.github.com/bootstrap/javascript.html#tabs
- * ========================================================
- * Copyright 2012 Twitter, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- * ======================================================== */
-
-
-!function ($) {
-
- "use strict"; // jshint ;_;
-
-
- /* TAB CLASS DEFINITION
- * ==================== */
-
- var Tab = function (element) {
- this.element = $(element)
- }
-
- Tab.prototype = {
-
- constructor: Tab
-
- , show: function () {
- var $this = this.element
- , $ul = $this.closest('ul:not(.dropdown-menu)')
- , selector = $this.attr('data-target')
- , previous
- , $target
- , e
-
- if (!selector) {
- selector = $this.attr('href')
- selector = selector && selector.replace(/.*(?=#[^\s]*$)/, '') //strip for ie7
- }
-
- if ( $this.parent('li').hasClass('active') ) return
-
- previous = $ul.find('.active a').last()[0]
-
- e = $.Event('show', {
- relatedTarget: previous
- })
-
- $this.trigger(e)
-
- if (e.isDefaultPrevented()) return
-
- $target = $(selector)
-
- this.activate($this.parent('li'), $ul)
- this.activate($target, $target.parent(), function () {
- $this.trigger({
- type: 'shown'
- , relatedTarget: previous
- })
- })
- }
-
- , activate: function ( element, container, callback) {
- var $active = container.find('> .active')
- , transition = callback
- && $.support.transition
- && $active.hasClass('fade')
-
- function next() {
- $active
- .removeClass('active')
- .find('> .dropdown-menu > .active')
- .removeClass('active')
-
- element.addClass('active')
-
- if (transition) {
- element[0].offsetWidth // reflow for transition
- element.addClass('in')
- } else {
- element.removeClass('fade')
- }
-
- if ( element.parent('.dropdown-menu') ) {
- element.closest('li.dropdown').addClass('active')
- }
-
- callback && callback()
- }
-
- transition ?
- $active.one($.support.transition.end, next) :
- next()
-
- $active.removeClass('in')
- }
- }
-
-
- /* TAB PLUGIN DEFINITION
- * ===================== */
-
- $.fn.tab = function ( option ) {
- return this.each(function () {
- var $this = $(this)
- , data = $this.data('tab')
- if (!data) $this.data('tab', (data = new Tab(this)))
- if (typeof option == 'string') data[option]()
- })
- }
-
- $.fn.tab.Constructor = Tab
-
-
- /* TAB DATA-API
- * ============ */
-
- $(function () {
- $('body').on('click.tab.data-api', '[data-toggle="tab"], [data-toggle="pill"]', function (e) {
- e.preventDefault()
- $(this).tab('show')
- })
- })
-
-}(window.jQuery);/* =============================================================
- * bootstrap-typeahead.js v2.1.0
- * http://twitter.github.com/bootstrap/javascript.html#typeahead
- * =============================================================
- * Copyright 2012 Twitter, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- * ============================================================ */
-
-
-!function($){
-
- "use strict"; // jshint ;_;
-
-
- /* TYPEAHEAD PUBLIC CLASS DEFINITION
- * ================================= */
-
- var Typeahead = function (element, options) {
- this.$element = $(element)
- this.options = $.extend({}, $.fn.typeahead.defaults, options)
- this.matcher = this.options.matcher || this.matcher
- this.sorter = this.options.sorter || this.sorter
- this.highlighter = this.options.highlighter || this.highlighter
- this.updater = this.options.updater || this.updater
- this.$menu = $(this.options.menu).appendTo('body')
- this.source = this.options.source
- this.shown = false
- this.listen()
- }
-
- Typeahead.prototype = {
-
- constructor: Typeahead
-
- , select: function () {
- var val = this.$menu.find('.active').attr('data-value')
- this.$element
- .val(this.updater(val))
- .change()
- return this.hide()
- }
-
- , updater: function (item) {
- return item
- }
-
- , show: function () {
- var pos = $.extend({}, this.$element.offset(), {
- height: this.$element[0].offsetHeight
- })
-
- this.$menu.css({
- top: pos.top + pos.height
- , left: pos.left
- })
-
- this.$menu.show()
- this.shown = true
- return this
- }
-
- , hide: function () {
- this.$menu.hide()
- this.shown = false
- return this
- }
-
- , lookup: function (event) {
- var items
-
- this.query = this.$element.val()
-
- if (!this.query || this.query.length < this.options.minLength) {
- return this.shown ? this.hide() : this
- }
-
- items = $.isFunction(this.source) ? this.source(this.query, $.proxy(this.process, this)) : this.source
-
- return items ? this.process(items) : this
- }
-
- , process: function (items) {
- var that = this
-
- items = $.grep(items, function (item) {
- return that.matcher(item)
- })
-
- items = this.sorter(items)
-
- if (!items.length) {
- return this.shown ? this.hide() : this
- }
-
- return this.render(items.slice(0, this.options.items)).show()
- }
-
- , matcher: function (item) {
- return ~item.toLowerCase().indexOf(this.query.toLowerCase())
- }
-
- , sorter: function (items) {
- var beginswith = []
- , caseSensitive = []
- , caseInsensitive = []
- , item
-
- while (item = items.shift()) {
- if (!item.toLowerCase().indexOf(this.query.toLowerCase())) beginswith.push(item)
- else if (~item.indexOf(this.query)) caseSensitive.push(item)
- else caseInsensitive.push(item)
- }
-
- return beginswith.concat(caseSensitive, caseInsensitive)
- }
-
- , highlighter: function (item) {
- var query = this.query.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, '\\$&')
- return item.replace(new RegExp('(' + query + ')', 'ig'), function ($1, match) {
- return '<strong>' + match + '</strong>'
- })
- }
-
- , render: function (items) {
- var that = this
-
- items = $(items).map(function (i, item) {
- i = $(that.options.item).attr('data-value', item)
- i.find('a').html(that.highlighter(item))
- return i[0]
- })
-
- items.first().addClass('active')
- this.$menu.html(items)
- return this
- }
-
- , next: function (event) {
- var active = this.$menu.find('.active').removeClass('active')
- , next = active.next()
-
- if (!next.length) {
- next = $(this.$menu.find('li')[0])
- }
-
- next.addClass('active')
- }
-
- , prev: function (event) {
- var active = this.$menu.find('.active').removeClass('active')
- , prev = active.prev()
-
- if (!prev.length) {
- prev = this.$menu.find('li').last()
- }
-
- prev.addClass('active')
- }
-
- , listen: function () {
- this.$element
- .on('blur', $.proxy(this.blur, this))
- .on('keypress', $.proxy(this.keypress, this))
- .on('keyup', $.proxy(this.keyup, this))
-
- if ($.browser.webkit || $.browser.msie) {
- this.$element.on('keydown', $.proxy(this.keydown, this))
- }
-
- this.$menu
- .on('click', $.proxy(this.click, this))
- .on('mouseenter', 'li', $.proxy(this.mouseenter, this))
- }
-
- , move: function (e) {
- if (!this.shown) return
-
- switch(e.keyCode) {
- case 9: // tab
- case 13: // enter
- case 27: // escape
- e.preventDefault()
- break
-
- case 38: // up arrow
- e.preventDefault()
- this.prev()
- break
-
- case 40: // down arrow
- e.preventDefault()
- this.next()
- break
- }
-
- e.stopPropagation()
- }
-
- , keydown: function (e) {
- this.suppressKeyPressRepeat = !~$.inArray(e.keyCode, [40,38,9,13,27])
- this.move(e)
- }
-
- , keypress: function (e) {
- if (this.suppressKeyPressRepeat) return
- this.move(e)
- }
-
- , keyup: function (e) {
- switch(e.keyCode) {
- case 40: // down arrow
- case 38: // up arrow
- break
-
- case 9: // tab
- case 13: // enter
- if (!this.shown) return
- this.select()
- break
-
- case 27: // escape
- if (!this.shown) return
- this.hide()
- break
-
- default:
- this.lookup()
- }
-
- e.stopPropagation()
- e.preventDefault()
- }
-
- , blur: function (e) {
- var that = this
- setTimeout(function () { that.hide() }, 150)
- }
-
- , click: function (e) {
- e.stopPropagation()
- e.preventDefault()
- this.select()
- }
-
- , mouseenter: function (e) {
- this.$menu.find('.active').removeClass('active')
- $(e.currentTarget).addClass('active')
- }
-
- }
-
-
- /* TYPEAHEAD PLUGIN DEFINITION
- * =========================== */
-
- $.fn.typeahead = function (option) {
- return this.each(function () {
- var $this = $(this)
- , data = $this.data('typeahead')
- , options = typeof option == 'object' && option
- if (!data) $this.data('typeahead', (data = new Typeahead(this, options)))
- if (typeof option == 'string') data[option]()
- })
- }
-
- $.fn.typeahead.defaults = {
- source: []
- , items: 8
- , menu: '<ul class="typeahead dropdown-menu"></ul>'
- , item: '<li><a href="#"></a></li>'
- , minLength: 1
- }
-
- $.fn.typeahead.Constructor = Typeahead
-
-
- /* TYPEAHEAD DATA-API
- * ================== */
-
- $(function () {
- $('body').on('focus.typeahead.data-api', '[data-provide="typeahead"]', function (e) {
- var $this = $(this)
- if ($this.data('typeahead')) return
- e.preventDefault()
- $this.typeahead($this.data())
- })
- })
-
-}(window.jQuery);
-/* ==========================================================
- * bootstrap-affix.js v2.1.0
- * http://twitter.github.com/bootstrap/javascript.html#affix
- * ==========================================================
- * Copyright 2012 Twitter, Inc.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- * ========================================================== */
-
-
-!function ($) {
-
- "use strict"; // jshint ;_;
-
-
- /* AFFIX CLASS DEFINITION
- * ====================== */
-
- var Affix = function (element, options) {
- this.options = $.extend({}, $.fn.affix.defaults, options)
- this.$window = $(window).on('scroll.affix.data-api', $.proxy(this.checkPosition, this))
- this.$element = $(element)
- this.checkPosition()
- }
-
- Affix.prototype.checkPosition = function () {
- if (!this.$element.is(':visible')) return
-
- var scrollHeight = $(document).height()
- , scrollTop = this.$window.scrollTop()
- , position = this.$element.offset()
- , offset = this.options.offset
- , offsetBottom = offset.bottom
- , offsetTop = offset.top
- , reset = 'affix affix-top affix-bottom'
- , affix
-
- if (typeof offset != 'object') offsetBottom = offsetTop = offset
- if (typeof offsetTop == 'function') offsetTop = offset.top()
- if (typeof offsetBottom == 'function') offsetBottom = offset.bottom()
-
- affix = this.unpin != null && (scrollTop + this.unpin <= position.top) ?
- false : offsetBottom != null && (position.top + this.$element.height() >= scrollHeight - offsetBottom) ?
- 'bottom' : offsetTop != null && scrollTop <= offsetTop ?
- 'top' : false
-
- if (this.affixed === affix) return
-
- this.affixed = affix
- this.unpin = affix == 'bottom' ? position.top - scrollTop : null
-
- this.$element.removeClass(reset).addClass('affix' + (affix ? '-' + affix : ''))
- }
-
-
- /* AFFIX PLUGIN DEFINITION
- * ======================= */
-
- $.fn.affix = function (option) {
- return this.each(function () {
- var $this = $(this)
- , data = $this.data('affix')
- , options = typeof option == 'object' && option
- if (!data) $this.data('affix', (data = new Affix(this, options)))
- if (typeof option == 'string') data[option]()
- })
- }
-
- $.fn.affix.Constructor = Affix
-
- $.fn.affix.defaults = {
- offset: 0
- }
-
-
- /* AFFIX DATA-API
- * ============== */
-
- $(window).on('load', function () {
- $('[data-spy="affix"]').each(function () {
- var $spy = $(this)
- , data = $spy.data()
-
- data.offset = data.offset || {}
-
- data.offsetBottom && (data.offset.bottom = data.offsetBottom)
- data.offsetTop && (data.offset.top = data.offsetTop)
-
- $spy.affix(data)
- })
- })
-
-
-}(window.jQuery);
\ No newline at end of file
diff --git a/src/site/resources/js/bootstrap.min.js b/src/site/resources/js/bootstrap.min.js
deleted file mode 100644
index 66e887b..0000000
--- a/src/site/resources/js/bootstrap.min.js
+++ /dev/null
@@ -1,6 +0,0 @@
-/*!
-* Bootstrap.js by @fat & @mdo
-* Copyright 2012 Twitter, Inc.
-* http://www.apache.org/licenses/LICENSE-2.0.txt
-*/
-!function(e){e(function(){"use strict";e.support.transition=function(){var e=function(){var e=document.createElement("bootstrap"),t={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"},n;for(n in t)if(e.style[n]!==undefined)return t[n]}();return e&&{end:e}}()})}(window.jQuery),!function(e){"use strict";var t='[data-dismiss="alert"]',n=function(n){e(n).on("click",t,this.close)};n.prototype.close=function(t){function s(){i.trigger("closed").remove()}var n=e(this),r=n.attr("data-target"),i;r||(r=n.attr("href"),r=r&&r.replace(/.*(?=#[^\s]*$)/,"")),i=e(r),t&&t.preventDefault(),i.length||(i=n.hasClass("alert")?n:n.parent()),i.trigger(t=e.Event("close"));if(t.isDefaultPrevented())return;i.removeClass("in"),e.support.transition&&i.hasClass("fade")?i.on(e.support.transition.end,s):s()},e.fn.alert=function(t){return this.each(function(){var r=e(this),i=r.data("alert");i||r.data("alert",i=new n(this)),typeof t=="string"&&i[t].call(r)})},e.fn.alert.Constructor=n,e(function(){e("body").on("click.alert.data-api",t,n.prototype.close)})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=e.extend({},e.fn.button.defaults,n)};t.prototype.setState=function(e){var t="disabled",n=this.$element,r=n.data(),i=n.is("input")?"val":"html";e+="Text",r.resetText||n.data("resetText",n[i]()),n[i](r[e]||this.options[e]),setTimeout(function(){e=="loadingText"?n.addClass(t).attr(t,t):n.removeClass(t).removeAttr(t)},0)},t.prototype.toggle=function(){var e=this.$element.parent('[data-toggle="buttons-radio"]');e&&e.find(".active").removeClass("active"),this.$element.toggleClass("active")},e.fn.button=function(n){return this.each(function(){var r=e(this),i=r.data("button"),s=typeof n=="object"&&n;i||r.data("button",i=new t(this,s)),n=="toggle"?i.toggle():n&&i.setState(n)})},e.fn.button.defaults={loadingText:"loading..."},e.fn.button.Constructor=t,e(function(){e("body").on("click.button.data-api","[data-toggle^=button]",function(t){var n=e(t.target);n.hasClass("btn")||(n=n.closest(".btn")),n.button("toggle")})})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=n,this.options.slide&&this.slide(this.options.slide),this.options.pause=="hover"&&this.$element.on("mouseenter",e.proxy(this.pause,this)).on("mouseleave",e.proxy(this.cycle,this))};t.prototype={cycle:function(t){return t||(this.paused=!1),this.options.interval&&!this.paused&&(this.interval=setInterval(e.proxy(this.next,this),this.options.interval)),this},to:function(t){var n=this.$element.find(".item.active"),r=n.parent().children(),i=r.index(n),s=this;if(t>r.length-1||t<0)return;return this.sliding?this.$element.one("slid",function(){s.to(t)}):i==t?this.pause().cycle():this.slide(t>i?"next":"prev",e(r[t]))},pause:function(t){return t||(this.paused=!0),this.$element.find(".next, .prev").length&&e.support.transition.end&&(this.$element.trigger(e.support.transition.end),this.cycle()),clearInterval(this.interval),this.interval=null,this},next:function(){if(this.sliding)return;return this.slide("next")},prev:function(){if(this.sliding)return;return this.slide("prev")},slide:function(t,n){var r=this.$element.find(".item.active"),i=n||r[t](),s=this.interval,o=t=="next"?"left":"right",u=t=="next"?"first":"last",a=this,f=e.Event("slide",{relatedTarget:i[0]});this.sliding=!0,s&&this.pause(),i=i.length?i:this.$element.find(".item")[u]();if(i.hasClass("active"))return;if(e.support.transition&&this.$element.hasClass("slide")){this.$element.trigger(f);if(f.isDefaultPrevented())return;i.addClass(t),i[0].offsetWidth,r.addClass(o),i.addClass(o),this.$element.one(e.support.transition.end,function(){i.removeClass([t,o].join(" ")).addClass("active"),r.removeClass(["active",o].join(" ")),a.sliding=!1,setTimeout(function(){a.$element.trigger("slid")},0)})}else{this.$element.trigger(f);if(f.isDefaultPrevented())return;r.removeClass("active"),i.addClass("active"),this.sliding=!1,this.$element.trigger("slid")}return s&&this.cycle(),this}},e.fn.carousel=function(n){return this.each(function(){var r=e(this),i=r.data("carousel"),s=e.extend({},e.fn.carousel.defaults,typeof n=="object"&&n),o=typeof n=="string"?n:s.slide;i||r.data("carousel",i=new t(this,s)),typeof n=="number"?i.to(n):o?i[o]():s.interval&&i.cycle()})},e.fn.carousel.defaults={interval:5e3,pause:"hover"},e.fn.carousel.Constructor=t,e(function(){e("body").on("click.carousel.data-api","[data-slide]",function(t){var n=e(this),r,i=e(n.attr("data-target")||(r=n.attr("href"))&&r.replace(/.*(?=#[^\s]+$)/,"")),s=!i.data("modal")&&e.extend({},i.data(),n.data());i.carousel(s),t.preventDefault()})})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=e.extend({},e.fn.collapse.defaults,n),this.options.parent&&(this.$parent=e(this.options.parent)),this.options.toggle&&this.toggle()};t.prototype={constructor:t,dimension:function(){var e=this.$element.hasClass("width");return e?"width":"height"},show:function(){var t,n,r,i;if(this.transitioning)return;t=this.dimension(),n=e.camelCase(["scroll",t].join("-")),r=this.$parent&&this.$parent.find("> .accordion-group > .in");if(r&&r.length){i=r.data("collapse");if(i&&i.transitioning)return;r.collapse("hide"),i||r.data("collapse",null)}this.$element[t](0),this.transition("addClass",e.Event("show"),"shown"),e.support.transition&&this.$element[t](this.$element[0][n])},hide:function(){var t;if(this.transitioning)return;t=this.dimension(),this.reset(this.$element[t]()),this.transition("removeClass",e.Event("hide"),"hidden"),this.$element[t](0)},reset:function(e){var t=this.dimension();return this.$element.removeClass("collapse")[t](e||"auto")[0].offsetWidth,this.$element[e!==null?"addClass":"removeClass"]("collapse"),this},transition:function(t,n,r){var i=this,s=function(){n.type=="show"&&i.reset(),i.transitioning=0,i.$element.trigger(r)};this.$element.trigger(n);if(n.isDefaultPrevented())return;this.transitioning=1,this.$element[t]("in"),e.support.transition&&this.$element.hasClass("collapse")?this.$element.one(e.support.transition.end,s):s()},toggle:function(){this[this.$element.hasClass("in")?"hide":"show"]()}},e.fn.collapse=function(n){return this.each(function(){var r=e(this),i=r.data("collapse"),s=typeof n=="object"&&n;i||r.data("collapse",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.collapse.defaults={toggle:!0},e.fn.collapse.Constructor=t,e(function(){e("body").on("click.collapse.data-api","[data-toggle=collapse]",function(t){var n=e(this),r,i=n.attr("data-target")||t.preventDefault()||(r=n.attr("href"))&&r.replace(/.*(?=#[^\s]+$)/,""),s=e(i).data("collapse")?"toggle":n.data();n[e(i).hasClass("in")?"addClass":"removeClass"]("collapsed"),e(i).collapse(s)})})}(window.jQuery),!function(e){"use strict";function r(){i(e(t)).removeClass("open")}function i(t){var n=t.attr("data-target"),r;return n||(n=t.attr("href"),n=n&&n.replace(/.*(?=#[^\s]*$)/,"")),r=e(n),r.length||(r=t.parent()),r}var t="[data-toggle=dropdown]",n=function(t){var n=e(t).on("click.dropdown.data-api",this.toggle);e("html").on("click.dropdown.data-api",function(){n.parent().removeClass("open")})};n.prototype={constructor:n,toggle:function(t){var n=e(this),s,o;if(n.is(".disabled, :disabled"))return;return s=i(n),o=s.hasClass("open"),r(),o||(s.toggleClass("open"),n.focus()),!1},keydown:function(t){var n,r,s,o,u,a;if(!/(38|40|27)/.test(t.keyCode))return;n=e(this),t.preventDefault(),t.stopPropagation();if(n.is(".disabled, :disabled"))return;o=i(n),u=o.hasClass("open");if(!u||u&&t.keyCode==27)return n.click();r=e("[role=menu] li:not(.divider) a",o);if(!r.length)return;a=r.index(r.filter(":focus")),t.keyCode==38&&a>0&&a--,t.keyCode==40&&a<r.length-1&&a++,~a||(a=0),r.eq(a).focus()}},e.fn.dropdown=function(t){return this.each(function(){var r=e(this),i=r.data("dropdown");i||r.data("dropdown",i=new n(this)),typeof t=="string"&&i[t].call(r)})},e.fn.dropdown.Constructor=n,e(function(){e("html").on("click.dropdown.data-api touchstart.dropdown.data-api",r),e("body").on("click.dropdown touchstart.dropdown.data-api",".dropdown",function(e){e.stopPropagation()}).on("click.dropdown.data-api touchstart.dropdown.data-api",t,n.prototype.toggle).on("keydown.dropdown.data-api touchstart.dropdown.data-api",t+", [role=menu]",n.prototype.keydown)})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.options=n,this.$element=e(t).delegate('[data-dismiss="modal"]',"click.dismiss.modal",e.proxy(this.hide,this)),this.options.remote&&this.$element.find(".modal-body").load(this.options.remote)};t.prototype={constructor:t,toggle:function(){return this[this.isShown?"hide":"show"]()},show:function(){var t=this,n=e.Event("show");this.$element.trigger(n);if(this.isShown||n.isDefaultPrevented())return;e("body").addClass("modal-open"),this.isShown=!0,this.escape(),this.backdrop(function(){var n=e.support.transition&&t.$element.hasClass("fade");t.$element.parent().length||t.$element.appendTo(document.body),t.$element.show(),n&&t.$element[0].offsetWidth,t.$element.addClass("in").attr("aria-hidden",!1).focus(),t.enforceFocus(),n?t.$element.one(e.support.transition.end,function(){t.$element.trigger("shown")}):t.$element.trigger("shown")})},hide:function(t){t&&t.preventDefault();var n=this;t=e.Event("hide"),this.$element.trigger(t);if(!this.isShown||t.isDefaultPrevented())return;this.isShown=!1,e("body").removeClass("modal-open"),this.escape(),e(document).off("focusin.modal"),this.$element.removeClass("in").attr("aria-hidden",!0),e.support.transition&&this.$element.hasClass("fade")?this.hideWithTransition():this.hideModal()},enforceFocus:function(){var t=this;e(document).on("focusin.modal",function(e){t.$element[0]!==e.target&&!t.$element.has(e.target).length&&t.$element.focus()})},escape:function(){var e=this;this.isShown&&this.options.keyboard?this.$element.on("keyup.dismiss.modal",function(t){t.which==27&&e.hide()}):this.isShown||this.$element.off("keyup.dismiss.modal")},hideWithTransition:function(){var t=this,n=setTimeout(function(){t.$element.off(e.support.transition.end),t.hideModal()},500);this.$element.one(e.support.transition.end,function(){clearTimeout(n),t.hideModal()})},hideModal:function(e){this.$element.hide().trigger("hidden"),this.backdrop()},removeBackdrop:function(){this.$backdrop.remove(),this.$backdrop=null},backdrop:function(t){var n=this,r=this.$element.hasClass("fade")?"fade":"";if(this.isShown&&this.options.backdrop){var i=e.support.transition&&r;this.$backdrop=e('<div class="modal-backdrop '+r+'" />').appendTo(document.body),this.options.backdrop!="static"&&this.$backdrop.click(e.proxy(this.hide,this)),i&&this.$backdrop[0].offsetWidth,this.$backdrop.addClass("in"),i?this.$backdrop.one(e.support.transition.end,t):t()}else!this.isShown&&this.$backdrop?(this.$backdrop.removeClass("in"),e.support.transition&&this.$element.hasClass("fade")?this.$backdrop.one(e.support.transition.end,e.proxy(this.removeBackdrop,this)):this.removeBackdrop()):t&&t()}},e.fn.modal=function(n){return this.each(function(){var r=e(this),i=r.data("modal"),s=e.extend({},e.fn.modal.defaults,r.data(),typeof n=="object"&&n);i||r.data("modal",i=new t(this,s)),typeof n=="string"?i[n]():s.show&&i.show()})},e.fn.modal.defaults={backdrop:!0,keyboard:!0,show:!0},e.fn.modal.Constructor=t,e(function(){e("body").on("click.modal.data-api",'[data-toggle="modal"]',function(t){var n=e(this),r=n.attr("href"),i=e(n.attr("data-target")||r&&r.replace(/.*(?=#[^\s]+$)/,"")),s=i.data("modal")?"toggle":e.extend({remote:!/#/.test(r)&&r},i.data(),n.data());t.preventDefault(),i.modal(s).one("hide",function(){n.focus()})})})}(window.jQuery),!function(e){"use strict";var t=function(e,t){this.init("tooltip",e,t)};t.prototype={constructor:t,init:function(t,n,r){var i,s;this.type=t,this.$element=e(n),this.options=this.getOptions(r),this.enabled=!0,this.options.trigger=="click"?this.$element.on("click."+this.type,this.options.selector,e.proxy(this.toggle,this)):this.options.trigger!="manual"&&(i=this.options.trigger=="hover"?"mouseenter":"focus",s=this.options.trigger=="hover"?"mouseleave":"blur",this.$element.on(i+"."+this.type,this.options.selector,e.proxy(this.enter,this)),this.$element.on(s+"."+this.type,this.options.selector,e.proxy(this.leave,this))),this.options.selector?this._options=e.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},getOptions:function(t){return t=e.extend({},e.fn[this.type].defaults,t,this.$element.data()),t.delay&&typeof t.delay=="number"&&(t.delay={show:t.delay,hide:t.delay}),t},enter:function(t){var n=e(t.currentTarget)[this.type](this._options).data(this.type);if(!n.options.delay||!n.options.delay.show)return n.show();clearTimeout(this.timeout),n.hoverState="in",this.timeout=setTimeout(function(){n.hoverState=="in"&&n.show()},n.options.delay.show)},leave:function(t){var n=e(t.currentTarget)[this.type](this._options).data(this.type);this.timeout&&clearTimeout(this.timeout);if(!n.options.delay||!n.options.delay.hide)return n.hide();n.hoverState="out",this.timeout=setTimeout(function(){n.hoverState=="out"&&n.hide()},n.options.delay.hide)},show:function(){var e,t,n,r,i,s,o;if(this.hasContent()&&this.enabled){e=this.tip(),this.setContent(),this.options.animation&&e.addClass("fade"),s=typeof this.options.placement=="function"?this.options.placement.call(this,e[0],this.$element[0]):this.options.placement,t=/in/.test(s),e.remove().css({top:0,left:0,display:"block"}).appendTo(t?this.$element:document.body),n=this.getPosition(t),r=e[0].offsetWidth,i=e[0].offsetHeight;switch(t?s.split(" ")[1]:s){case"bottom":o={top:n.top+n.height,left:n.left+n.width/2-r/2};break;case"top":o={top:n.top-i,left:n.left+n.width/2-r/2};break;case"left":o={top:n.top+n.height/2-i/2,left:n.left-r};break;case"right":o={top:n.top+n.height/2-i/2,left:n.left+n.width}}e.css(o).addClass(s).addClass("in")}},setContent:function(){var e=this.tip(),t=this.getTitle();e.find(".tooltip-inner")[this.options.html?"html":"text"](t),e.removeClass("fade in top bottom left right")},hide:function(){function r(){var t=setTimeout(function(){n.off(e.support.transition.end).remove()},500);n.one(e.support.transition.end,function(){clearTimeout(t),n.remove()})}var t=this,n=this.tip();return n.removeClass("in"),e.support.transition&&this.$tip.hasClass("fade")?r():n.remove(),this},fixTitle:function(){var e=this.$element;(e.attr("title")||typeof e.attr("data-original-title")!="string")&&e.attr("data-original-title",e.attr("title")||"").removeAttr("title")},hasContent:function(){return this.getTitle()},getPosition:function(t){return e.extend({},t?{top:0,left:0}:this.$element.offset(),{width:this.$element[0].offsetWidth,height:this.$element[0].offsetHeight})},getTitle:function(){var e,t=this.$element,n=this.options;return e=t.attr("data-original-title")||(typeof n.title=="function"?n.title.call(t[0]):n.title),e},tip:function(){return this.$tip=this.$tip||e(this.options.template)},validate:function(){this.$element[0].parentNode||(this.hide(),this.$element=null,this.options=null)},enable:function(){this.enabled=!0},disable:function(){this.enabled=!1},toggleEnabled:function(){this.enabled=!this.enabled},toggle:function(){this[this.tip().hasClass("in")?"hide":"show"]()},destroy:function(){this.hide().$element.off("."+this.type).removeData(this.type)}},e.fn.tooltip=function(n){return this.each(function(){var r=e(this),i=r.data("tooltip"),s=typeof n=="object"&&n;i||r.data("tooltip",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.tooltip.Constructor=t,e.fn.tooltip.defaults={animation:!0,placement:"top",selector:!1,template:'<div class="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>',trigger:"hover",title:"",delay:0,html:!0}}(window.jQuery),!function(e){"use strict";var t=function(e,t){this.init("popover",e,t)};t.prototype=e.extend({},e.fn.tooltip.Constructor.prototype,{constructor:t,setContent:function(){var e=this.tip(),t=this.getTitle(),n=this.getContent();e.find(".popover-title")[this.options.html?"html":"text"](t),e.find(".popover-content > *")[this.options.html?"html":"text"](n),e.removeClass("fade top bottom left right in")},hasContent:function(){return this.getTitle()||this.getContent()},getContent:function(){var e,t=this.$element,n=this.options;return e=t.attr("data-content")||(typeof n.content=="function"?n.content.call(t[0]):n.content),e},tip:function(){return this.$tip||(this.$tip=e(this.options.template)),this.$tip},destroy:function(){this.hide().$element.off("."+this.type).removeData(this.type)}}),e.fn.popover=function(n){return this.each(function(){var r=e(this),i=r.data("popover"),s=typeof n=="object"&&n;i||r.data("popover",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.popover.Constructor=t,e.fn.popover.defaults=e.extend({},e.fn.tooltip.defaults,{placement:"right",trigger:"click",content:"",template:'<div class="popover"><div class="arrow"></div><div class="popover-inner"><h3 class="popover-title"></h3><div class="popover-content"><p></p></div></div></div>'})}(window.jQuery),!function(e){"use strict";function t(t,n){var r=e.proxy(this.process,this),i=e(t).is("body")?e(window):e(t),s;this.options=e.extend({},e.fn.scrollspy.defaults,n),this.$scrollElement=i.on("scroll.scroll-spy.data-api",r),this.selector=(this.options.target||(s=e(t).attr("href"))&&s.replace(/.*(?=#[^\s]+$)/,"")||"")+" .nav li > a",this.$body=e("body"),this.refresh(),this.process()}t.prototype={constructor:t,refresh:function(){var t=this,n;this.offsets=e([]),this.targets=e([]),n=this.$body.find(this.selector).map(function(){var t=e(this),n=t.data("target")||t.attr("href"),r=/^#\w/.test(n)&&e(n);return r&&r.length&&[[r.position().top,n]]||null}).sort(function(e,t){return e[0]-t[0]}).each(function(){t.offsets.push(this[0]),t.targets.push(this[1])})},process:function(){var e=this.$scrollElement.scrollTop()+this.options.offset,t=this.$scrollElement[0].scrollHeight||this.$body[0].scrollHeight,n=t-this.$scrollElement.height(),r=this.offsets,i=this.targets,s=this.activeTarget,o;if(e>=n)return s!=(o=i.last()[0])&&this.activate(o);for(o=r.length;o--;)s!=i[o]&&e>=r[o]&&(!r[o+1]||e<=r[o+1])&&this.activate(i[o])},activate:function(t){var n,r;this.activeTarget=t,e(this.selector).parent(".active").removeClass("active"),r=this.selector+'[data-target="'+t+'"],'+this.selector+'[href="'+t+'"]',n=e(r).parent("li").addClass("active"),n.parent(".dropdown-menu").length&&(n=n.closest("li.dropdown").addClass("active")),n.trigger("activate")}},e.fn.scrollspy=function(n){return this.each(function(){var r=e(this),i=r.data("scrollspy"),s=typeof n=="object"&&n;i||r.data("scrollspy",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.scrollspy.Constructor=t,e.fn.scrollspy.defaults={offset:10},e(window).on("load",function(){e('[data-spy="scroll"]').each(function(){var t=e(this);t.scrollspy(t.data())})})}(window.jQuery),!function(e){"use strict";var t=function(t){this.element=e(t)};t.prototype={constructor:t,show:function(){var t=this.element,n=t.closest("ul:not(.dropdown-menu)"),r=t.attr("data-target"),i,s,o;r||(r=t.attr("href"),r=r&&r.replace(/.*(?=#[^\s]*$)/,""));if(t.parent("li").hasClass("active"))return;i=n.find(".active a").last()[0],o=e.Event("show",{relatedTarget:i}),t.trigger(o);if(o.isDefaultPrevented())return;s=e(r),this.activate(t.parent("li"),n),this.activate(s,s.parent(),function(){t.trigger({type:"shown",relatedTarget:i})})},activate:function(t,n,r){function o(){i.removeClass("active").find("> .dropdown-menu > .active").removeClass("active"),t.addClass("active"),s?(t[0].offsetWidth,t.addClass("in")):t.removeClass("fade"),t.parent(".dropdown-menu")&&t.closest("li.dropdown").addClass("active"),r&&r()}var i=n.find("> .active"),s=r&&e.support.transition&&i.hasClass("fade");s?i.one(e.support.transition.end,o):o(),i.removeClass("in")}},e.fn.tab=function(n){return this.each(function(){var r=e(this),i=r.data("tab");i||r.data("tab",i=new t(this)),typeof n=="string"&&i[n]()})},e.fn.tab.Constructor=t,e(function(){e("body").on("click.tab.data-api",'[data-toggle="tab"], [data-toggle="pill"]',function(t){t.preventDefault(),e(this).tab("show")})})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.$element=e(t),this.options=e.extend({},e.fn.typeahead.defaults,n),this.matcher=this.options.matcher||this.matcher,this.sorter=this.options.sorter||this.sorter,this.highlighter=this.options.highlighter||this.highlighter,this.updater=this.options.updater||this.updater,this.$menu=e(this.options.menu).appendTo("body"),this.source=this.options.source,this.shown=!1,this.listen()};t.prototype={constructor:t,select:function(){var e=this.$menu.find(".active").attr("data-value");return this.$element.val(this.updater(e)).change(),this.hide()},updater:function(e){return e},show:function(){var t=e.extend({},this.$element.offset(),{height:this.$element[0].offsetHeight});return this.$menu.css({top:t.top+t.height,left:t.left}),this.$menu.show(),this.shown=!0,this},hide:function(){return this.$menu.hide(),this.shown=!1,this},lookup:function(t){var n;return this.query=this.$element.val(),!this.query||this.query.length<this.options.minLength?this.shown?this.hide():this:(n=e.isFunction(this.source)?this.source(this.query,e.proxy(this.process,this)):this.source,n?this.process(n):this)},process:function(t){var n=this;return t=e.grep(t,function(e){return n.matcher(e)}),t=this.sorter(t),t.length?this.render(t.slice(0,this.options.items)).show():this.shown?this.hide():this},matcher:function(e){return~e.toLowerCase().indexOf(this.query.toLowerCase())},sorter:function(e){var t=[],n=[],r=[],i;while(i=e.shift())i.toLowerCase().indexOf(this.query.toLowerCase())?~i.indexOf(this.query)?n.push(i):r.push(i):t.push(i);return t.concat(n,r)},highlighter:function(e){var t=this.query.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g,"\\$&");return e.replace(new RegExp("("+t+")","ig"),function(e,t){return"<strong>"+t+"</strong>"})},render:function(t){var n=this;return t=e(t).map(function(t,r){return t=e(n.options.item).attr("data-value",r),t.find("a").html(n.highlighter(r)),t[0]}),t.first().addClass("active"),this.$menu.html(t),this},next:function(t){var n=this.$menu.find(".active").removeClass("active"),r=n.next();r.length||(r=e(this.$menu.find("li")[0])),r.addClass("active")},prev:function(e){var t=this.$menu.find(".active").removeClass("active"),n=t.prev();n.length||(n=this.$menu.find("li").last()),n.addClass("active")},listen:function(){this.$element.on("blur",e.proxy(this.blur,this)).on("keypress",e.proxy(this.keypress,this)).on("keyup",e.proxy(this.keyup,this)),(e.browser.webkit||e.browser.msie)&&this.$element.on("keydown",e.proxy(this.keydown,this)),this.$menu.on("click",e.proxy(this.click,this)).on("mouseenter","li",e.proxy(this.mouseenter,this))},move:function(e){if(!this.shown)return;switch(e.keyCode){case 9:case 13:case 27:e.preventDefault();break;case 38:e.preventDefault(),this.prev();break;case 40:e.preventDefault(),this.next()}e.stopPropagation()},keydown:function(t){this.suppressKeyPressRepeat=!~e.inArray(t.keyCode,[40,38,9,13,27]),this.move(t)},keypress:function(e){if(this.suppressKeyPressRepeat)return;this.move(e)},keyup:function(e){switch(e.keyCode){case 40:case 38:break;case 9:case 13:if(!this.shown)return;this.select();break;case 27:if(!this.shown)return;this.hide();break;default:this.lookup()}e.stopPropagation(),e.preventDefault()},blur:function(e){var t=this;setTimeout(function(){t.hide()},150)},click:function(e){e.stopPropagation(),e.preventDefault(),this.select()},mouseenter:function(t){this.$menu.find(".active").removeClass("active"),e(t.currentTarget).addClass("active")}},e.fn.typeahead=function(n){return this.each(function(){var r=e(this),i=r.data("typeahead"),s=typeof n=="object"&&n;i||r.data("typeahead",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.typeahead.defaults={source:[],items:8,menu:'<ul class="typeahead dropdown-menu"></ul>',item:'<li><a href="#"></a></li>',minLength:1},e.fn.typeahead.Constructor=t,e(function(){e("body").on("focus.typeahead.data-api",'[data-provide="typeahead"]',function(t){var n=e(this);if(n.data("typeahead"))return;t.preventDefault(),n.typeahead(n.data())})})}(window.jQuery),!function(e){"use strict";var t=function(t,n){this.options=e.extend({},e.fn.affix.defaults,n),this.$window=e(window).on("scroll.affix.data-api",e.proxy(this.checkPosition,this)),this.$element=e(t),this.checkPosition()};t.prototype.checkPosition=function(){if(!this.$element.is(":visible"))return;var t=e(document).height(),n=this.$window.scrollTop(),r=this.$element.offset(),i=this.options.offset,s=i.bottom,o=i.top,u="affix affix-top affix-bottom",a;typeof i!="object"&&(s=o=i),typeof o=="function"&&(o=i.top()),typeof s=="function"&&(s=i.bottom()),a=this.unpin!=null&&n+this.unpin<=r.top?!1:s!=null&&r.top+this.$element.height()>=t-s?"bottom":o!=null&&n<=o?"top":!1;if(this.affixed===a)return;this.affixed=a,this.unpin=a=="bottom"?r.top-n:null,this.$element.removeClass(u).addClass("affix"+(a?"-"+a:""))},e.fn.affix=function(n){return this.each(function(){var r=e(this),i=r.data("affix"),s=typeof n=="object"&&n;i||r.data("affix",i=new t(this,s)),typeof n=="string"&&i[n]()})},e.fn.affix.Constructor=t,e.fn.affix.defaults={offset:0},e(window).on("load",function(){e('[data-spy="affix"]').each(function(){var t=e(this),n=t.data();n.offset=n.offset||{},n.offsetBottom&&(n.offset.bottom=n.offsetBottom),n.offsetTop&&(n.offset.top=n.offsetTop),t.affix(n)})})}(window.jQuery);
\ No newline at end of file
diff --git a/src/site/resources/js/highlight.pack.js b/src/site/resources/js/highlight.pack.js
deleted file mode 100644
index 786b1b1..0000000
--- a/src/site/resources/js/highlight.pack.js
+++ /dev/null
@@ -1,2 +0,0 @@
-/*! highlight.js v9.12.0 | BSD3 License | git.io/hljslicense */
-!function(e){var n="object"==typeof window&&window||"object"==typeof self&&self;"undefined"!=typeof exports?e(exports):n&&(n.hljs=e({}),"function"==typeof define&&define.amd&&define([],function(){return n.hljs}))}(function(e){function n(e){return e.replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">")}function t(e){return e.nodeName.toLowerCase()}function r(e,n){var t=e&&e.exec(n);return t&&0===t.index}function a(e){return k.test(e)}function i(e){var n,t,r,i,o=e.className+" ";if(o+=e.parentNode?e.parentNode.className:"",t=B.exec(o))return w(t[1])?t[1]:"no-highlight";for(o=o.split(/\s+/),n=0,r=o.length;r>n;n++)if(i=o[n],a(i)||w(i))return i}function o(e){var n,t={},r=Array.prototype.slice.call(arguments,1);for(n in e)t[n]=e[n];return r.forEach(function(e){for(n in e)t[n]=e[n]}),t}function u(e){var n=[];return function r(e,a){for(var i=e.firstChild;i;i=i.nextSibling)3===i.nodeType?a+=i.nodeValue.length:1===i.nodeType&&(n.push({event:"start",offset:a,node:i}),a=r(i,a),t(i).match(/br|hr|img|input/)||n.push({event:"stop",offset:a,node:i}));return a}(e,0),n}function c(e,r,a){function i(){return e.length&&r.length?e[0].offset!==r[0].offset?e[0].offset<r[0].offset?e:r:"start"===r[0].event?e:r:e.length?e:r}function o(e){function r(e){return" "+e.nodeName+'="'+n(e.value).replace('"',""")+'"'}s+="<"+t(e)+E.map.call(e.attributes,r).join("")+">"}function u(e){s+="</"+t(e)+">"}function c(e){("start"===e.event?o:u)(e.node)}for(var l=0,s="",f=[];e.length||r.length;){var g=i();if(s+=n(a.substring(l,g[0].offset)),l=g[0].offset,g===e){f.reverse().forEach(u);do c(g.splice(0,1)[0]),g=i();while(g===e&&g.length&&g[0].offset===l);f.reverse().forEach(o)}else"start"===g[0].event?f.push(g[0].node):f.pop(),c(g.splice(0,1)[0])}return s+n(a.substr(l))}function l(e){return e.v&&!e.cached_variants&&(e.cached_variants=e.v.map(function(n){return o(e,{v:null},n)})),e.cached_variants||e.eW&&[o(e)]||[e]}function s(e){function n(e){return e&&e.source||e}function t(t,r){return new RegExp(n(t),"m"+(e.cI?"i":"")+(r?"g":""))}function r(a,i){if(!a.compiled){if(a.compiled=!0,a.k=a.k||a.bK,a.k){var o={},u=function(n,t){e.cI&&(t=t.toLowerCase()),t.split(" ").forEach(function(e){var t=e.split("|");o[t[0]]=[n,t[1]?Number(t[1]):1]})};"string"==typeof a.k?u("keyword",a.k):x(a.k).forEach(function(e){u(e,a.k[e])}),a.k=o}a.lR=t(a.l||/\w+/,!0),i&&(a.bK&&(a.b="\\b("+a.bK.split(" ").join("|")+")\\b"),a.b||(a.b=/\B|\b/),a.bR=t(a.b),a.e||a.eW||(a.e=/\B|\b/),a.e&&(a.eR=t(a.e)),a.tE=n(a.e)||"",a.eW&&i.tE&&(a.tE+=(a.e?"|":"")+i.tE)),a.i&&(a.iR=t(a.i)),null==a.r&&(a.r=1),a.c||(a.c=[]),a.c=Array.prototype.concat.apply([],a.c.map(function(e){return l("self"===e?a:e)})),a.c.forEach(function(e){r(e,a)}),a.starts&&r(a.starts,i);var c=a.c.map(function(e){return e.bK?"\\.?("+e.b+")\\.?":e.b}).concat([a.tE,a.i]).map(n).filter(Boolean);a.t=c.length?t(c.join("|"),!0):{exec:function(){return null}}}}r(e)}function f(e,t,a,i){function o(e,n){var t,a;for(t=0,a=n.c.length;a>t;t++)if(r(n.c[t].bR,e))return n.c[t]}function u(e,n){if(r(e.eR,n)){for(;e.endsParent&&e.parent;)e=e.parent;return e}return e.eW?u(e.parent,n):void 0}function c(e,n){return!a&&r(n.iR,e)}function l(e,n){var t=N.cI?n[0].toLowerCase():n[0];return e.k.hasOwnProperty(t)&&e.k[t]}function p(e,n,t,r){var a=r?"":I.classPrefix,i='<span class="'+a,o=t?"":C;return i+=e+'">',i+n+o}function h(){var e,t,r,a;if(!E.k)return n(k);for(a="",t=0,E.lR.lastIndex=0,r=E.lR.exec(k);r;)a+=n(k.substring(t,r.index)),e=l(E,r),e?(B+=e[1],a+=p(e[0],n(r[0]))):a+=n(r[0]),t=E.lR.lastIndex,r=E.lR.exec(k);return a+n(k.substr(t))}function d(){var e="string"==typeof E.sL;if(e&&!y[E.sL])return n(k);var t=e?f(E.sL,k,!0,x[E.sL]):g(k,E.sL.length?E.sL:void 0);return E.r>0&&(B+=t.r),e&&(x[E.sL]=t.top),p(t.language,t.value,!1,!0)}function b(){L+=null!=E.sL?d():h(),k=""}function v(e){L+=e.cN?p(e.cN,"",!0):"",E=Object.create(e,{parent:{value:E}})}function m(e,n){if(k+=e,null==n)return b(),0;var t=o(n,E);if(t)return t.skip?k+=n:(t.eB&&(k+=n),b(),t.rB||t.eB||(k=n)),v(t,n),t.rB?0:n.length;var r=u(E,n);if(r){var a=E;a.skip?k+=n:(a.rE||a.eE||(k+=n),b(),a.eE&&(k=n));do E.cN&&(L+=C),E.skip||(B+=E.r),E=E.parent;while(E!==r.parent);return r.starts&&v(r.starts,""),a.rE?0:n.length}if(c(n,E))throw new Error('Illegal lexeme "'+n+'" for mode "'+(E.cN||"<unnamed>")+'"');return k+=n,n.length||1}var N=w(e);if(!N)throw new Error('Unknown language: "'+e+'"');s(N);var R,E=i||N,x={},L="";for(R=E;R!==N;R=R.parent)R.cN&&(L=p(R.cN,"",!0)+L);var k="",B=0;try{for(var M,j,O=0;;){if(E.t.lastIndex=O,M=E.t.exec(t),!M)break;j=m(t.substring(O,M.index),M[0]),O=M.index+j}for(m(t.substr(O)),R=E;R.parent;R=R.parent)R.cN&&(L+=C);return{r:B,value:L,language:e,top:E}}catch(T){if(T.message&&-1!==T.message.indexOf("Illegal"))return{r:0,value:n(t)};throw T}}function g(e,t){t=t||I.languages||x(y);var r={r:0,value:n(e)},a=r;return t.filter(w).forEach(function(n){var t=f(n,e,!1);t.language=n,t.r>a.r&&(a=t),t.r>r.r&&(a=r,r=t)}),a.language&&(r.second_best=a),r}function p(e){return I.tabReplace||I.useBR?e.replace(M,function(e,n){return I.useBR&&"\n"===e?"<br>":I.tabReplace?n.replace(/\t/g,I.tabReplace):""}):e}function h(e,n,t){var r=n?L[n]:t,a=[e.trim()];return e.match(/\bhljs\b/)||a.push("hljs"),-1===e.indexOf(r)&&a.push(r),a.join(" ").trim()}function d(e){var n,t,r,o,l,s=i(e);a(s)||(I.useBR?(n=document.createElementNS("http://www.w3.org/1999/xhtml","div"),n.innerHTML=e.innerHTML.replace(/\n/g,"").replace(/<br[ \/]*>/g,"\n")):n=e,l=n.textContent,r=s?f(s,l,!0):g(l),t=u(n),t.length&&(o=document.createElementNS("http://www.w3.org/1999/xhtml","div"),o.innerHTML=r.value,r.value=c(t,u(o),l)),r.value=p(r.value),e.innerHTML=r.value,e.className=h(e.className,s,r.language),e.result={language:r.language,re:r.r},r.second_best&&(e.second_best={language:r.second_best.language,re:r.second_best.r}))}function b(e){I=o(I,e)}function v(){if(!v.called){v.called=!0;var e=document.querySelectorAll("pre code");E.forEach.call(e,d)}}function m(){addEventListener("DOMContentLoaded",v,!1),addEventListener("load",v,!1)}function N(n,t){var r=y[n]=t(e);r.aliases&&r.aliases.forEach(function(e){L[e]=n})}function R(){return x(y)}function w(e){return e=(e||"").toLowerCase(),y[e]||y[L[e]]}var E=[],x=Object.keys,y={},L={},k=/^(no-?highlight|plain|text)$/i,B=/\blang(?:uage)?-([\w-]+)\b/i,M=/((^(<[^>]+>|\t|)+|(?:\n)))/gm,C="</span>",I={classPrefix:"hljs-",tabReplace:null,useBR:!1,languages:void 0};return e.highlight=f,e.highlightAuto=g,e.fixMarkup=p,e.highlightBlock=d,e.configure=b,e.initHighlighting=v,e.initHighlightingOnLoad=m,e.registerLanguage=N,e.listLanguages=R,e.getLanguage=w,e.inherit=o,e.IR="[a-zA-Z]\\w*",e.UIR="[a-zA-Z_]\\w*",e.NR="\\b\\d+(\\.\\d+)?",e.CNR="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",e.BNR="\\b(0b[01]+)",e.RSR="!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",e.BE={b:"\\\\[\\s\\S]",r:0},e.ASM={cN:"string",b:"'",e:"'",i:"\\n",c:[e.BE]},e.QSM={cN:"string",b:'"',e:'"',i:"\\n",c:[e.BE]},e.PWM={b:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/},e.C=function(n,t,r){var a=e.inherit({cN:"comment",b:n,e:t,c:[]},r||{});return a.c.push(e.PWM),a.c.push({cN:"doctag",b:"(?:TODO|FIXME|NOTE|BUG|XXX):",r:0}),a},e.CLCM=e.C("//","$"),e.CBCM=e.C("/\\*","\\*/"),e.HCM=e.C("#","$"),e.NM={cN:"number",b:e.NR,r:0},e.CNM={cN:"number",b:e.CNR,r:0},e.BNM={cN:"number",b:e.BNR,r:0},e.CSSNM={cN:"number",b:e.NR+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",r:0},e.RM={cN:"regexp",b:/\//,e:/\/[gimuy]*/,i:/\n/,c:[e.BE,{b:/\[/,e:/\]/,r:0,c:[e.BE]}]},e.TM={cN:"title",b:e.IR,r:0},e.UTM={cN:"title",b:e.UIR,r:0},e.METHOD_GUARD={b:"\\.\\s*"+e.UIR,r:0},e});hljs.registerLanguage("javascript",function(e){var r="[A-Za-z$_][0-9A-Za-z$_]*",t={keyword:"in of if for while finally var new function do return void else break catch instanceof with throw case default try this switch continue typeof delete let yield const export super debugger as async await static import from as",literal:"true false null undefined NaN Infinity",built_in:"eval isFinite isNaN parseFloat parseInt decodeURI decodeURIComponent encodeURI encodeURIComponent escape unescape Object Function Boolean Error EvalError InternalError RangeError ReferenceError StopIteration SyntaxError TypeError URIError Number Math Date String RegExp Array Float32Array Float64Array Int16Array Int32Array Int8Array Uint16Array Uint32Array Uint8Array Uint8ClampedArray ArrayBuffer DataView JSON Intl arguments require module console window document Symbol Set Map WeakSet WeakMap Proxy Reflect Promise"},a={cN:"number",v:[{b:"\\b(0[bB][01]+)"},{b:"\\b(0[oO][0-7]+)"},{b:e.CNR}],r:0},n={cN:"subst",b:"\\$\\{",e:"\\}",k:t,c:[]},c={cN:"string",b:"`",e:"`",c:[e.BE,n]};n.c=[e.ASM,e.QSM,c,a,e.RM];var s=n.c.concat([e.CBCM,e.CLCM]);return{aliases:["js","jsx"],k:t,c:[{cN:"meta",r:10,b:/^\s*['"]use (strict|asm)['"]/},{cN:"meta",b:/^#!/,e:/$/},e.ASM,e.QSM,c,e.CLCM,e.CBCM,a,{b:/[{,]\s*/,r:0,c:[{b:r+"\\s*:",rB:!0,r:0,c:[{cN:"attr",b:r,r:0}]}]},{b:"("+e.RSR+"|\\b(case|return|throw)\\b)\\s*",k:"return throw case",c:[e.CLCM,e.CBCM,e.RM,{cN:"function",b:"(\\(.*?\\)|"+r+")\\s*=>",rB:!0,e:"\\s*=>",c:[{cN:"params",v:[{b:r},{b:/\(\s*\)/},{b:/\(/,e:/\)/,eB:!0,eE:!0,k:t,c:s}]}]},{b:/</,e:/(\/\w+|\w+\/)>/,sL:"xml",c:[{b:/<\w+\s*\/>/,skip:!0},{b:/<\w+/,e:/(\/\w+|\w+\/)>/,skip:!0,c:[{b:/<\w+\s*\/>/,skip:!0},"self"]}]}],r:0},{cN:"function",bK:"function",e:/\{/,eE:!0,c:[e.inherit(e.TM,{b:r}),{cN:"params",b:/\(/,e:/\)/,eB:!0,eE:!0,c:s}],i:/\[|%/},{b:/\$[(.]/},e.METHOD_GUARD,{cN:"class",bK:"class",e:/[{;=]/,eE:!0,i:/[:"\[\]]/,c:[{bK:"extends"},e.UTM]},{bK:"constructor",e:/\{/,eE:!0}],i:/#(?!!)/}});hljs.registerLanguage("xml",function(s){var e="[A-Za-z0-9\\._:-]+",t={eW:!0,i:/</,r:0,c:[{cN:"attr",b:e,r:0},{b:/=\s*/,r:0,c:[{cN:"string",endsParent:!0,v:[{b:/"/,e:/"/},{b:/'/,e:/'/},{b:/[^\s"'=<>`]+/}]}]}]};return{aliases:["html","xhtml","rss","atom","xjb","xsd","xsl","plist"],cI:!0,c:[{cN:"meta",b:"<!DOCTYPE",e:">",r:10,c:[{b:"\\[",e:"\\]"}]},s.C("<!--","-->",{r:10}),{b:"<\\!\\[CDATA\\[",e:"\\]\\]>",r:10},{b:/<\?(php)?/,e:/\?>/,sL:"php",c:[{b:"/\\*",e:"\\*/",skip:!0}]},{cN:"tag",b:"<style(?=\\s|>|$)",e:">",k:{name:"style"},c:[t],starts:{e:"</style>",rE:!0,sL:["css","xml"]}},{cN:"tag",b:"<script(?=\\s|>|$)",e:">",k:{name:"script"},c:[t],starts:{e:"</script>",rE:!0,sL:["actionscript","javascript","handlebars","xml"]}},{cN:"meta",v:[{b:/<\?xml/,e:/\?>/,r:10},{b:/<\?\w+/,e:/\?>/}]},{cN:"tag",b:"</?",e:"/?>",c:[{cN:"name",b:/[^\/><\s]+/,r:0},t]}]}});hljs.registerLanguage("gradle",function(e){return{cI:!0,k:{keyword:"task project allprojects subprojects artifacts buildscript configurations dependencies repositories sourceSets description delete from into include exclude source classpath destinationDir includes options sourceCompatibility targetCompatibility group flatDir doLast doFirst flatten todir fromdir ant def abstract break case catch continue default do else extends final finally for if implements instanceof native new private protected public return static switch synchronized throw throws transient try volatile while strictfp package import false null super this true antlrtask checkstyle codenarc copy boolean byte char class double float int interface long short void compile runTime file fileTree abs any append asList asWritable call collect compareTo count div dump each eachByte eachFile eachLine every find findAll flatten getAt getErr getIn getOut getText grep immutable inject inspect intersect invokeMethods isCase join leftShift minus multiply newInputStream newOutputStream newPrintWriter newReader newWriter next plus pop power previous print println push putAt read readBytes readLines reverse reverseEach round size sort splitEachLine step subMap times toInteger toList tokenize upto waitForOrKill withPrintWriter withReader withStream withWriter withWriterAppend write writeLine"},c:[e.CLCM,e.CBCM,e.ASM,e.QSM,e.NM,e.RM]}});hljs.registerLanguage("ruby",function(e){var b="[a-zA-Z_]\\w*[!?=]?|[-+~]\\@|<<|>>|=~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~`|]|\\[\\]=?",r={keyword:"and then defined module in return redo if BEGIN retry end for self when next until do begin unless END rescue else break undef not super class case require yield alias while ensure elsif or include attr_reader attr_writer attr_accessor",literal:"true false nil"},c={cN:"doctag",b:"@[A-Za-z]+"},a={b:"#<",e:">"},s=[e.C("#","$",{c:[c]}),e.C("^\\=begin","^\\=end",{c:[c],r:10}),e.C("^__END__","\\n$")],n={cN:"subst",b:"#\\{",e:"}",k:r},t={cN:"string",c:[e.BE,n],v:[{b:/'/,e:/'/},{b:/"/,e:/"/},{b:/`/,e:/`/},{b:"%[qQwWx]?\\(",e:"\\)"},{b:"%[qQwWx]?\\[",e:"\\]"},{b:"%[qQwWx]?{",e:"}"},{b:"%[qQwWx]?<",e:">"},{b:"%[qQwWx]?/",e:"/"},{b:"%[qQwWx]?%",e:"%"},{b:"%[qQwWx]?-",e:"-"},{b:"%[qQwWx]?\\|",e:"\\|"},{b:/\B\?(\\\d{1,3}|\\x[A-Fa-f0-9]{1,2}|\\u[A-Fa-f0-9]{4}|\\?\S)\b/},{b:/<<(-?)\w+$/,e:/^\s*\w+$/}]},i={cN:"params",b:"\\(",e:"\\)",endsParent:!0,k:r},d=[t,a,{cN:"class",bK:"class module",e:"$|;",i:/=/,c:[e.inherit(e.TM,{b:"[A-Za-z_]\\w*(::\\w+)*(\\?|\\!)?"}),{b:"<\\s*",c:[{b:"("+e.IR+"::)?"+e.IR}]}].concat(s)},{cN:"function",bK:"def",e:"$|;",c:[e.inherit(e.TM,{b:b}),i].concat(s)},{b:e.IR+"::"},{cN:"symbol",b:e.UIR+"(\\!|\\?)?:",r:0},{cN:"symbol",b:":(?!\\s)",c:[t,{b:b}],r:0},{cN:"number",b:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",r:0},{b:"(\\$\\W)|((\\$|\\@\\@?)(\\w+))"},{cN:"params",b:/\|/,e:/\|/,k:r},{b:"("+e.RSR+"|unless)\\s*",k:"unless",c:[a,{cN:"regexp",c:[e.BE,n],i:/\n/,v:[{b:"/",e:"/[a-z]*"},{b:"%r{",e:"}[a-z]*"},{b:"%r\\(",e:"\\)[a-z]*"},{b:"%r!",e:"![a-z]*"},{b:"%r\\[",e:"\\][a-z]*"}]}].concat(s),r:0}].concat(s);n.c=d,i.c=d;var l="[>?]>",o="[\\w#]+\\(\\w+\\):\\d+:\\d+>",u="(\\w+-)?\\d+\\.\\d+\\.\\d(p\\d+)?[^>]+>",w=[{b:/^\s*=>/,starts:{e:"$",c:d}},{cN:"meta",b:"^("+l+"|"+o+"|"+u+")",starts:{e:"$",c:d}}];return{aliases:["rb","gemspec","podspec","thor","irb"],k:r,i:/\/\*/,c:s.concat(w).concat(d)}});hljs.registerLanguage("yaml",function(e){var b="true false yes no null",a="^[ \\-]*",r="[a-zA-Z_][\\w\\-]*",t={cN:"attr",v:[{b:a+r+":"},{b:a+'"'+r+'":'},{b:a+"'"+r+"':"}]},c={cN:"template-variable",v:[{b:"{{",e:"}}"},{b:"%{",e:"}"}]},l={cN:"string",r:0,v:[{b:/'/,e:/'/},{b:/"/,e:/"/},{b:/\S+/}],c:[e.BE,c]};return{cI:!0,aliases:["yml","YAML","yaml"],c:[t,{cN:"meta",b:"^---s*$",r:10},{cN:"string",b:"[\\|>] *$",rE:!0,c:l.c,e:t.v[0].b},{b:"<%[%=-]?",e:"[%-]?%>",sL:"ruby",eB:!0,eE:!0,r:0},{cN:"type",b:"!!"+e.UIR},{cN:"meta",b:"&"+e.UIR+"$"},{cN:"meta",b:"\\*"+e.UIR+"$"},{cN:"bullet",b:"^ *-",r:0},e.HCM,{bK:b,k:{literal:b}},e.CNM,l]}});hljs.registerLanguage("groovy",function(e){return{k:{literal:"true false null",keyword:"byte short char int long boolean float double void def as in assert trait super this abstract static volatile transient public private protected synchronized final class interface enum if else for while switch case break default continue throw throws try catch finally implements extends new import package return instanceof"},c:[e.C("/\\*\\*","\\*/",{r:0,c:[{b:/\w+@/,r:0},{cN:"doctag",b:"@[A-Za-z]+"}]}),e.CLCM,e.CBCM,{cN:"string",b:'"""',e:'"""'},{cN:"string",b:"'''",e:"'''"},{cN:"string",b:"\\$/",e:"/\\$",r:10},e.ASM,{cN:"regexp",b:/~?\/[^\/\n]+\//,c:[e.BE]},e.QSM,{cN:"meta",b:"^#!/usr/bin/env",e:"$",i:"\n"},e.BNM,{cN:"class",bK:"class interface trait enum",e:"{",i:":",c:[{bK:"extends implements"},e.UTM]},e.CNM,{cN:"meta",b:"@[A-Za-z]+"},{cN:"string",b:/[^\?]{0}[A-Za-z0-9_$]+ *:/},{b:/\?/,e:/\:/},{cN:"symbol",b:"^\\s*[A-Za-z0-9_$]+:",r:0}],i:/#|<\//}});hljs.registerLanguage("bash",function(e){var t={cN:"variable",v:[{b:/\$[\w\d#@][\w\d_]*/},{b:/\$\{(.*?)}/}]},s={cN:"string",b:/"/,e:/"/,c:[e.BE,t,{cN:"variable",b:/\$\(/,e:/\)/,c:[e.BE]}]},a={cN:"string",b:/'/,e:/'/};return{aliases:["sh","zsh"],l:/\b-?[a-z\._]+\b/,k:{keyword:"if then else elif fi for while in do done case esac function",literal:"true false",built_in:"break cd continue eval exec exit export getopts hash pwd readonly return shift test times trap umask unset alias bind builtin caller command declare echo enable help let local logout mapfile printf read readarray source type typeset ulimit unalias set shopt autoload bg bindkey bye cap chdir clone comparguments compcall compctl compdescribe compfiles compgroups compquote comptags comptry compvalues dirs disable disown echotc echoti emulate fc fg float functions getcap getln history integer jobs kill limit log noglob popd print pushd pushln rehash sched setcap setopt stat suspend ttyctl unfunction unhash unlimit unsetopt vared wait whence where which zcompile zformat zftp zle zmodload zparseopts zprof zpty zregexparse zsocket zstyle ztcp",_:"-ne -eq -lt -gt -f -d -e -s -l -a"},c:[{cN:"meta",b:/^#![^\n]+sh\s*$/,r:10},{cN:"function",b:/\w[\w\d_]*\s*\(\s*\)\s*\{/,rB:!0,c:[e.inherit(e.TM,{b:/\w[\w\d_]*/})],r:0},e.HCM,s,a,t]}});hljs.registerLanguage("kotlin",function(e){var t={keyword:"abstract as val var vararg get set class object open private protected public noinline crossinline dynamic final enum if else do while for when throw try catch finally import package is in fun override companion reified inline lateinit initinterface annotation data sealed internal infix operator out by constructor super trait volatile transient native default",built_in:"Byte Short Char Int Long Boolean Float Double Void Unit Nothing",literal:"true false null"},r={cN:"keyword",b:/\b(break|continue|return|this)\b/,starts:{c:[{cN:"symbol",b:/@\w+/}]}},i={cN:"symbol",b:e.UIR+"@"},n={cN:"subst",b:"\\${",e:"}",c:[e.ASM,e.CNM]},a={cN:"variable",b:"\\$"+e.UIR},c={cN:"string",v:[{b:'"""',e:'"""',c:[a,n]},{b:"'",e:"'",i:/\n/,c:[e.BE]},{b:'"',e:'"',i:/\n/,c:[e.BE,a,n]}]},s={cN:"meta",b:"@(?:file|property|field|get|set|receiver|param|setparam|delegate)\\s*:(?:\\s*"+e.UIR+")?"},o={cN:"meta",b:"@"+e.UIR,c:[{b:/\(/,e:/\)/,c:[e.inherit(c,{cN:"meta-string"})]}]};return{k:t,c:[e.C("/\\*\\*","\\*/",{r:0,c:[{cN:"doctag",b:"@[A-Za-z]+"}]}),e.CLCM,e.CBCM,r,i,s,o,{cN:"function",bK:"fun",e:"[(]|$",rB:!0,eE:!0,k:t,i:/fun\s+(<.*>)?[^\s\(]+(\s+[^\s\(]+)\s*=/,r:5,c:[{b:e.UIR+"\\s*\\(",rB:!0,r:0,c:[e.UTM]},{cN:"type",b:/</,e:/>/,k:"reified",r:0},{cN:"params",b:/\(/,e:/\)/,endsParent:!0,k:t,r:0,c:[{b:/:/,e:/[=,\/]/,eW:!0,c:[{cN:"type",b:e.UIR},e.CLCM,e.CBCM],r:0},e.CLCM,e.CBCM,s,o,c,e.CNM]},e.CBCM]},{cN:"class",bK:"class interface trait",e:/[:\{(]|$/,eE:!0,i:"extends implements",c:[{bK:"public protected internal private constructor"},e.UTM,{cN:"type",b:/</,e:/>/,eB:!0,eE:!0,r:0},{cN:"type",b:/[,:]\s*/,e:/[<\(,]|$/,eB:!0,rE:!0},s,o]},c,{cN:"meta",b:"^#!/usr/bin/env",e:"$",i:"\n"},e.CNM]}});hljs.registerLanguage("json",function(e){var i={literal:"true false null"},n=[e.QSM,e.CNM],r={e:",",eW:!0,eE:!0,c:n,k:i},t={b:"{",e:"}",c:[{cN:"attr",b:/"/,e:/"/,c:[e.BE],i:"\\n"},e.inherit(r,{b:/:/})],i:"\\S"},c={b:"\\[",e:"\\]",c:[e.inherit(r)],i:"\\S"};return n.splice(n.length,0,t,c),{c:n,k:i,i:"\\S"}});hljs.registerLanguage("scala",function(e){var t={cN:"meta",b:"@[A-Za-z]+"},a={cN:"subst",v:[{b:"\\$[A-Za-z0-9_]+"},{b:"\\${",e:"}"}]},r={cN:"string",v:[{b:'"',e:'"',i:"\\n",c:[e.BE]},{b:'"""',e:'"""',r:10},{b:'[a-z]+"',e:'"',i:"\\n",c:[e.BE,a]},{cN:"string",b:'[a-z]+"""',e:'"""',c:[a],r:10}]},c={cN:"symbol",b:"'\\w[\\w\\d_]*(?!')"},i={cN:"type",b:"\\b[A-Z][A-Za-z0-9_]*",r:0},s={cN:"title",b:/[^0-9\n\t "'(),.`{}\[\]:;][^\n\t "'(),.`{}\[\]:;]+|[^0-9\n\t "'(),.`{}\[\]:;=]/,r:0},n={cN:"class",bK:"class object trait type",e:/[:={\[\n;]/,eE:!0,c:[{bK:"extends with",r:10},{b:/\[/,e:/\]/,eB:!0,eE:!0,r:0,c:[i]},{cN:"params",b:/\(/,e:/\)/,eB:!0,eE:!0,r:0,c:[i]},s]},l={cN:"function",bK:"def",e:/[:={\[(\n;]/,eE:!0,c:[s]};return{k:{literal:"true false null",keyword:"type yield lazy override def with val var sealed abstract private trait object if forSome for while throw finally protected extends import final return else break new catch super class case package default try this match continue throws implicit"},c:[e.CLCM,e.CBCM,r,c,i,l,n,e.CNM,t]}});hljs.registerLanguage("java",function(e){var a="[À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*",t=a+"(<"+a+"(\\s*,\\s*"+a+")*>)?",r="false synchronized int abstract float private char boolean static null if const for true while long strictfp finally protected import native final void enum else break transient catch instanceof byte super volatile case assert short package default double public try this switch continue throws protected public private module requires exports do",s="\\b(0[bB]([01]+[01_]+[01]+|[01]+)|0[xX]([a-fA-F0-9]+[a-fA-F0-9_]+[a-fA-F0-9]+|[a-fA-F0-9]+)|(([\\d]+[\\d_]+[\\d]+|[\\d]+)(\\.([\\d]+[\\d_]+[\\d]+|[\\d]+))?|\\.([\\d]+[\\d_]+[\\d]+|[\\d]+))([eE][-+]?\\d+)?)[lLfF]?",c={cN:"number",b:s,r:0};return{aliases:["jsp"],k:r,i:/<\/|#/,c:[e.C("/\\*\\*","\\*/",{r:0,c:[{b:/\w+@/,r:0},{cN:"doctag",b:"@[A-Za-z]+"}]}),e.CLCM,e.CBCM,e.ASM,e.QSM,{cN:"class",bK:"class interface",e:/[{;=]/,eE:!0,k:"class interface",i:/[:"\[\]]/,c:[{bK:"extends implements"},e.UTM]},{bK:"new throw return else",r:0},{cN:"function",b:"("+t+"\\s+)+"+e.UIR+"\\s*\\(",rB:!0,e:/[{;=]/,eE:!0,k:r,c:[{b:e.UIR+"\\s*\\(",rB:!0,r:0,c:[e.UTM]},{cN:"params",b:/\(/,e:/\)/,k:r,r:0,c:[e.ASM,e.QSM,e.CNM,e.CBCM]},e.CLCM,e.CBCM]},c,{cN:"meta",b:"@[A-Za-z]+"}]}});hljs.registerLanguage("shell",function(s){return{aliases:["console"],c:[{cN:"meta",b:"^\\s{0,3}[\\w\\d\\[\\]()@-]*[>%$#]",starts:{e:"$",sL:"bash"}}]}});hljs.registerLanguage("sql",function(e){var t=e.C("--","$");return{cI:!0,i:/[<>{}*#]/,c:[{bK:"begin end start commit rollback savepoint lock alter create drop rename call delete do handler insert load replace select truncate update set show pragma grant merge describe use explain help declare prepare execute deallocate release unlock purge reset change stop analyze cache flush optimize repair kill install uninstall checksum restore check backup revoke comment",e:/;/,eW:!0,l:/[\w\.]+/,k:{keyword:"abort abs absolute acc acce accep accept access accessed accessible account acos action activate add addtime admin administer advanced advise aes_decrypt aes_encrypt after agent aggregate ali alia alias allocate allow alter always analyze ancillary and any anydata anydataset anyschema anytype apply archive archived archivelog are as asc ascii asin assembly assertion associate asynchronous at atan atn2 attr attri attrib attribu attribut attribute attributes audit authenticated authentication authid authors auto autoallocate autodblink autoextend automatic availability avg backup badfile basicfile before begin beginning benchmark between bfile bfile_base big bigfile bin binary_double binary_float binlog bit_and bit_count bit_length bit_or bit_xor bitmap blob_base block blocksize body both bound buffer_cache buffer_pool build bulk by byte byteordermark bytes cache caching call calling cancel capacity cascade cascaded case cast catalog category ceil ceiling chain change changed char_base char_length character_length characters characterset charindex charset charsetform charsetid check checksum checksum_agg child choose chr chunk class cleanup clear client clob clob_base clone close cluster_id cluster_probability cluster_set clustering coalesce coercibility col collate collation collect colu colum column column_value columns columns_updated comment commit compact compatibility compiled complete composite_limit compound compress compute concat concat_ws concurrent confirm conn connec connect connect_by_iscycle connect_by_isleaf connect_by_root connect_time connection consider consistent constant constraint constraints constructor container content contents context contributors controlfile conv convert convert_tz corr corr_k corr_s corresponding corruption cos cost count count_big counted covar_pop covar_samp cpu_per_call cpu_per_session crc32 create creation critical cross cube cume_dist curdate current current_date current_time current_timestamp current_user cursor curtime customdatum cycle data database databases datafile datafiles datalength date_add date_cache date_format date_sub dateadd datediff datefromparts datename datepart datetime2fromparts day day_to_second dayname dayofmonth dayofweek dayofyear days db_role_change dbtimezone ddl deallocate declare decode decompose decrement decrypt deduplicate def defa defau defaul default defaults deferred defi defin define degrees delayed delegate delete delete_all delimited demand dense_rank depth dequeue des_decrypt des_encrypt des_key_file desc descr descri describ describe descriptor deterministic diagnostics difference dimension direct_load directory disable disable_all disallow disassociate discardfile disconnect diskgroup distinct distinctrow distribute distributed div do document domain dotnet double downgrade drop dumpfile duplicate duration each edition editionable editions element ellipsis else elsif elt empty enable enable_all enclosed encode encoding encrypt end end-exec endian enforced engine engines enqueue enterprise entityescaping eomonth error errors escaped evalname evaluate event eventdata events except exception exceptions exchange exclude excluding execu execut execute exempt exists exit exp expire explain export export_set extended extent external external_1 external_2 externally extract failed failed_login_attempts failover failure far fast feature_set feature_value fetch field fields file file_name_convert filesystem_like_logging final finish first first_value fixed flash_cache flashback floor flush following follows for forall force form forma format found found_rows freelist freelists freepools fresh from from_base64 from_days ftp full function general generated get get_format get_lock getdate getutcdate global global_name globally go goto grant grants greatest group group_concat group_id grouping grouping_id groups gtid_subtract guarantee guard handler hash hashkeys having hea head headi headin heading heap help hex hierarchy high high_priority hosts hour http id ident_current ident_incr ident_seed identified identity idle_time if ifnull ignore iif ilike ilm immediate import in include including increment index indexes indexing indextype indicator indices inet6_aton inet6_ntoa inet_aton inet_ntoa infile initial initialized initially initrans inmemory inner innodb input insert install instance instantiable instr interface interleaved intersect into invalidate invisible is is_free_lock is_ipv4 is_ipv4_compat is_not is_not_null is_used_lock isdate isnull isolation iterate java join json json_exists keep keep_duplicates key keys kill language large last last_day last_insert_id last_value lax lcase lead leading least leaves left len lenght length less level levels library like like2 like4 likec limit lines link list listagg little ln load load_file lob lobs local localtime localtimestamp locate locator lock locked log log10 log2 logfile logfiles logging logical logical_reads_per_call logoff logon logs long loop low low_priority lower lpad lrtrim ltrim main make_set makedate maketime managed management manual map mapping mask master master_pos_wait match matched materialized max maxextents maximize maxinstances maxlen maxlogfiles maxloghistory maxlogmembers maxsize maxtrans md5 measures median medium member memcompress memory merge microsecond mid migration min minextents minimum mining minus minute minvalue missing mod mode model modification modify module monitoring month months mount move movement multiset mutex name name_const names nan national native natural nav nchar nclob nested never new newline next nextval no no_write_to_binlog noarchivelog noaudit nobadfile nocheck nocompress nocopy nocycle nodelay nodiscardfile noentityescaping noguarantee nokeep nologfile nomapping nomaxvalue nominimize nominvalue nomonitoring none noneditionable nonschema noorder nopr nopro noprom nopromp noprompt norely noresetlogs noreverse normal norowdependencies noschemacheck noswitch not nothing notice notrim novalidate now nowait nth_value nullif nulls num numb numbe nvarchar nvarchar2 object ocicoll ocidate ocidatetime ociduration ociinterval ociloblocator ocinumber ociref ocirefcursor ocirowid ocistring ocitype oct octet_length of off offline offset oid oidindex old on online only opaque open operations operator optimal optimize option optionally or oracle oracle_date oradata ord ordaudio orddicom orddoc order ordimage ordinality ordvideo organization orlany orlvary out outer outfile outline output over overflow overriding package pad parallel parallel_enable parameters parent parse partial partition partitions pascal passing password password_grace_time password_lock_time password_reuse_max password_reuse_time password_verify_function patch path patindex pctincrease pctthreshold pctused pctversion percent percent_rank percentile_cont percentile_disc performance period period_add period_diff permanent physical pi pipe pipelined pivot pluggable plugin policy position post_transaction pow power pragma prebuilt precedes preceding precision prediction prediction_cost prediction_details prediction_probability prediction_set prepare present preserve prior priority private private_sga privileges procedural procedure procedure_analyze processlist profiles project prompt protection public publishingservername purge quarter query quick quiesce quota quotename radians raise rand range rank raw read reads readsize rebuild record records recover recovery recursive recycle redo reduced ref reference referenced references referencing refresh regexp_like register regr_avgx regr_avgy regr_count regr_intercept regr_r2 regr_slope regr_sxx regr_sxy reject rekey relational relative relaylog release release_lock relies_on relocate rely rem remainder rename repair repeat replace replicate replication required reset resetlogs resize resource respect restore restricted result result_cache resumable resume retention return returning returns reuse reverse revoke right rlike role roles rollback rolling rollup round row row_count rowdependencies rowid rownum rows rtrim rules safe salt sample save savepoint sb1 sb2 sb4 scan schema schemacheck scn scope scroll sdo_georaster sdo_topo_geometry search sec_to_time second section securefile security seed segment select self sequence sequential serializable server servererror session session_user sessions_per_user set sets settings sha sha1 sha2 share shared shared_pool short show shrink shutdown si_averagecolor si_colorhistogram si_featurelist si_positionalcolor si_stillimage si_texture siblings sid sign sin size size_t sizes skip slave sleep smalldatetimefromparts smallfile snapshot some soname sort soundex source space sparse spfile split sql sql_big_result sql_buffer_result sql_cache sql_calc_found_rows sql_small_result sql_variant_property sqlcode sqldata sqlerror sqlname sqlstate sqrt square standalone standby start starting startup statement static statistics stats_binomial_test stats_crosstab stats_ks_test stats_mode stats_mw_test stats_one_way_anova stats_t_test_ stats_t_test_indep stats_t_test_one stats_t_test_paired stats_wsr_test status std stddev stddev_pop stddev_samp stdev stop storage store stored str str_to_date straight_join strcmp strict string struct stuff style subdate subpartition subpartitions substitutable substr substring subtime subtring_index subtype success sum suspend switch switchoffset switchover sync synchronous synonym sys sys_xmlagg sysasm sysaux sysdate sysdatetimeoffset sysdba sysoper system system_user sysutcdatetime table tables tablespace tan tdo template temporary terminated tertiary_weights test than then thread through tier ties time time_format time_zone timediff timefromparts timeout timestamp timestampadd timestampdiff timezone_abbr timezone_minute timezone_region to to_base64 to_date to_days to_seconds todatetimeoffset trace tracking transaction transactional translate translation treat trigger trigger_nestlevel triggers trim truncate try_cast try_convert try_parse type ub1 ub2 ub4 ucase unarchived unbounded uncompress under undo unhex unicode uniform uninstall union unique unix_timestamp unknown unlimited unlock unpivot unrecoverable unsafe unsigned until untrusted unusable unused update updated upgrade upped upper upsert url urowid usable usage use use_stored_outlines user user_data user_resources users using utc_date utc_timestamp uuid uuid_short validate validate_password_strength validation valist value values var var_samp varcharc vari varia variab variabl variable variables variance varp varraw varrawc varray verify version versions view virtual visible void wait wallet warning warnings week weekday weekofyear wellformed when whene whenev wheneve whenever where while whitespace with within without work wrapped xdb xml xmlagg xmlattributes xmlcast xmlcolattval xmlelement xmlexists xmlforest xmlindex xmlnamespaces xmlpi xmlquery xmlroot xmlschema xmlserialize xmltable xmltype xor year year_to_month years yearweek",literal:"true false null",built_in:"array bigint binary bit blob boolean char character date dec decimal float int int8 integer interval number numeric real record serial serial8 smallint text varchar varying void"},c:[{cN:"string",b:"'",e:"'",c:[e.BE,{b:"''"}]},{cN:"string",b:'"',e:'"',c:[e.BE,{b:'""'}]},{cN:"string",b:"`",e:"`",c:[e.BE]},e.CNM,e.CBCM,t]},e.CBCM,t]}});
\ No newline at end of file
diff --git a/src/site/resources/js/jquery.js b/src/site/resources/js/jquery.js
deleted file mode 100644
index 8ccd0ea..0000000
--- a/src/site/resources/js/jquery.js
+++ /dev/null
@@ -1,9266 +0,0 @@
-/*!
- * jQuery JavaScript Library v1.7.1
- * http://jquery.com/
- *
- * Copyright 2011, John Resig
- * Dual licensed under the MIT or GPL Version 2 licenses.
- * http://jquery.org/license
- *
- * Includes Sizzle.js
- * http://sizzlejs.com/
- * Copyright 2011, The Dojo Foundation
- * Released under the MIT, BSD, and GPL Licenses.
- *
- * Date: Mon Nov 21 21:11:03 2011 -0500
- */
-(function( window, undefined ) {
-
-// Use the correct document accordingly with window argument (sandbox)
-var document = window.document,
- navigator = window.navigator,
- location = window.location;
-var jQuery = (function() {
-
-// Define a local copy of jQuery
-var jQuery = function( selector, context ) {
- // The jQuery object is actually just the init constructor 'enhanced'
- return new jQuery.fn.init( selector, context, rootjQuery );
- },
-
- // Map over jQuery in case of overwrite
- _jQuery = window.jQuery,
-
- // Map over the $ in case of overwrite
- _$ = window.$,
-
- // A central reference to the root jQuery(document)
- rootjQuery,
-
- // A simple way to check for HTML strings or ID strings
- // Prioritize #id over <tag> to avoid XSS via location.hash (#9521)
- quickExpr = /^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,
-
- // Check if a string has a non-whitespace character in it
- rnotwhite = /\S/,
-
- // Used for trimming whitespace
- trimLeft = /^\s+/,
- trimRight = /\s+$/,
-
- // Match a standalone tag
- rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>)?$/,
-
- // JSON RegExp
- rvalidchars = /^[\],:{}\s]*$/,
- rvalidescape = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,
- rvalidtokens = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,
- rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g,
-
- // Useragent RegExp
- rwebkit = /(webkit)[ \/]([\w.]+)/,
- ropera = /(opera)(?:.*version)?[ \/]([\w.]+)/,
- rmsie = /(msie) ([\w.]+)/,
- rmozilla = /(mozilla)(?:.*? rv:([\w.]+))?/,
-
- // Matches dashed string for camelizing
- rdashAlpha = /-([a-z]|[0-9])/ig,
- rmsPrefix = /^-ms-/,
-
- // Used by jQuery.camelCase as callback to replace()
- fcamelCase = function( all, letter ) {
- return ( letter + "" ).toUpperCase();
- },
-
- // Keep a UserAgent string for use with jQuery.browser
- userAgent = navigator.userAgent,
-
- // For matching the engine and version of the browser
- browserMatch,
-
- // The deferred used on DOM ready
- readyList,
-
- // The ready event handler
- DOMContentLoaded,
-
- // Save a reference to some core methods
- toString = Object.prototype.toString,
- hasOwn = Object.prototype.hasOwnProperty,
- push = Array.prototype.push,
- slice = Array.prototype.slice,
- trim = String.prototype.trim,
- indexOf = Array.prototype.indexOf,
-
- // [[Class]] -> type pairs
- class2type = {};
-
-jQuery.fn = jQuery.prototype = {
- constructor: jQuery,
- init: function( selector, context, rootjQuery ) {
- var match, elem, ret, doc;
-
- // Handle $(""), $(null), or $(undefined)
- if ( !selector ) {
- return this;
- }
-
- // Handle $(DOMElement)
- if ( selector.nodeType ) {
- this.context = this[0] = selector;
- this.length = 1;
- return this;
- }
-
- // The body element only exists once, optimize finding it
- if ( selector === "body" && !context && document.body ) {
- this.context = document;
- this[0] = document.body;
- this.selector = selector;
- this.length = 1;
- return this;
- }
-
- // Handle HTML strings
- if ( typeof selector === "string" ) {
- // Are we dealing with HTML string or an ID?
- if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) {
- // Assume that strings that start and end with <> are HTML and skip the regex check
- match = [ null, selector, null ];
-
- } else {
- match = quickExpr.exec( selector );
- }
-
- // Verify a match, and that no context was specified for #id
- if ( match && (match[1] || !context) ) {
-
- // HANDLE: $(html) -> $(array)
- if ( match[1] ) {
- context = context instanceof jQuery ? context[0] : context;
- doc = ( context ? context.ownerDocument || context : document );
-
- // If a single string is passed in and it's a single tag
- // just do a createElement and skip the rest
- ret = rsingleTag.exec( selector );
-
- if ( ret ) {
- if ( jQuery.isPlainObject( context ) ) {
- selector = [ document.createElement( ret[1] ) ];
- jQuery.fn.attr.call( selector, context, true );
-
- } else {
- selector = [ doc.createElement( ret[1] ) ];
- }
-
- } else {
- ret = jQuery.buildFragment( [ match[1] ], [ doc ] );
- selector = ( ret.cacheable ? jQuery.clone(ret.fragment) : ret.fragment ).childNodes;
- }
-
- return jQuery.merge( this, selector );
-
- // HANDLE: $("#id")
- } else {
- elem = document.getElementById( match[2] );
-
- // Check parentNode to catch when Blackberry 4.6 returns
- // nodes that are no longer in the document #6963
- if ( elem && elem.parentNode ) {
- // Handle the case where IE and Opera return items
- // by name instead of ID
- if ( elem.id !== match[2] ) {
- return rootjQuery.find( selector );
- }
-
- // Otherwise, we inject the element directly into the jQuery object
- this.length = 1;
- this[0] = elem;
- }
-
- this.context = document;
- this.selector = selector;
- return this;
- }
-
- // HANDLE: $(expr, $(...))
- } else if ( !context || context.jquery ) {
- return ( context || rootjQuery ).find( selector );
-
- // HANDLE: $(expr, context)
- // (which is just equivalent to: $(context).find(expr)
- } else {
- return this.constructor( context ).find( selector );
- }
-
- // HANDLE: $(function)
- // Shortcut for document ready
- } else if ( jQuery.isFunction( selector ) ) {
- return rootjQuery.ready( selector );
- }
-
- if ( selector.selector !== undefined ) {
- this.selector = selector.selector;
- this.context = selector.context;
- }
-
- return jQuery.makeArray( selector, this );
- },
-
- // Start with an empty selector
- selector: "",
-
- // The current version of jQuery being used
- jquery: "1.7.1",
-
- // The default length of a jQuery object is 0
- length: 0,
-
- // The number of elements contained in the matched element set
- size: function() {
- return this.length;
- },
-
- toArray: function() {
- return slice.call( this, 0 );
- },
-
- // Get the Nth element in the matched element set OR
- // Get the whole matched element set as a clean array
- get: function( num ) {
- return num == null ?
-
- // Return a 'clean' array
- this.toArray() :
-
- // Return just the object
- ( num < 0 ? this[ this.length + num ] : this[ num ] );
- },
-
- // Take an array of elements and push it onto the stack
- // (returning the new matched element set)
- pushStack: function( elems, name, selector ) {
- // Build a new jQuery matched element set
- var ret = this.constructor();
-
- if ( jQuery.isArray( elems ) ) {
- push.apply( ret, elems );
-
- } else {
- jQuery.merge( ret, elems );
- }
-
- // Add the old object onto the stack (as a reference)
- ret.prevObject = this;
-
- ret.context = this.context;
-
- if ( name === "find" ) {
- ret.selector = this.selector + ( this.selector ? " " : "" ) + selector;
- } else if ( name ) {
- ret.selector = this.selector + "." + name + "(" + selector + ")";
- }
-
- // Return the newly-formed element set
- return ret;
- },
-
- // Execute a callback for every element in the matched set.
- // (You can seed the arguments with an array of args, but this is
- // only used internally.)
- each: function( callback, args ) {
- return jQuery.each( this, callback, args );
- },
-
- ready: function( fn ) {
- // Attach the listeners
- jQuery.bindReady();
-
- // Add the callback
- readyList.add( fn );
-
- return this;
- },
-
- eq: function( i ) {
- i = +i;
- return i === -1 ?
- this.slice( i ) :
- this.slice( i, i + 1 );
- },
-
- first: function() {
- return this.eq( 0 );
- },
-
- last: function() {
- return this.eq( -1 );
- },
-
- slice: function() {
- return this.pushStack( slice.apply( this, arguments ),
- "slice", slice.call(arguments).join(",") );
- },
-
- map: function( callback ) {
- return this.pushStack( jQuery.map(this, function( elem, i ) {
- return callback.call( elem, i, elem );
- }));
- },
-
- end: function() {
- return this.prevObject || this.constructor(null);
- },
-
- // For internal use only.
- // Behaves like an Array's method, not like a jQuery method.
- push: push,
- sort: [].sort,
- splice: [].splice
-};
-
-// Give the init function the jQuery prototype for later instantiation
-jQuery.fn.init.prototype = jQuery.fn;
-
-jQuery.extend = jQuery.fn.extend = function() {
- var options, name, src, copy, copyIsArray, clone,
- target = arguments[0] || {},
- i = 1,
- length = arguments.length,
- deep = false;
-
- // Handle a deep copy situation
- if ( typeof target === "boolean" ) {
- deep = target;
- target = arguments[1] || {};
- // skip the boolean and the target
- i = 2;
- }
-
- // Handle case when target is a string or something (possible in deep copy)
- if ( typeof target !== "object" && !jQuery.isFunction(target) ) {
- target = {};
- }
-
- // extend jQuery itself if only one argument is passed
- if ( length === i ) {
- target = this;
- --i;
- }
-
- for ( ; i < length; i++ ) {
- // Only deal with non-null/undefined values
- if ( (options = arguments[ i ]) != null ) {
- // Extend the base object
- for ( name in options ) {
- src = target[ name ];
- copy = options[ name ];
-
- // Prevent never-ending loop
- if ( target === copy ) {
- continue;
- }
-
- // Recurse if we're merging plain objects or arrays
- if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {
- if ( copyIsArray ) {
- copyIsArray = false;
- clone = src && jQuery.isArray(src) ? src : [];
-
- } else {
- clone = src && jQuery.isPlainObject(src) ? src : {};
- }
-
- // Never move original objects, clone them
- target[ name ] = jQuery.extend( deep, clone, copy );
-
- // Don't bring in undefined values
- } else if ( copy !== undefined ) {
- target[ name ] = copy;
- }
- }
- }
- }
-
- // Return the modified object
- return target;
-};
-
-jQuery.extend({
- noConflict: function( deep ) {
- if ( window.$ === jQuery ) {
- window.$ = _$;
- }
-
- if ( deep && window.jQuery === jQuery ) {
- window.jQuery = _jQuery;
- }
-
- return jQuery;
- },
-
- // Is the DOM ready to be used? Set to true once it occurs.
- isReady: false,
-
- // A counter to track how many items to wait for before
- // the ready event fires. See #6781
- readyWait: 1,
-
- // Hold (or release) the ready event
- holdReady: function( hold ) {
- if ( hold ) {
- jQuery.readyWait++;
- } else {
- jQuery.ready( true );
- }
- },
-
- // Handle when the DOM is ready
- ready: function( wait ) {
- // Either a released hold or an DOMready/load event and not yet ready
- if ( (wait === true && !--jQuery.readyWait) || (wait !== true && !jQuery.isReady) ) {
- // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).
- if ( !document.body ) {
- return setTimeout( jQuery.ready, 1 );
- }
-
- // Remember that the DOM is ready
- jQuery.isReady = true;
-
- // If a normal DOM Ready event fired, decrement, and wait if need be
- if ( wait !== true && --jQuery.readyWait > 0 ) {
- return;
- }
-
- // If there are functions bound, to execute
- readyList.fireWith( document, [ jQuery ] );
-
- // Trigger any bound ready events
- if ( jQuery.fn.trigger ) {
- jQuery( document ).trigger( "ready" ).off( "ready" );
- }
- }
- },
-
- bindReady: function() {
- if ( readyList ) {
- return;
- }
-
- readyList = jQuery.Callbacks( "once memory" );
-
- // Catch cases where $(document).ready() is called after the
- // browser event has already occurred.
- if ( document.readyState === "complete" ) {
- // Handle it asynchronously to allow scripts the opportunity to delay ready
- return setTimeout( jQuery.ready, 1 );
- }
-
- // Mozilla, Opera and webkit nightlies currently support this event
- if ( document.addEventListener ) {
- // Use the handy event callback
- document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false );
-
- // A fallback to window.onload, that will always work
- window.addEventListener( "load", jQuery.ready, false );
-
- // If IE event model is used
- } else if ( document.attachEvent ) {
- // ensure firing before onload,
- // maybe late but safe also for iframes
- document.attachEvent( "onreadystatechange", DOMContentLoaded );
-
- // A fallback to window.onload, that will always work
- window.attachEvent( "onload", jQuery.ready );
-
- // If IE and not a frame
- // continually check to see if the document is ready
- var toplevel = false;
-
- try {
- toplevel = window.frameElement == null;
- } catch(e) {}
-
- if ( document.documentElement.doScroll && toplevel ) {
- doScrollCheck();
- }
- }
- },
-
- // See test/unit/core.js for details concerning isFunction.
- // Since version 1.3, DOM methods and functions like alert
- // aren't supported. They return false on IE (#2968).
- isFunction: function( obj ) {
- return jQuery.type(obj) === "function";
- },
-
- isArray: Array.isArray || function( obj ) {
- return jQuery.type(obj) === "array";
- },
-
- // A crude way of determining if an object is a window
- isWindow: function( obj ) {
- return obj && typeof obj === "object" && "setInterval" in obj;
- },
-
- isNumeric: function( obj ) {
- return !isNaN( parseFloat(obj) ) && isFinite( obj );
- },
-
- type: function( obj ) {
- return obj == null ?
- String( obj ) :
- class2type[ toString.call(obj) ] || "object";
- },
-
- isPlainObject: function( obj ) {
- // Must be an Object.
- // Because of IE, we also have to check the presence of the constructor property.
- // Make sure that DOM nodes and window objects don't pass through, as well
- if ( !obj || jQuery.type(obj) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) {
- return false;
- }
-
- try {
- // Not own constructor property must be Object
- if ( obj.constructor &&
- !hasOwn.call(obj, "constructor") &&
- !hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) {
- return false;
- }
- } catch ( e ) {
- // IE8,9 Will throw exceptions on certain host objects #9897
- return false;
- }
-
- // Own properties are enumerated firstly, so to speed up,
- // if last one is own, then all properties are own.
-
- var key;
- for ( key in obj ) {}
-
- return key === undefined || hasOwn.call( obj, key );
- },
-
- isEmptyObject: function( obj ) {
- for ( var name in obj ) {
- return false;
- }
- return true;
- },
-
- error: function( msg ) {
- throw new Error( msg );
- },
-
- parseJSON: function( data ) {
- if ( typeof data !== "string" || !data ) {
- return null;
- }
-
- // Make sure leading/trailing whitespace is removed (IE can't handle it)
- data = jQuery.trim( data );
-
- // Attempt to parse using the native JSON parser first
- if ( window.JSON && window.JSON.parse ) {
- return window.JSON.parse( data );
- }
-
- // Make sure the incoming data is actual JSON
- // Logic borrowed from http://json.org/json2.js
- if ( rvalidchars.test( data.replace( rvalidescape, "@" )
- .replace( rvalidtokens, "]" )
- .replace( rvalidbraces, "")) ) {
-
- return ( new Function( "return " + data ) )();
-
- }
- jQuery.error( "Invalid JSON: " + data );
- },
-
- // Cross-browser xml parsing
- parseXML: function( data ) {
- var xml, tmp;
- try {
- if ( window.DOMParser ) { // Standard
- tmp = new DOMParser();
- xml = tmp.parseFromString( data , "text/xml" );
- } else { // IE
- xml = new ActiveXObject( "Microsoft.XMLDOM" );
- xml.async = "false";
- xml.loadXML( data );
- }
- } catch( e ) {
- xml = undefined;
- }
- if ( !xml || !xml.documentElement || xml.getElementsByTagName( "parsererror" ).length ) {
- jQuery.error( "Invalid XML: " + data );
- }
- return xml;
- },
-
- noop: function() {},
-
- // Evaluates a script in a global context
- // Workarounds based on findings by Jim Driscoll
- // http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context
- globalEval: function( data ) {
- if ( data && rnotwhite.test( data ) ) {
- // We use execScript on Internet Explorer
- // We use an anonymous function so that context is window
- // rather than jQuery in Firefox
- ( window.execScript || function( data ) {
- window[ "eval" ].call( window, data );
- } )( data );
- }
- },
-
- // Convert dashed to camelCase; used by the css and data modules
- // Microsoft forgot to hump their vendor prefix (#9572)
- camelCase: function( string ) {
- return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase );
- },
-
- nodeName: function( elem, name ) {
- return elem.nodeName && elem.nodeName.toUpperCase() === name.toUpperCase();
- },
-
- // args is for internal usage only
- each: function( object, callback, args ) {
- var name, i = 0,
- length = object.length,
- isObj = length === undefined || jQuery.isFunction( object );
-
- if ( args ) {
- if ( isObj ) {
- for ( name in object ) {
- if ( callback.apply( object[ name ], args ) === false ) {
- break;
- }
- }
- } else {
- for ( ; i < length; ) {
- if ( callback.apply( object[ i++ ], args ) === false ) {
- break;
- }
- }
- }
-
- // A special, fast, case for the most common use of each
- } else {
- if ( isObj ) {
- for ( name in object ) {
- if ( callback.call( object[ name ], name, object[ name ] ) === false ) {
- break;
- }
- }
- } else {
- for ( ; i < length; ) {
- if ( callback.call( object[ i ], i, object[ i++ ] ) === false ) {
- break;
- }
- }
- }
- }
-
- return object;
- },
-
- // Use native String.trim function wherever possible
- trim: trim ?
- function( text ) {
- return text == null ?
- "" :
- trim.call( text );
- } :
-
- // Otherwise use our own trimming functionality
- function( text ) {
- return text == null ?
- "" :
- text.toString().replace( trimLeft, "" ).replace( trimRight, "" );
- },
-
- // results is for internal usage only
- makeArray: function( array, results ) {
- var ret = results || [];
-
- if ( array != null ) {
- // The window, strings (and functions) also have 'length'
- // Tweaked logic slightly to handle Blackberry 4.7 RegExp issues #6930
- var type = jQuery.type( array );
-
- if ( array.length == null || type === "string" || type === "function" || type === "regexp" || jQuery.isWindow( array ) ) {
- push.call( ret, array );
- } else {
- jQuery.merge( ret, array );
- }
- }
-
- return ret;
- },
-
- inArray: function( elem, array, i ) {
- var len;
-
- if ( array ) {
- if ( indexOf ) {
- return indexOf.call( array, elem, i );
- }
-
- len = array.length;
- i = i ? i < 0 ? Math.max( 0, len + i ) : i : 0;
-
- for ( ; i < len; i++ ) {
- // Skip accessing in sparse arrays
- if ( i in array && array[ i ] === elem ) {
- return i;
- }
- }
- }
-
- return -1;
- },
-
- merge: function( first, second ) {
- var i = first.length,
- j = 0;
-
- if ( typeof second.length === "number" ) {
- for ( var l = second.length; j < l; j++ ) {
- first[ i++ ] = second[ j ];
- }
-
- } else {
- while ( second[j] !== undefined ) {
- first[ i++ ] = second[ j++ ];
- }
- }
-
- first.length = i;
-
- return first;
- },
-
- grep: function( elems, callback, inv ) {
- var ret = [], retVal;
- inv = !!inv;
-
- // Go through the array, only saving the items
- // that pass the validator function
- for ( var i = 0, length = elems.length; i < length; i++ ) {
- retVal = !!callback( elems[ i ], i );
- if ( inv !== retVal ) {
- ret.push( elems[ i ] );
- }
- }
-
- return ret;
- },
-
- // arg is for internal usage only
- map: function( elems, callback, arg ) {
- var value, key, ret = [],
- i = 0,
- length = elems.length,
- // jquery objects are treated as arrays
- isArray = elems instanceof jQuery || length !== undefined && typeof length === "number" && ( ( length > 0 && elems[ 0 ] && elems[ length -1 ] ) || length === 0 || jQuery.isArray( elems ) ) ;
-
- // Go through the array, translating each of the items to their
- if ( isArray ) {
- for ( ; i < length; i++ ) {
- value = callback( elems[ i ], i, arg );
-
- if ( value != null ) {
- ret[ ret.length ] = value;
- }
- }
-
- // Go through every key on the object,
- } else {
- for ( key in elems ) {
- value = callback( elems[ key ], key, arg );
-
- if ( value != null ) {
- ret[ ret.length ] = value;
- }
- }
- }
-
- // Flatten any nested arrays
- return ret.concat.apply( [], ret );
- },
-
- // A global GUID counter for objects
- guid: 1,
-
- // Bind a function to a context, optionally partially applying any
- // arguments.
- proxy: function( fn, context ) {
- if ( typeof context === "string" ) {
- var tmp = fn[ context ];
- context = fn;
- fn = tmp;
- }
-
- // Quick check to determine if target is callable, in the spec
- // this throws a TypeError, but we will just return undefined.
- if ( !jQuery.isFunction( fn ) ) {
- return undefined;
- }
-
- // Simulated bind
- var args = slice.call( arguments, 2 ),
- proxy = function() {
- return fn.apply( context, args.concat( slice.call( arguments ) ) );
- };
-
- // Set the guid of unique handler to the same of original handler, so it can be removed
- proxy.guid = fn.guid = fn.guid || proxy.guid || jQuery.guid++;
-
- return proxy;
- },
-
- // Mutifunctional method to get and set values to a collection
- // The value/s can optionally be executed if it's a function
- access: function( elems, key, value, exec, fn, pass ) {
- var length = elems.length;
-
- // Setting many attributes
- if ( typeof key === "object" ) {
- for ( var k in key ) {
- jQuery.access( elems, k, key[k], exec, fn, value );
- }
- return elems;
- }
-
- // Setting one attribute
- if ( value !== undefined ) {
- // Optionally, function values get executed if exec is true
- exec = !pass && exec && jQuery.isFunction(value);
-
- for ( var i = 0; i < length; i++ ) {
- fn( elems[i], key, exec ? value.call( elems[i], i, fn( elems[i], key ) ) : value, pass );
- }
-
- return elems;
- }
-
- // Getting an attribute
- return length ? fn( elems[0], key ) : undefined;
- },
-
- now: function() {
- return ( new Date() ).getTime();
- },
-
- // Use of jQuery.browser is frowned upon.
- // More details: http://docs.jquery.com/Utilities/jQuery.browser
- uaMatch: function( ua ) {
- ua = ua.toLowerCase();
-
- var match = rwebkit.exec( ua ) ||
- ropera.exec( ua ) ||
- rmsie.exec( ua ) ||
- ua.indexOf("compatible") < 0 && rmozilla.exec( ua ) ||
- [];
-
- return { browser: match[1] || "", version: match[2] || "0" };
- },
-
- sub: function() {
- function jQuerySub( selector, context ) {
- return new jQuerySub.fn.init( selector, context );
- }
- jQuery.extend( true, jQuerySub, this );
- jQuerySub.superclass = this;
- jQuerySub.fn = jQuerySub.prototype = this();
- jQuerySub.fn.constructor = jQuerySub;
- jQuerySub.sub = this.sub;
- jQuerySub.fn.init = function init( selector, context ) {
- if ( context && context instanceof jQuery && !(context instanceof jQuerySub) ) {
- context = jQuerySub( context );
- }
-
- return jQuery.fn.init.call( this, selector, context, rootjQuerySub );
- };
- jQuerySub.fn.init.prototype = jQuerySub.fn;
- var rootjQuerySub = jQuerySub(document);
- return jQuerySub;
- },
-
- browser: {}
-});
-
-// Populate the class2type map
-jQuery.each("Boolean Number String Function Array Date RegExp Object".split(" "), function(i, name) {
- class2type[ "[object " + name + "]" ] = name.toLowerCase();
-});
-
-browserMatch = jQuery.uaMatch( userAgent );
-if ( browserMatch.browser ) {
- jQuery.browser[ browserMatch.browser ] = true;
- jQuery.browser.version = browserMatch.version;
-}
-
-// Deprecated, use jQuery.browser.webkit instead
-if ( jQuery.browser.webkit ) {
- jQuery.browser.safari = true;
-}
-
-// IE doesn't match non-breaking spaces with \s
-if ( rnotwhite.test( "\xA0" ) ) {
- trimLeft = /^[\s\xA0]+/;
- trimRight = /[\s\xA0]+$/;
-}
-
-// All jQuery objects should point back to these
-rootjQuery = jQuery(document);
-
-// Cleanup functions for the document ready method
-if ( document.addEventListener ) {
- DOMContentLoaded = function() {
- document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false );
- jQuery.ready();
- };
-
-} else if ( document.attachEvent ) {
- DOMContentLoaded = function() {
- // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).
- if ( document.readyState === "complete" ) {
- document.detachEvent( "onreadystatechange", DOMContentLoaded );
- jQuery.ready();
- }
- };
-}
-
-// The DOM ready check for Internet Explorer
-function doScrollCheck() {
- if ( jQuery.isReady ) {
- return;
- }
-
- try {
- // If IE is used, use the trick by Diego Perini
- // http://javascript.nwbox.com/IEContentLoaded/
- document.documentElement.doScroll("left");
- } catch(e) {
- setTimeout( doScrollCheck, 1 );
- return;
- }
-
- // and execute any waiting functions
- jQuery.ready();
-}
-
-return jQuery;
-
-})();
-
-
-// String to Object flags format cache
-var flagsCache = {};
-
-// Convert String-formatted flags into Object-formatted ones and store in cache
-function createFlags( flags ) {
- var object = flagsCache[ flags ] = {},
- i, length;
- flags = flags.split( /\s+/ );
- for ( i = 0, length = flags.length; i < length; i++ ) {
- object[ flags[i] ] = true;
- }
- return object;
-}
-
-/*
- * Create a callback list using the following parameters:
- *
- * flags: an optional list of space-separated flags that will change how
- * the callback list behaves
- *
- * By default a callback list will act like an event callback list and can be
- * "fired" multiple times.
- *
- * Possible flags:
- *
- * once: will ensure the callback list can only be fired once (like a Deferred)
- *
- * memory: will keep track of previous values and will call any callback added
- * after the list has been fired right away with the latest "memorized"
- * values (like a Deferred)
- *
- * unique: will ensure a callback can only be added once (no duplicate in the list)
- *
- * stopOnFalse: interrupt callings when a callback returns false
- *
- */
-jQuery.Callbacks = function( flags ) {
-
- // Convert flags from String-formatted to Object-formatted
- // (we check in cache first)
- flags = flags ? ( flagsCache[ flags ] || createFlags( flags ) ) : {};
-
- var // Actual callback list
- list = [],
- // Stack of fire calls for repeatable lists
- stack = [],
- // Last fire value (for non-forgettable lists)
- memory,
- // Flag to know if list is currently firing
- firing,
- // First callback to fire (used internally by add and fireWith)
- firingStart,
- // End of the loop when firing
- firingLength,
- // Index of currently firing callback (modified by remove if needed)
- firingIndex,
- // Add one or several callbacks to the list
- add = function( args ) {
- var i,
- length,
- elem,
- type,
- actual;
- for ( i = 0, length = args.length; i < length; i++ ) {
- elem = args[ i ];
- type = jQuery.type( elem );
- if ( type === "array" ) {
- // Inspect recursively
- add( elem );
- } else if ( type === "function" ) {
- // Add if not in unique mode and callback is not in
- if ( !flags.unique || !self.has( elem ) ) {
- list.push( elem );
- }
- }
- }
- },
- // Fire callbacks
- fire = function( context, args ) {
- args = args || [];
- memory = !flags.memory || [ context, args ];
- firing = true;
- firingIndex = firingStart || 0;
- firingStart = 0;
- firingLength = list.length;
- for ( ; list && firingIndex < firingLength; firingIndex++ ) {
- if ( list[ firingIndex ].apply( context, args ) === false && flags.stopOnFalse ) {
- memory = true; // Mark as halted
- break;
- }
- }
- firing = false;
- if ( list ) {
- if ( !flags.once ) {
- if ( stack && stack.length ) {
- memory = stack.shift();
- self.fireWith( memory[ 0 ], memory[ 1 ] );
- }
- } else if ( memory === true ) {
- self.disable();
- } else {
- list = [];
- }
- }
- },
- // Actual Callbacks object
- self = {
- // Add a callback or a collection of callbacks to the list
- add: function() {
- if ( list ) {
- var length = list.length;
- add( arguments );
- // Do we need to add the callbacks to the
- // current firing batch?
- if ( firing ) {
- firingLength = list.length;
- // With memory, if we're not firing then
- // we should call right away, unless previous
- // firing was halted (stopOnFalse)
- } else if ( memory && memory !== true ) {
- firingStart = length;
- fire( memory[ 0 ], memory[ 1 ] );
- }
- }
- return this;
- },
- // Remove a callback from the list
- remove: function() {
- if ( list ) {
- var args = arguments,
- argIndex = 0,
- argLength = args.length;
- for ( ; argIndex < argLength ; argIndex++ ) {
- for ( var i = 0; i < list.length; i++ ) {
- if ( args[ argIndex ] === list[ i ] ) {
- // Handle firingIndex and firingLength
- if ( firing ) {
- if ( i <= firingLength ) {
- firingLength--;
- if ( i <= firingIndex ) {
- firingIndex--;
- }
- }
- }
- // Remove the element
- list.splice( i--, 1 );
- // If we have some unicity property then
- // we only need to do this once
- if ( flags.unique ) {
- break;
- }
- }
- }
- }
- }
- return this;
- },
- // Control if a given callback is in the list
- has: function( fn ) {
- if ( list ) {
- var i = 0,
- length = list.length;
- for ( ; i < length; i++ ) {
- if ( fn === list[ i ] ) {
- return true;
- }
- }
- }
- return false;
- },
- // Remove all callbacks from the list
- empty: function() {
- list = [];
- return this;
- },
- // Have the list do nothing anymore
- disable: function() {
- list = stack = memory = undefined;
- return this;
- },
- // Is it disabled?
- disabled: function() {
- return !list;
- },
- // Lock the list in its current state
- lock: function() {
- stack = undefined;
- if ( !memory || memory === true ) {
- self.disable();
- }
- return this;
- },
- // Is it locked?
- locked: function() {
- return !stack;
- },
- // Call all callbacks with the given context and arguments
- fireWith: function( context, args ) {
- if ( stack ) {
- if ( firing ) {
- if ( !flags.once ) {
- stack.push( [ context, args ] );
- }
- } else if ( !( flags.once && memory ) ) {
- fire( context, args );
- }
- }
- return this;
- },
- // Call all the callbacks with the given arguments
- fire: function() {
- self.fireWith( this, arguments );
- return this;
- },
- // To know if the callbacks have already been called at least once
- fired: function() {
- return !!memory;
- }
- };
-
- return self;
-};
-
-
-
-
-var // Static reference to slice
- sliceDeferred = [].slice;
-
-jQuery.extend({
-
- Deferred: function( func ) {
- var doneList = jQuery.Callbacks( "once memory" ),
- failList = jQuery.Callbacks( "once memory" ),
- progressList = jQuery.Callbacks( "memory" ),
- state = "pending",
- lists = {
- resolve: doneList,
- reject: failList,
- notify: progressList
- },
- promise = {
- done: doneList.add,
- fail: failList.add,
- progress: progressList.add,
-
- state: function() {
- return state;
- },
-
- // Deprecated
- isResolved: doneList.fired,
- isRejected: failList.fired,
-
- then: function( doneCallbacks, failCallbacks, progressCallbacks ) {
- deferred.done( doneCallbacks ).fail( failCallbacks ).progress( progressCallbacks );
- return this;
- },
- always: function() {
- deferred.done.apply( deferred, arguments ).fail.apply( deferred, arguments );
- return this;
- },
- pipe: function( fnDone, fnFail, fnProgress ) {
- return jQuery.Deferred(function( newDefer ) {
- jQuery.each( {
- done: [ fnDone, "resolve" ],
- fail: [ fnFail, "reject" ],
- progress: [ fnProgress, "notify" ]
- }, function( handler, data ) {
- var fn = data[ 0 ],
- action = data[ 1 ],
- returned;
- if ( jQuery.isFunction( fn ) ) {
- deferred[ handler ](function() {
- returned = fn.apply( this, arguments );
- if ( returned && jQuery.isFunction( returned.promise ) ) {
- returned.promise().then( newDefer.resolve, newDefer.reject, newDefer.notify );
- } else {
- newDefer[ action + "With" ]( this === deferred ? newDefer : this, [ returned ] );
- }
- });
- } else {
- deferred[ handler ]( newDefer[ action ] );
- }
- });
- }).promise();
- },
- // Get a promise for this deferred
- // If obj is provided, the promise aspect is added to the object
- promise: function( obj ) {
- if ( obj == null ) {
- obj = promise;
- } else {
- for ( var key in promise ) {
- obj[ key ] = promise[ key ];
- }
- }
- return obj;
- }
- },
- deferred = promise.promise({}),
- key;
-
- for ( key in lists ) {
- deferred[ key ] = lists[ key ].fire;
- deferred[ key + "With" ] = lists[ key ].fireWith;
- }
-
- // Handle state
- deferred.done( function() {
- state = "resolved";
- }, failList.disable, progressList.lock ).fail( function() {
- state = "rejected";
- }, doneList.disable, progressList.lock );
-
- // Call given func if any
- if ( func ) {
- func.call( deferred, deferred );
- }
-
- // All done!
- return deferred;
- },
-
- // Deferred helper
- when: function( firstParam ) {
- var args = sliceDeferred.call( arguments, 0 ),
- i = 0,
- length = args.length,
- pValues = new Array( length ),
- count = length,
- pCount = length,
- deferred = length <= 1 && firstParam && jQuery.isFunction( firstParam.promise ) ?
- firstParam :
- jQuery.Deferred(),
- promise = deferred.promise();
- function resolveFunc( i ) {
- return function( value ) {
- args[ i ] = arguments.length > 1 ? sliceDeferred.call( arguments, 0 ) : value;
- if ( !( --count ) ) {
- deferred.resolveWith( deferred, args );
- }
- };
- }
- function progressFunc( i ) {
- return function( value ) {
- pValues[ i ] = arguments.length > 1 ? sliceDeferred.call( arguments, 0 ) : value;
- deferred.notifyWith( promise, pValues );
- };
- }
- if ( length > 1 ) {
- for ( ; i < length; i++ ) {
- if ( args[ i ] && args[ i ].promise && jQuery.isFunction( args[ i ].promise ) ) {
- args[ i ].promise().then( resolveFunc(i), deferred.reject, progressFunc(i) );
- } else {
- --count;
- }
- }
- if ( !count ) {
- deferred.resolveWith( deferred, args );
- }
- } else if ( deferred !== firstParam ) {
- deferred.resolveWith( deferred, length ? [ firstParam ] : [] );
- }
- return promise;
- }
-});
-
-
-
-
-jQuery.support = (function() {
-
- var support,
- all,
- a,
- select,
- opt,
- input,
- marginDiv,
- fragment,
- tds,
- events,
- eventName,
- i,
- isSupported,
- div = document.createElement( "div" ),
- documentElement = document.documentElement;
-
- // Preliminary tests
- div.setAttribute("className", "t");
- div.innerHTML = " <link/><table></table><a href='/a' style='top:1px;float:left;opacity:.55;'>a</a><input type='checkbox'/>";
-
- all = div.getElementsByTagName( "*" );
- a = div.getElementsByTagName( "a" )[ 0 ];
-
- // Can't get basic test support
- if ( !all || !all.length || !a ) {
- return {};
- }
-
- // First batch of supports tests
- select = document.createElement( "select" );
- opt = select.appendChild( document.createElement("option") );
- input = div.getElementsByTagName( "input" )[ 0 ];
-
- support = {
- // IE strips leading whitespace when .innerHTML is used
- leadingWhitespace: ( div.firstChild.nodeType === 3 ),
-
- // Make sure that tbody elements aren't automatically inserted
- // IE will insert them into empty tables
- tbody: !div.getElementsByTagName("tbody").length,
-
- // Make sure that link elements get serialized correctly by innerHTML
- // This requires a wrapper element in IE
- htmlSerialize: !!div.getElementsByTagName("link").length,
-
- // Get the style information from getAttribute
- // (IE uses .cssText instead)
- style: /top/.test( a.getAttribute("style") ),
-
- // Make sure that URLs aren't manipulated
- // (IE normalizes it by default)
- hrefNormalized: ( a.getAttribute("href") === "/a" ),
-
- // Make sure that element opacity exists
- // (IE uses filter instead)
- // Use a regex to work around a WebKit issue. See #5145
- opacity: /^0.55/.test( a.style.opacity ),
-
- // Verify style float existence
- // (IE uses styleFloat instead of cssFloat)
- cssFloat: !!a.style.cssFloat,
-
- // Make sure that if no value is specified for a checkbox
- // that it defaults to "on".
- // (WebKit defaults to "" instead)
- checkOn: ( input.value === "on" ),
-
- // Make sure that a selected-by-default option has a working selected property.
- // (WebKit defaults to false instead of true, IE too, if it's in an optgroup)
- optSelected: opt.selected,
-
- // Test setAttribute on camelCase class. If it works, we need attrFixes when doing get/setAttribute (ie6/7)
- getSetAttribute: div.className !== "t",
-
- // Tests for enctype support on a form(#6743)
- enctype: !!document.createElement("form").enctype,
-
- // Makes sure cloning an html5 element does not cause problems
- // Where outerHTML is undefined, this still works
- html5Clone: document.createElement("nav").cloneNode( true ).outerHTML !== "<:nav></:nav>",
-
- // Will be defined later
- submitBubbles: true,
- changeBubbles: true,
- focusinBubbles: false,
- deleteExpando: true,
- noCloneEvent: true,
- inlineBlockNeedsLayout: false,
- shrinkWrapBlocks: false,
- reliableMarginRight: true
- };
-
- // Make sure checked status is properly cloned
- input.checked = true;
- support.noCloneChecked = input.cloneNode( true ).checked;
-
- // Make sure that the options inside disabled selects aren't marked as disabled
- // (WebKit marks them as disabled)
- select.disabled = true;
- support.optDisabled = !opt.disabled;
-
- // Test to see if it's possible to delete an expando from an element
- // Fails in Internet Explorer
- try {
- delete div.test;
- } catch( e ) {
- support.deleteExpando = false;
- }
-
- if ( !div.addEventListener && div.attachEvent && div.fireEvent ) {
- div.attachEvent( "onclick", function() {
- // Cloning a node shouldn't copy over any
- // bound event handlers (IE does this)
- support.noCloneEvent = false;
- });
- div.cloneNode( true ).fireEvent( "onclick" );
- }
-
- // Check if a radio maintains its value
- // after being appended to the DOM
- input = document.createElement("input");
- input.value = "t";
- input.setAttribute("type", "radio");
- support.radioValue = input.value === "t";
-
- input.setAttribute("checked", "checked");
- div.appendChild( input );
- fragment = document.createDocumentFragment();
- fragment.appendChild( div.lastChild );
-
- // WebKit doesn't clone checked state correctly in fragments
- support.checkClone = fragment.cloneNode( true ).cloneNode( true ).lastChild.checked;
-
- // Check if a disconnected checkbox will retain its checked
- // value of true after appended to the DOM (IE6/7)
- support.appendChecked = input.checked;
-
- fragment.removeChild( input );
- fragment.appendChild( div );
-
- div.innerHTML = "";
-
- // Check if div with explicit width and no margin-right incorrectly
- // gets computed margin-right based on width of container. For more
- // info see bug #3333
- // Fails in WebKit before Feb 2011 nightlies
- // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right
- if ( window.getComputedStyle ) {
- marginDiv = document.createElement( "div" );
- marginDiv.style.width = "0";
- marginDiv.style.marginRight = "0";
- div.style.width = "2px";
- div.appendChild( marginDiv );
- support.reliableMarginRight =
- ( parseInt( ( window.getComputedStyle( marginDiv, null ) || { marginRight: 0 } ).marginRight, 10 ) || 0 ) === 0;
- }
-
- // Technique from Juriy Zaytsev
- // http://perfectionkills.com/detecting-event-support-without-browser-sniffing/
- // We only care about the case where non-standard event systems
- // are used, namely in IE. Short-circuiting here helps us to
- // avoid an eval call (in setAttribute) which can cause CSP
- // to go haywire. See: https://developer.mozilla.org/en/Security/CSP
- if ( div.attachEvent ) {
- for( i in {
- submit: 1,
- change: 1,
- focusin: 1
- }) {
- eventName = "on" + i;
- isSupported = ( eventName in div );
- if ( !isSupported ) {
- div.setAttribute( eventName, "return;" );
- isSupported = ( typeof div[ eventName ] === "function" );
- }
- support[ i + "Bubbles" ] = isSupported;
- }
- }
-
- fragment.removeChild( div );
-
- // Null elements to avoid leaks in IE
- fragment = select = opt = marginDiv = div = input = null;
-
- // Run tests that need a body at doc ready
- jQuery(function() {
- var container, outer, inner, table, td, offsetSupport,
- conMarginTop, ptlm, vb, style, html,
- body = document.getElementsByTagName("body")[0];
-
- if ( !body ) {
- // Return for frameset docs that don't have a body
- return;
- }
-
- conMarginTop = 1;
- ptlm = "position:absolute;top:0;left:0;width:1px;height:1px;margin:0;";
- vb = "visibility:hidden;border:0;";
- style = "style='" + ptlm + "border:5px solid #000;padding:0;'";
- html = "<div " + style + "><div></div></div>" +
- "<table " + style + " cellpadding='0' cellspacing='0'>" +
- "<tr><td></td></tr></table>";
-
- container = document.createElement("div");
- container.style.cssText = vb + "width:0;height:0;position:static;top:0;margin-top:" + conMarginTop + "px";
- body.insertBefore( container, body.firstChild );
-
- // Construct the test element
- div = document.createElement("div");
- container.appendChild( div );
-
- // Check if table cells still have offsetWidth/Height when they are set
- // to display:none and there are still other visible table cells in a
- // table row; if so, offsetWidth/Height are not reliable for use when
- // determining if an element has been hidden directly using
- // display:none (it is still safe to use offsets if a parent element is
- // hidden; don safety goggles and see bug #4512 for more information).
- // (only IE 8 fails this test)
- div.innerHTML = "<table><tr><td style='padding:0;border:0;display:none'></td><td>t</td></tr></table>";
- tds = div.getElementsByTagName( "td" );
- isSupported = ( tds[ 0 ].offsetHeight === 0 );
-
- tds[ 0 ].style.display = "";
- tds[ 1 ].style.display = "none";
-
- // Check if empty table cells still have offsetWidth/Height
- // (IE <= 8 fail this test)
- support.reliableHiddenOffsets = isSupported && ( tds[ 0 ].offsetHeight === 0 );
-
- // Figure out if the W3C box model works as expected
- div.innerHTML = "";
- div.style.width = div.style.paddingLeft = "1px";
- jQuery.boxModel = support.boxModel = div.offsetWidth === 2;
-
- if ( typeof div.style.zoom !== "undefined" ) {
- // Check if natively block-level elements act like inline-block
- // elements when setting their display to 'inline' and giving
- // them layout
- // (IE < 8 does this)
- div.style.display = "inline";
- div.style.zoom = 1;
- support.inlineBlockNeedsLayout = ( div.offsetWidth === 2 );
-
- // Check if elements with layout shrink-wrap their children
- // (IE 6 does this)
- div.style.display = "";
- div.innerHTML = "<div style='width:4px;'></div>";
- support.shrinkWrapBlocks = ( div.offsetWidth !== 2 );
- }
-
- div.style.cssText = ptlm + vb;
- div.innerHTML = html;
-
- outer = div.firstChild;
- inner = outer.firstChild;
- td = outer.nextSibling.firstChild.firstChild;
-
- offsetSupport = {
- doesNotAddBorder: ( inner.offsetTop !== 5 ),
- doesAddBorderForTableAndCells: ( td.offsetTop === 5 )
- };
-
- inner.style.position = "fixed";
- inner.style.top = "20px";
-
- // safari subtracts parent border width here which is 5px
- offsetSupport.fixedPosition = ( inner.offsetTop === 20 || inner.offsetTop === 15 );
- inner.style.position = inner.style.top = "";
-
- outer.style.overflow = "hidden";
- outer.style.position = "relative";
-
- offsetSupport.subtractsBorderForOverflowNotVisible = ( inner.offsetTop === -5 );
- offsetSupport.doesNotIncludeMarginInBodyOffset = ( body.offsetTop !== conMarginTop );
-
- body.removeChild( container );
- div = container = null;
-
- jQuery.extend( support, offsetSupport );
- });
-
- return support;
-})();
-
-
-
-
-var rbrace = /^(?:\{.*\}|\[.*\])$/,
- rmultiDash = /([A-Z])/g;
-
-jQuery.extend({
- cache: {},
-
- // Please use with caution
- uuid: 0,
-
- // Unique for each copy of jQuery on the page
- // Non-digits removed to match rinlinejQuery
- expando: "jQuery" + ( jQuery.fn.jquery + Math.random() ).replace( /\D/g, "" ),
-
- // The following elements throw uncatchable exceptions if you
- // attempt to add expando properties to them.
- noData: {
- "embed": true,
- // Ban all objects except for Flash (which handle expandos)
- "object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",
- "applet": true
- },
-
- hasData: function( elem ) {
- elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ];
- return !!elem && !isEmptyDataObject( elem );
- },
-
- data: function( elem, name, data, pvt /* Internal Use Only */ ) {
- if ( !jQuery.acceptData( elem ) ) {
- return;
- }
-
- var privateCache, thisCache, ret,
- internalKey = jQuery.expando,
- getByName = typeof name === "string",
-
- // We have to handle DOM nodes and JS objects differently because IE6-7
- // can't GC object references properly across the DOM-JS boundary
- isNode = elem.nodeType,
-
- // Only DOM nodes need the global jQuery cache; JS object data is
- // attached directly to the object so GC can occur automatically
- cache = isNode ? jQuery.cache : elem,
-
- // Only defining an ID for JS objects if its cache already exists allows
- // the code to shortcut on the same path as a DOM node with no cache
- id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey,
- isEvents = name === "events";
-
- // Avoid doing any more work than we need to when trying to get data on an
- // object that has no data at all
- if ( (!id || !cache[id] || (!isEvents && !pvt && !cache[id].data)) && getByName && data === undefined ) {
- return;
- }
-
- if ( !id ) {
- // Only DOM nodes need a new unique ID for each element since their data
- // ends up in the global cache
- if ( isNode ) {
- elem[ internalKey ] = id = ++jQuery.uuid;
- } else {
- id = internalKey;
- }
- }
-
- if ( !cache[ id ] ) {
- cache[ id ] = {};
-
- // Avoids exposing jQuery metadata on plain JS objects when the object
- // is serialized using JSON.stringify
- if ( !isNode ) {
- cache[ id ].toJSON = jQuery.noop;
- }
- }
-
- // An object can be passed to jQuery.data instead of a key/value pair; this gets
- // shallow copied over onto the existing cache
- if ( typeof name === "object" || typeof name === "function" ) {
- if ( pvt ) {
- cache[ id ] = jQuery.extend( cache[ id ], name );
- } else {
- cache[ id ].data = jQuery.extend( cache[ id ].data, name );
- }
- }
-
- privateCache = thisCache = cache[ id ];
-
- // jQuery data() is stored in a separate object inside the object's internal data
- // cache in order to avoid key collisions between internal data and user-defined
- // data.
- if ( !pvt ) {
- if ( !thisCache.data ) {
- thisCache.data = {};
- }
-
- thisCache = thisCache.data;
- }
-
- if ( data !== undefined ) {
- thisCache[ jQuery.camelCase( name ) ] = data;
- }
-
- // Users should not attempt to inspect the internal events object using jQuery.data,
- // it is undocumented and subject to change. But does anyone listen? No.
- if ( isEvents && !thisCache[ name ] ) {
- return privateCache.events;
- }
-
- // Check for both converted-to-camel and non-converted data property names
- // If a data property was specified
- if ( getByName ) {
-
- // First Try to find as-is property data
- ret = thisCache[ name ];
-
- // Test for null|undefined property data
- if ( ret == null ) {
-
- // Try to find the camelCased property
- ret = thisCache[ jQuery.camelCase( name ) ];
- }
- } else {
- ret = thisCache;
- }
-
- return ret;
- },
-
- removeData: function( elem, name, pvt /* Internal Use Only */ ) {
- if ( !jQuery.acceptData( elem ) ) {
- return;
- }
-
- var thisCache, i, l,
-
- // Reference to internal data cache key
- internalKey = jQuery.expando,
-
- isNode = elem.nodeType,
-
- // See jQuery.data for more information
- cache = isNode ? jQuery.cache : elem,
-
- // See jQuery.data for more information
- id = isNode ? elem[ internalKey ] : internalKey;
-
- // If there is already no cache entry for this object, there is no
- // purpose in continuing
- if ( !cache[ id ] ) {
- return;
- }
-
- if ( name ) {
-
- thisCache = pvt ? cache[ id ] : cache[ id ].data;
-
- if ( thisCache ) {
-
- // Support array or space separated string names for data keys
- if ( !jQuery.isArray( name ) ) {
-
- // try the string as a key before any manipulation
- if ( name in thisCache ) {
- name = [ name ];
- } else {
-
- // split the camel cased version by spaces unless a key with the spaces exists
- name = jQuery.camelCase( name );
- if ( name in thisCache ) {
- name = [ name ];
- } else {
- name = name.split( " " );
- }
- }
- }
-
- for ( i = 0, l = name.length; i < l; i++ ) {
- delete thisCache[ name[i] ];
- }
-
- // If there is no data left in the cache, we want to continue
- // and let the cache object itself get destroyed
- if ( !( pvt ? isEmptyDataObject : jQuery.isEmptyObject )( thisCache ) ) {
- return;
- }
- }
- }
-
- // See jQuery.data for more information
- if ( !pvt ) {
- delete cache[ id ].data;
-
- // Don't destroy the parent cache unless the internal data object
- // had been the only thing left in it
- if ( !isEmptyDataObject(cache[ id ]) ) {
- return;
- }
- }
-
- // Browsers that fail expando deletion also refuse to delete expandos on
- // the window, but it will allow it on all other JS objects; other browsers
- // don't care
- // Ensure that `cache` is not a window object #10080
- if ( jQuery.support.deleteExpando || !cache.setInterval ) {
- delete cache[ id ];
- } else {
- cache[ id ] = null;
- }
-
- // We destroyed the cache and need to eliminate the expando on the node to avoid
- // false lookups in the cache for entries that no longer exist
- if ( isNode ) {
- // IE does not allow us to delete expando properties from nodes,
- // nor does it have a removeAttribute function on Document nodes;
- // we must handle all of these cases
- if ( jQuery.support.deleteExpando ) {
- delete elem[ internalKey ];
- } else if ( elem.removeAttribute ) {
- elem.removeAttribute( internalKey );
- } else {
- elem[ internalKey ] = null;
- }
- }
- },
-
- // For internal use only.
- _data: function( elem, name, data ) {
- return jQuery.data( elem, name, data, true );
- },
-
- // A method for determining if a DOM node can handle the data expando
- acceptData: function( elem ) {
- if ( elem.nodeName ) {
- var match = jQuery.noData[ elem.nodeName.toLowerCase() ];
-
- if ( match ) {
- return !(match === true || elem.getAttribute("classid") !== match);
- }
- }
-
- return true;
- }
-});
-
-jQuery.fn.extend({
- data: function( key, value ) {
- var parts, attr, name,
- data = null;
-
- if ( typeof key === "undefined" ) {
- if ( this.length ) {
- data = jQuery.data( this[0] );
-
- if ( this[0].nodeType === 1 && !jQuery._data( this[0], "parsedAttrs" ) ) {
- attr = this[0].attributes;
- for ( var i = 0, l = attr.length; i < l; i++ ) {
- name = attr[i].name;
-
- if ( name.indexOf( "data-" ) === 0 ) {
- name = jQuery.camelCase( name.substring(5) );
-
- dataAttr( this[0], name, data[ name ] );
- }
- }
- jQuery._data( this[0], "parsedAttrs", true );
- }
- }
-
- return data;
-
- } else if ( typeof key === "object" ) {
- return this.each(function() {
- jQuery.data( this, key );
- });
- }
-
- parts = key.split(".");
- parts[1] = parts[1] ? "." + parts[1] : "";
-
- if ( value === undefined ) {
- data = this.triggerHandler("getData" + parts[1] + "!", [parts[0]]);
-
- // Try to fetch any internally stored data first
- if ( data === undefined && this.length ) {
- data = jQuery.data( this[0], key );
- data = dataAttr( this[0], key, data );
- }
-
- return data === undefined && parts[1] ?
- this.data( parts[0] ) :
- data;
-
- } else {
- return this.each(function() {
- var self = jQuery( this ),
- args = [ parts[0], value ];
-
- self.triggerHandler( "setData" + parts[1] + "!", args );
- jQuery.data( this, key, value );
- self.triggerHandler( "changeData" + parts[1] + "!", args );
- });
- }
- },
-
- removeData: function( key ) {
- return this.each(function() {
- jQuery.removeData( this, key );
- });
- }
-});
-
-function dataAttr( elem, key, data ) {
- // If nothing was found internally, try to fetch any
- // data from the HTML5 data-* attribute
- if ( data === undefined && elem.nodeType === 1 ) {
-
- var name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase();
-
- data = elem.getAttribute( name );
-
- if ( typeof data === "string" ) {
- try {
- data = data === "true" ? true :
- data === "false" ? false :
- data === "null" ? null :
- jQuery.isNumeric( data ) ? parseFloat( data ) :
- rbrace.test( data ) ? jQuery.parseJSON( data ) :
- data;
- } catch( e ) {}
-
- // Make sure we set the data so it isn't changed later
- jQuery.data( elem, key, data );
-
- } else {
- data = undefined;
- }
- }
-
- return data;
-}
-
-// checks a cache object for emptiness
-function isEmptyDataObject( obj ) {
- for ( var name in obj ) {
-
- // if the public data object is empty, the private is still empty
- if ( name === "data" && jQuery.isEmptyObject( obj[name] ) ) {
- continue;
- }
- if ( name !== "toJSON" ) {
- return false;
- }
- }
-
- return true;
-}
-
-
-
-
-function handleQueueMarkDefer( elem, type, src ) {
- var deferDataKey = type + "defer",
- queueDataKey = type + "queue",
- markDataKey = type + "mark",
- defer = jQuery._data( elem, deferDataKey );
- if ( defer &&
- ( src === "queue" || !jQuery._data(elem, queueDataKey) ) &&
- ( src === "mark" || !jQuery._data(elem, markDataKey) ) ) {
- // Give room for hard-coded callbacks to fire first
- // and eventually mark/queue something else on the element
- setTimeout( function() {
- if ( !jQuery._data( elem, queueDataKey ) &&
- !jQuery._data( elem, markDataKey ) ) {
- jQuery.removeData( elem, deferDataKey, true );
- defer.fire();
- }
- }, 0 );
- }
-}
-
-jQuery.extend({
-
- _mark: function( elem, type ) {
- if ( elem ) {
- type = ( type || "fx" ) + "mark";
- jQuery._data( elem, type, (jQuery._data( elem, type ) || 0) + 1 );
- }
- },
-
- _unmark: function( force, elem, type ) {
- if ( force !== true ) {
- type = elem;
- elem = force;
- force = false;
- }
- if ( elem ) {
- type = type || "fx";
- var key = type + "mark",
- count = force ? 0 : ( (jQuery._data( elem, key ) || 1) - 1 );
- if ( count ) {
- jQuery._data( elem, key, count );
- } else {
- jQuery.removeData( elem, key, true );
- handleQueueMarkDefer( elem, type, "mark" );
- }
- }
- },
-
- queue: function( elem, type, data ) {
- var q;
- if ( elem ) {
- type = ( type || "fx" ) + "queue";
- q = jQuery._data( elem, type );
-
- // Speed up dequeue by getting out quickly if this is just a lookup
- if ( data ) {
- if ( !q || jQuery.isArray(data) ) {
- q = jQuery._data( elem, type, jQuery.makeArray(data) );
- } else {
- q.push( data );
- }
- }
- return q || [];
- }
- },
-
- dequeue: function( elem, type ) {
- type = type || "fx";
-
- var queue = jQuery.queue( elem, type ),
- fn = queue.shift(),
- hooks = {};
-
- // If the fx queue is dequeued, always remove the progress sentinel
- if ( fn === "inprogress" ) {
- fn = queue.shift();
- }
-
- if ( fn ) {
- // Add a progress sentinel to prevent the fx queue from being
- // automatically dequeued
- if ( type === "fx" ) {
- queue.unshift( "inprogress" );
- }
-
- jQuery._data( elem, type + ".run", hooks );
- fn.call( elem, function() {
- jQuery.dequeue( elem, type );
- }, hooks );
- }
-
- if ( !queue.length ) {
- jQuery.removeData( elem, type + "queue " + type + ".run", true );
- handleQueueMarkDefer( elem, type, "queue" );
- }
- }
-});
-
-jQuery.fn.extend({
- queue: function( type, data ) {
- if ( typeof type !== "string" ) {
- data = type;
- type = "fx";
- }
-
- if ( data === undefined ) {
- return jQuery.queue( this[0], type );
- }
- return this.each(function() {
- var queue = jQuery.queue( this, type, data );
-
- if ( type === "fx" && queue[0] !== "inprogress" ) {
- jQuery.dequeue( this, type );
- }
- });
- },
- dequeue: function( type ) {
- return this.each(function() {
- jQuery.dequeue( this, type );
- });
- },
- // Based off of the plugin by Clint Helfers, with permission.
- // http://blindsignals.com/index.php/2009/07/jquery-delay/
- delay: function( time, type ) {
- time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time;
- type = type || "fx";
-
- return this.queue( type, function( next, hooks ) {
- var timeout = setTimeout( next, time );
- hooks.stop = function() {
- clearTimeout( timeout );
- };
- });
- },
- clearQueue: function( type ) {
- return this.queue( type || "fx", [] );
- },
- // Get a promise resolved when queues of a certain type
- // are emptied (fx is the type by default)
- promise: function( type, object ) {
- if ( typeof type !== "string" ) {
- object = type;
- type = undefined;
- }
- type = type || "fx";
- var defer = jQuery.Deferred(),
- elements = this,
- i = elements.length,
- count = 1,
- deferDataKey = type + "defer",
- queueDataKey = type + "queue",
- markDataKey = type + "mark",
- tmp;
- function resolve() {
- if ( !( --count ) ) {
- defer.resolveWith( elements, [ elements ] );
- }
- }
- while( i-- ) {
- if (( tmp = jQuery.data( elements[ i ], deferDataKey, undefined, true ) ||
- ( jQuery.data( elements[ i ], queueDataKey, undefined, true ) ||
- jQuery.data( elements[ i ], markDataKey, undefined, true ) ) &&
- jQuery.data( elements[ i ], deferDataKey, jQuery.Callbacks( "once memory" ), true ) )) {
- count++;
- tmp.add( resolve );
- }
- }
- resolve();
- return defer.promise();
- }
-});
-
-
-
-
-var rclass = /[\n\t\r]/g,
- rspace = /\s+/,
- rreturn = /\r/g,
- rtype = /^(?:button|input)$/i,
- rfocusable = /^(?:button|input|object|select|textarea)$/i,
- rclickable = /^a(?:rea)?$/i,
- rboolean = /^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i,
- getSetAttribute = jQuery.support.getSetAttribute,
- nodeHook, boolHook, fixSpecified;
-
-jQuery.fn.extend({
- attr: function( name, value ) {
- return jQuery.access( this, name, value, true, jQuery.attr );
- },
-
- removeAttr: function( name ) {
- return this.each(function() {
- jQuery.removeAttr( this, name );
- });
- },
-
- prop: function( name, value ) {
- return jQuery.access( this, name, value, true, jQuery.prop );
- },
-
- removeProp: function( name ) {
- name = jQuery.propFix[ name ] || name;
- return this.each(function() {
- // try/catch handles cases where IE balks (such as removing a property on window)
- try {
- this[ name ] = undefined;
- delete this[ name ];
- } catch( e ) {}
- });
- },
-
- addClass: function( value ) {
- var classNames, i, l, elem,
- setClass, c, cl;
-
- if ( jQuery.isFunction( value ) ) {
- return this.each(function( j ) {
- jQuery( this ).addClass( value.call(this, j, this.className) );
- });
- }
-
- if ( value && typeof value === "string" ) {
- classNames = value.split( rspace );
-
- for ( i = 0, l = this.length; i < l; i++ ) {
- elem = this[ i ];
-
- if ( elem.nodeType === 1 ) {
- if ( !elem.className && classNames.length === 1 ) {
- elem.className = value;
-
- } else {
- setClass = " " + elem.className + " ";
-
- for ( c = 0, cl = classNames.length; c < cl; c++ ) {
- if ( !~setClass.indexOf( " " + classNames[ c ] + " " ) ) {
- setClass += classNames[ c ] + " ";
- }
- }
- elem.className = jQuery.trim( setClass );
- }
- }
- }
- }
-
- return this;
- },
-
- removeClass: function( value ) {
- var classNames, i, l, elem, className, c, cl;
-
- if ( jQuery.isFunction( value ) ) {
- return this.each(function( j ) {
- jQuery( this ).removeClass( value.call(this, j, this.className) );
- });
- }
-
- if ( (value && typeof value === "string") || value === undefined ) {
- classNames = ( value || "" ).split( rspace );
-
- for ( i = 0, l = this.length; i < l; i++ ) {
- elem = this[ i ];
-
- if ( elem.nodeType === 1 && elem.className ) {
- if ( value ) {
- className = (" " + elem.className + " ").replace( rclass, " " );
- for ( c = 0, cl = classNames.length; c < cl; c++ ) {
- className = className.replace(" " + classNames[ c ] + " ", " ");
- }
- elem.className = jQuery.trim( className );
-
- } else {
- elem.className = "";
- }
- }
- }
- }
-
- return this;
- },
-
- toggleClass: function( value, stateVal ) {
- var type = typeof value,
- isBool = typeof stateVal === "boolean";
-
- if ( jQuery.isFunction( value ) ) {
- return this.each(function( i ) {
- jQuery( this ).toggleClass( value.call(this, i, this.className, stateVal), stateVal );
- });
- }
-
- return this.each(function() {
- if ( type === "string" ) {
- // toggle individual class names
- var className,
- i = 0,
- self = jQuery( this ),
- state = stateVal,
- classNames = value.split( rspace );
-
- while ( (className = classNames[ i++ ]) ) {
- // check each className given, space seperated list
- state = isBool ? state : !self.hasClass( className );
- self[ state ? "addClass" : "removeClass" ]( className );
- }
-
- } else if ( type === "undefined" || type === "boolean" ) {
- if ( this.className ) {
- // store className if set
- jQuery._data( this, "__className__", this.className );
- }
-
- // toggle whole className
- this.className = this.className || value === false ? "" : jQuery._data( this, "__className__" ) || "";
- }
- });
- },
-
- hasClass: function( selector ) {
- var className = " " + selector + " ",
- i = 0,
- l = this.length;
- for ( ; i < l; i++ ) {
- if ( this[i].nodeType === 1 && (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) > -1 ) {
- return true;
- }
- }
-
- return false;
- },
-
- val: function( value ) {
- var hooks, ret, isFunction,
- elem = this[0];
-
- if ( !arguments.length ) {
- if ( elem ) {
- hooks = jQuery.valHooks[ elem.nodeName.toLowerCase() ] || jQuery.valHooks[ elem.type ];
-
- if ( hooks && "get" in hooks && (ret = hooks.get( elem, "value" )) !== undefined ) {
- return ret;
- }
-
- ret = elem.value;
-
- return typeof ret === "string" ?
- // handle most common string cases
- ret.replace(rreturn, "") :
- // handle cases where value is null/undef or number
- ret == null ? "" : ret;
- }
-
- return;
- }
-
- isFunction = jQuery.isFunction( value );
-
- return this.each(function( i ) {
- var self = jQuery(this), val;
-
- if ( this.nodeType !== 1 ) {
- return;
- }
-
- if ( isFunction ) {
- val = value.call( this, i, self.val() );
- } else {
- val = value;
- }
-
- // Treat null/undefined as ""; convert numbers to string
- if ( val == null ) {
- val = "";
- } else if ( typeof val === "number" ) {
- val += "";
- } else if ( jQuery.isArray( val ) ) {
- val = jQuery.map(val, function ( value ) {
- return value == null ? "" : value + "";
- });
- }
-
- hooks = jQuery.valHooks[ this.nodeName.toLowerCase() ] || jQuery.valHooks[ this.type ];
-
- // If set returns undefined, fall back to normal setting
- if ( !hooks || !("set" in hooks) || hooks.set( this, val, "value" ) === undefined ) {
- this.value = val;
- }
- });
- }
-});
-
-jQuery.extend({
- valHooks: {
- option: {
- get: function( elem ) {
- // attributes.value is undefined in Blackberry 4.7 but
- // uses .value. See #6932
- var val = elem.attributes.value;
- return !val || val.specified ? elem.value : elem.text;
- }
- },
- select: {
- get: function( elem ) {
- var value, i, max, option,
- index = elem.selectedIndex,
- values = [],
- options = elem.options,
- one = elem.type === "select-one";
-
- // Nothing was selected
- if ( index < 0 ) {
- return null;
- }
-
- // Loop through all the selected options
- i = one ? index : 0;
- max = one ? index + 1 : options.length;
- for ( ; i < max; i++ ) {
- option = options[ i ];
-
- // Don't return options that are disabled or in a disabled optgroup
- if ( option.selected && (jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null) &&
- (!option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" )) ) {
-
- // Get the specific value for the option
- value = jQuery( option ).val();
-
- // We don't need an array for one selects
- if ( one ) {
- return value;
- }
-
- // Multi-Selects return an array
- values.push( value );
- }
- }
-
- // Fixes Bug #2551 -- select.val() broken in IE after form.reset()
- if ( one && !values.length && options.length ) {
- return jQuery( options[ index ] ).val();
- }
-
- return values;
- },
-
- set: function( elem, value ) {
- var values = jQuery.makeArray( value );
-
- jQuery(elem).find("option").each(function() {
- this.selected = jQuery.inArray( jQuery(this).val(), values ) >= 0;
- });
-
- if ( !values.length ) {
- elem.selectedIndex = -1;
- }
- return values;
- }
- }
- },
-
- attrFn: {
- val: true,
- css: true,
- html: true,
- text: true,
- data: true,
- width: true,
- height: true,
- offset: true
- },
-
- attr: function( elem, name, value, pass ) {
- var ret, hooks, notxml,
- nType = elem.nodeType;
-
- // don't get/set attributes on text, comment and attribute nodes
- if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
- return;
- }
-
- if ( pass && name in jQuery.attrFn ) {
- return jQuery( elem )[ name ]( value );
- }
-
- // Fallback to prop when attributes are not supported
- if ( typeof elem.getAttribute === "undefined" ) {
- return jQuery.prop( elem, name, value );
- }
-
- notxml = nType !== 1 || !jQuery.isXMLDoc( elem );
-
- // All attributes are lowercase
- // Grab necessary hook if one is defined
- if ( notxml ) {
- name = name.toLowerCase();
- hooks = jQuery.attrHooks[ name ] || ( rboolean.test( name ) ? boolHook : nodeHook );
- }
-
- if ( value !== undefined ) {
-
- if ( value === null ) {
- jQuery.removeAttr( elem, name );
- return;
-
- } else if ( hooks && "set" in hooks && notxml && (ret = hooks.set( elem, value, name )) !== undefined ) {
- return ret;
-
- } else {
- elem.setAttribute( name, "" + value );
- return value;
- }
-
- } else if ( hooks && "get" in hooks && notxml && (ret = hooks.get( elem, name )) !== null ) {
- return ret;
-
- } else {
-
- ret = elem.getAttribute( name );
-
- // Non-existent attributes return null, we normalize to undefined
- return ret === null ?
- undefined :
- ret;
- }
- },
-
- removeAttr: function( elem, value ) {
- var propName, attrNames, name, l,
- i = 0;
-
- if ( value && elem.nodeType === 1 ) {
- attrNames = value.toLowerCase().split( rspace );
- l = attrNames.length;
-
- for ( ; i < l; i++ ) {
- name = attrNames[ i ];
-
- if ( name ) {
- propName = jQuery.propFix[ name ] || name;
-
- // See #9699 for explanation of this approach (setting first, then removal)
- jQuery.attr( elem, name, "" );
- elem.removeAttribute( getSetAttribute ? name : propName );
-
- // Set corresponding property to false for boolean attributes
- if ( rboolean.test( name ) && propName in elem ) {
- elem[ propName ] = false;
- }
- }
- }
- }
- },
-
- attrHooks: {
- type: {
- set: function( elem, value ) {
- // We can't allow the type property to be changed (since it causes problems in IE)
- if ( rtype.test( elem.nodeName ) && elem.parentNode ) {
- jQuery.error( "type property can't be changed" );
- } else if ( !jQuery.support.radioValue && value === "radio" && jQuery.nodeName(elem, "input") ) {
- // Setting the type on a radio button after the value resets the value in IE6-9
- // Reset value to it's default in case type is set after value
- // This is for element creation
- var val = elem.value;
- elem.setAttribute( "type", value );
- if ( val ) {
- elem.value = val;
- }
- return value;
- }
- }
- },
- // Use the value property for back compat
- // Use the nodeHook for button elements in IE6/7 (#1954)
- value: {
- get: function( elem, name ) {
- if ( nodeHook && jQuery.nodeName( elem, "button" ) ) {
- return nodeHook.get( elem, name );
- }
- return name in elem ?
- elem.value :
- null;
- },
- set: function( elem, value, name ) {
- if ( nodeHook && jQuery.nodeName( elem, "button" ) ) {
- return nodeHook.set( elem, value, name );
- }
- // Does not return so that setAttribute is also used
- elem.value = value;
- }
- }
- },
-
- propFix: {
- tabindex: "tabIndex",
- readonly: "readOnly",
- "for": "htmlFor",
- "class": "className",
- maxlength: "maxLength",
- cellspacing: "cellSpacing",
- cellpadding: "cellPadding",
- rowspan: "rowSpan",
- colspan: "colSpan",
- usemap: "useMap",
- frameborder: "frameBorder",
- contenteditable: "contentEditable"
- },
-
- prop: function( elem, name, value ) {
- var ret, hooks, notxml,
- nType = elem.nodeType;
-
- // don't get/set properties on text, comment and attribute nodes
- if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
- return;
- }
-
- notxml = nType !== 1 || !jQuery.isXMLDoc( elem );
-
- if ( notxml ) {
- // Fix name and attach hooks
- name = jQuery.propFix[ name ] || name;
- hooks = jQuery.propHooks[ name ];
- }
-
- if ( value !== undefined ) {
- if ( hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) {
- return ret;
-
- } else {
- return ( elem[ name ] = value );
- }
-
- } else {
- if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) {
- return ret;
-
- } else {
- return elem[ name ];
- }
- }
- },
-
- propHooks: {
- tabIndex: {
- get: function( elem ) {
- // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set
- // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/
- var attributeNode = elem.getAttributeNode("tabindex");
-
- return attributeNode && attributeNode.specified ?
- parseInt( attributeNode.value, 10 ) :
- rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ?
- 0 :
- undefined;
- }
- }
- }
-});
-
-// Add the tabIndex propHook to attrHooks for back-compat (different case is intentional)
-jQuery.attrHooks.tabindex = jQuery.propHooks.tabIndex;
-
-// Hook for boolean attributes
-boolHook = {
- get: function( elem, name ) {
- // Align boolean attributes with corresponding properties
- // Fall back to attribute presence where some booleans are not supported
- var attrNode,
- property = jQuery.prop( elem, name );
- return property === true || typeof property !== "boolean" && ( attrNode = elem.getAttributeNode(name) ) && attrNode.nodeValue !== false ?
- name.toLowerCase() :
- undefined;
- },
- set: function( elem, value, name ) {
- var propName;
- if ( value === false ) {
- // Remove boolean attributes when set to false
- jQuery.removeAttr( elem, name );
- } else {
- // value is true since we know at this point it's type boolean and not false
- // Set boolean attributes to the same name and set the DOM property
- propName = jQuery.propFix[ name ] || name;
- if ( propName in elem ) {
- // Only set the IDL specifically if it already exists on the element
- elem[ propName ] = true;
- }
-
- elem.setAttribute( name, name.toLowerCase() );
- }
- return name;
- }
-};
-
-// IE6/7 do not support getting/setting some attributes with get/setAttribute
-if ( !getSetAttribute ) {
-
- fixSpecified = {
- name: true,
- id: true
- };
-
- // Use this for any attribute in IE6/7
- // This fixes almost every IE6/7 issue
- nodeHook = jQuery.valHooks.button = {
- get: function( elem, name ) {
- var ret;
- ret = elem.getAttributeNode( name );
- return ret && ( fixSpecified[ name ] ? ret.nodeValue !== "" : ret.specified ) ?
- ret.nodeValue :
- undefined;
- },
- set: function( elem, value, name ) {
- // Set the existing or create a new attribute node
- var ret = elem.getAttributeNode( name );
- if ( !ret ) {
- ret = document.createAttribute( name );
- elem.setAttributeNode( ret );
- }
- return ( ret.nodeValue = value + "" );
- }
- };
-
- // Apply the nodeHook to tabindex
- jQuery.attrHooks.tabindex.set = nodeHook.set;
-
- // Set width and height to auto instead of 0 on empty string( Bug #8150 )
- // This is for removals
- jQuery.each([ "width", "height" ], function( i, name ) {
- jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], {
- set: function( elem, value ) {
- if ( value === "" ) {
- elem.setAttribute( name, "auto" );
- return value;
- }
- }
- });
- });
-
- // Set contenteditable to false on removals(#10429)
- // Setting to empty string throws an error as an invalid value
- jQuery.attrHooks.contenteditable = {
- get: nodeHook.get,
- set: function( elem, value, name ) {
- if ( value === "" ) {
- value = "false";
- }
- nodeHook.set( elem, value, name );
- }
- };
-}
-
-
-// Some attributes require a special call on IE
-if ( !jQuery.support.hrefNormalized ) {
- jQuery.each([ "href", "src", "width", "height" ], function( i, name ) {
- jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], {
- get: function( elem ) {
- var ret = elem.getAttribute( name, 2 );
- return ret === null ? undefined : ret;
- }
- });
- });
-}
-
-if ( !jQuery.support.style ) {
- jQuery.attrHooks.style = {
- get: function( elem ) {
- // Return undefined in the case of empty string
- // Normalize to lowercase since IE uppercases css property names
- return elem.style.cssText.toLowerCase() || undefined;
- },
- set: function( elem, value ) {
- return ( elem.style.cssText = "" + value );
- }
- };
-}
-
-// Safari mis-reports the default selected property of an option
-// Accessing the parent's selectedIndex property fixes it
-if ( !jQuery.support.optSelected ) {
- jQuery.propHooks.selected = jQuery.extend( jQuery.propHooks.selected, {
- get: function( elem ) {
- var parent = elem.parentNode;
-
- if ( parent ) {
- parent.selectedIndex;
-
- // Make sure that it also works with optgroups, see #5701
- if ( parent.parentNode ) {
- parent.parentNode.selectedIndex;
- }
- }
- return null;
- }
- });
-}
-
-// IE6/7 call enctype encoding
-if ( !jQuery.support.enctype ) {
- jQuery.propFix.enctype = "encoding";
-}
-
-// Radios and checkboxes getter/setter
-if ( !jQuery.support.checkOn ) {
- jQuery.each([ "radio", "checkbox" ], function() {
- jQuery.valHooks[ this ] = {
- get: function( elem ) {
- // Handle the case where in Webkit "" is returned instead of "on" if a value isn't specified
- return elem.getAttribute("value") === null ? "on" : elem.value;
- }
- };
- });
-}
-jQuery.each([ "radio", "checkbox" ], function() {
- jQuery.valHooks[ this ] = jQuery.extend( jQuery.valHooks[ this ], {
- set: function( elem, value ) {
- if ( jQuery.isArray( value ) ) {
- return ( elem.checked = jQuery.inArray( jQuery(elem).val(), value ) >= 0 );
- }
- }
- });
-});
-
-
-
-
-var rformElems = /^(?:textarea|input|select)$/i,
- rtypenamespace = /^([^\.]*)?(?:\.(.+))?$/,
- rhoverHack = /\bhover(\.\S+)?\b/,
- rkeyEvent = /^key/,
- rmouseEvent = /^(?:mouse|contextmenu)|click/,
- rfocusMorph = /^(?:focusinfocus|focusoutblur)$/,
- rquickIs = /^(\w*)(?:#([\w\-]+))?(?:\.([\w\-]+))?$/,
- quickParse = function( selector ) {
- var quick = rquickIs.exec( selector );
- if ( quick ) {
- // 0 1 2 3
- // [ _, tag, id, class ]
- quick[1] = ( quick[1] || "" ).toLowerCase();
- quick[3] = quick[3] && new RegExp( "(?:^|\\s)" + quick[3] + "(?:\\s|$)" );
- }
- return quick;
- },
- quickIs = function( elem, m ) {
- var attrs = elem.attributes || {};
- return (
- (!m[1] || elem.nodeName.toLowerCase() === m[1]) &&
- (!m[2] || (attrs.id || {}).value === m[2]) &&
- (!m[3] || m[3].test( (attrs[ "class" ] || {}).value ))
- );
- },
- hoverHack = function( events ) {
- return jQuery.event.special.hover ? events : events.replace( rhoverHack, "mouseenter$1 mouseleave$1" );
- };
-
-/*
- * Helper functions for managing events -- not part of the public interface.
- * Props to Dean Edwards' addEvent library for many of the ideas.
- */
-jQuery.event = {
-
- add: function( elem, types, handler, data, selector ) {
-
- var elemData, eventHandle, events,
- t, tns, type, namespaces, handleObj,
- handleObjIn, quick, handlers, special;
-
- // Don't attach events to noData or text/comment nodes (allow plain objects tho)
- if ( elem.nodeType === 3 || elem.nodeType === 8 || !types || !handler || !(elemData = jQuery._data( elem )) ) {
- return;
- }
-
- // Caller can pass in an object of custom data in lieu of the handler
- if ( handler.handler ) {
- handleObjIn = handler;
- handler = handleObjIn.handler;
- }
-
- // Make sure that the handler has a unique ID, used to find/remove it later
- if ( !handler.guid ) {
- handler.guid = jQuery.guid++;
- }
-
- // Init the element's event structure and main handler, if this is the first
- events = elemData.events;
- if ( !events ) {
- elemData.events = events = {};
- }
- eventHandle = elemData.handle;
- if ( !eventHandle ) {
- elemData.handle = eventHandle = function( e ) {
- // Discard the second event of a jQuery.event.trigger() and
- // when an event is called after a page has unloaded
- return typeof jQuery !== "undefined" && (!e || jQuery.event.triggered !== e.type) ?
- jQuery.event.dispatch.apply( eventHandle.elem, arguments ) :
- undefined;
- };
- // Add elem as a property of the handle fn to prevent a memory leak with IE non-native events
- eventHandle.elem = elem;
- }
-
- // Handle multiple events separated by a space
- // jQuery(...).bind("mouseover mouseout", fn);
- types = jQuery.trim( hoverHack(types) ).split( " " );
- for ( t = 0; t < types.length; t++ ) {
-
- tns = rtypenamespace.exec( types[t] ) || [];
- type = tns[1];
- namespaces = ( tns[2] || "" ).split( "." ).sort();
-
- // If event changes its type, use the special event handlers for the changed type
- special = jQuery.event.special[ type ] || {};
-
- // If selector defined, determine special event api type, otherwise given type
- type = ( selector ? special.delegateType : special.bindType ) || type;
-
- // Update special based on newly reset type
- special = jQuery.event.special[ type ] || {};
-
- // handleObj is passed to all event handlers
- handleObj = jQuery.extend({
- type: type,
- origType: tns[1],
- data: data,
- handler: handler,
- guid: handler.guid,
- selector: selector,
- quick: quickParse( selector ),
- namespace: namespaces.join(".")
- }, handleObjIn );
-
- // Init the event handler queue if we're the first
- handlers = events[ type ];
- if ( !handlers ) {
- handlers = events[ type ] = [];
- handlers.delegateCount = 0;
-
- // Only use addEventListener/attachEvent if the special events handler returns false
- if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) {
- // Bind the global event handler to the element
- if ( elem.addEventListener ) {
- elem.addEventListener( type, eventHandle, false );
-
- } else if ( elem.attachEvent ) {
- elem.attachEvent( "on" + type, eventHandle );
- }
- }
- }
-
- if ( special.add ) {
- special.add.call( elem, handleObj );
-
- if ( !handleObj.handler.guid ) {
- handleObj.handler.guid = handler.guid;
- }
- }
-
- // Add to the element's handler list, delegates in front
- if ( selector ) {
- handlers.splice( handlers.delegateCount++, 0, handleObj );
- } else {
- handlers.push( handleObj );
- }
-
- // Keep track of which events have ever been used, for event optimization
- jQuery.event.global[ type ] = true;
- }
-
- // Nullify elem to prevent memory leaks in IE
- elem = null;
- },
-
- global: {},
-
- // Detach an event or set of events from an element
- remove: function( elem, types, handler, selector, mappedTypes ) {
-
- var elemData = jQuery.hasData( elem ) && jQuery._data( elem ),
- t, tns, type, origType, namespaces, origCount,
- j, events, special, handle, eventType, handleObj;
-
- if ( !elemData || !(events = elemData.events) ) {
- return;
- }
-
- // Once for each type.namespace in types; type may be omitted
- types = jQuery.trim( hoverHack( types || "" ) ).split(" ");
- for ( t = 0; t < types.length; t++ ) {
- tns = rtypenamespace.exec( types[t] ) || [];
- type = origType = tns[1];
- namespaces = tns[2];
-
- // Unbind all events (on this namespace, if provided) for the element
- if ( !type ) {
- for ( type in events ) {
- jQuery.event.remove( elem, type + types[ t ], handler, selector, true );
- }
- continue;
- }
-
- special = jQuery.event.special[ type ] || {};
- type = ( selector? special.delegateType : special.bindType ) || type;
- eventType = events[ type ] || [];
- origCount = eventType.length;
- namespaces = namespaces ? new RegExp("(^|\\.)" + namespaces.split(".").sort().join("\\.(?:.*\\.)?") + "(\\.|$)") : null;
-
- // Remove matching events
- for ( j = 0; j < eventType.length; j++ ) {
- handleObj = eventType[ j ];
-
- if ( ( mappedTypes || origType === handleObj.origType ) &&
- ( !handler || handler.guid === handleObj.guid ) &&
- ( !namespaces || namespaces.test( handleObj.namespace ) ) &&
- ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) {
- eventType.splice( j--, 1 );
-
- if ( handleObj.selector ) {
- eventType.delegateCount--;
- }
- if ( special.remove ) {
- special.remove.call( elem, handleObj );
- }
- }
- }
-
- // Remove generic event handler if we removed something and no more handlers exist
- // (avoids potential for endless recursion during removal of special event handlers)
- if ( eventType.length === 0 && origCount !== eventType.length ) {
- if ( !special.teardown || special.teardown.call( elem, namespaces ) === false ) {
- jQuery.removeEvent( elem, type, elemData.handle );
- }
-
- delete events[ type ];
- }
- }
-
- // Remove the expando if it's no longer used
- if ( jQuery.isEmptyObject( events ) ) {
- handle = elemData.handle;
- if ( handle ) {
- handle.elem = null;
- }
-
- // removeData also checks for emptiness and clears the expando if empty
- // so use it instead of delete
- jQuery.removeData( elem, [ "events", "handle" ], true );
- }
- },
-
- // Events that are safe to short-circuit if no handlers are attached.
- // Native DOM events should not be added, they may have inline handlers.
- customEvent: {
- "getData": true,
- "setData": true,
- "changeData": true
- },
-
- trigger: function( event, data, elem, onlyHandlers ) {
- // Don't do events on text and comment nodes
- if ( elem && (elem.nodeType === 3 || elem.nodeType === 8) ) {
- return;
- }
-
- // Event object or event type
- var type = event.type || event,
- namespaces = [],
- cache, exclusive, i, cur, old, ontype, special, handle, eventPath, bubbleType;
-
- // focus/blur morphs to focusin/out; ensure we're not firing them right now
- if ( rfocusMorph.test( type + jQuery.event.triggered ) ) {
- return;
- }
-
- if ( type.indexOf( "!" ) >= 0 ) {
- // Exclusive events trigger only for the exact event (no namespaces)
- type = type.slice(0, -1);
- exclusive = true;
- }
-
- if ( type.indexOf( "." ) >= 0 ) {
- // Namespaced trigger; create a regexp to match event type in handle()
- namespaces = type.split(".");
- type = namespaces.shift();
- namespaces.sort();
- }
-
- if ( (!elem || jQuery.event.customEvent[ type ]) && !jQuery.event.global[ type ] ) {
- // No jQuery handlers for this event type, and it can't have inline handlers
- return;
- }
-
- // Caller can pass in an Event, Object, or just an event type string
- event = typeof event === "object" ?
- // jQuery.Event object
- event[ jQuery.expando ] ? event :
- // Object literal
- new jQuery.Event( type, event ) :
- // Just the event type (string)
- new jQuery.Event( type );
-
- event.type = type;
- event.isTrigger = true;
- event.exclusive = exclusive;
- event.namespace = namespaces.join( "." );
- event.namespace_re = event.namespace? new RegExp("(^|\\.)" + namespaces.join("\\.(?:.*\\.)?") + "(\\.|$)") : null;
- ontype = type.indexOf( ":" ) < 0 ? "on" + type : "";
-
- // Handle a global trigger
- if ( !elem ) {
-
- // TODO: Stop taunting the data cache; remove global events and always attach to document
- cache = jQuery.cache;
- for ( i in cache ) {
- if ( cache[ i ].events && cache[ i ].events[ type ] ) {
- jQuery.event.trigger( event, data, cache[ i ].handle.elem, true );
- }
- }
- return;
- }
-
- // Clean up the event in case it is being reused
- event.result = undefined;
- if ( !event.target ) {
- event.target = elem;
- }
-
- // Clone any incoming data and prepend the event, creating the handler arg list
- data = data != null ? jQuery.makeArray( data ) : [];
- data.unshift( event );
-
- // Allow special events to draw outside the lines
- special = jQuery.event.special[ type ] || {};
- if ( special.trigger && special.trigger.apply( elem, data ) === false ) {
- return;
- }
-
- // Determine event propagation path in advance, per W3C events spec (#9951)
- // Bubble up to document, then to window; watch for a global ownerDocument var (#9724)
- eventPath = [[ elem, special.bindType || type ]];
- if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) {
-
- bubbleType = special.delegateType || type;
- cur = rfocusMorph.test( bubbleType + type ) ? elem : elem.parentNode;
- old = null;
- for ( ; cur; cur = cur.parentNode ) {
- eventPath.push([ cur, bubbleType ]);
- old = cur;
- }
-
- // Only add window if we got to document (e.g., not plain obj or detached DOM)
- if ( old && old === elem.ownerDocument ) {
- eventPath.push([ old.defaultView || old.parentWindow || window, bubbleType ]);
- }
- }
-
- // Fire handlers on the event path
- for ( i = 0; i < eventPath.length && !event.isPropagationStopped(); i++ ) {
-
- cur = eventPath[i][0];
- event.type = eventPath[i][1];
-
- handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] && jQuery._data( cur, "handle" );
- if ( handle ) {
- handle.apply( cur, data );
- }
- // Note that this is a bare JS function and not a jQuery handler
- handle = ontype && cur[ ontype ];
- if ( handle && jQuery.acceptData( cur ) && handle.apply( cur, data ) === false ) {
- event.preventDefault();
- }
- }
- event.type = type;
-
- // If nobody prevented the default action, do it now
- if ( !onlyHandlers && !event.isDefaultPrevented() ) {
-
- if ( (!special._default || special._default.apply( elem.ownerDocument, data ) === false) &&
- !(type === "click" && jQuery.nodeName( elem, "a" )) && jQuery.acceptData( elem ) ) {
-
- // Call a native DOM method on the target with the same name name as the event.
- // Can't use an .isFunction() check here because IE6/7 fails that test.
- // Don't do default actions on window, that's where global variables be (#6170)
- // IE<9 dies on focus/blur to hidden element (#1486)
- if ( ontype && elem[ type ] && ((type !== "focus" && type !== "blur") || event.target.offsetWidth !== 0) && !jQuery.isWindow( elem ) ) {
-
- // Don't re-trigger an onFOO event when we call its FOO() method
- old = elem[ ontype ];
-
- if ( old ) {
- elem[ ontype ] = null;
- }
-
- // Prevent re-triggering of the same event, since we already bubbled it above
- jQuery.event.triggered = type;
- elem[ type ]();
- jQuery.event.triggered = undefined;
-
- if ( old ) {
- elem[ ontype ] = old;
- }
- }
- }
- }
-
- return event.result;
- },
-
- dispatch: function( event ) {
-
- // Make a writable jQuery.Event from the native event object
- event = jQuery.event.fix( event || window.event );
-
- var handlers = ( (jQuery._data( this, "events" ) || {} )[ event.type ] || []),
- delegateCount = handlers.delegateCount,
- args = [].slice.call( arguments, 0 ),
- run_all = !event.exclusive && !event.namespace,
- handlerQueue = [],
- i, j, cur, jqcur, ret, selMatch, matched, matches, handleObj, sel, related;
-
- // Use the fix-ed jQuery.Event rather than the (read-only) native event
- args[0] = event;
- event.delegateTarget = this;
-
- // Determine handlers that should run if there are delegated events
- // Avoid disabled elements in IE (#6911) and non-left-click bubbling in Firefox (#3861)
- if ( delegateCount && !event.target.disabled && !(event.button && event.type === "click") ) {
-
- // Pregenerate a single jQuery object for reuse with .is()
- jqcur = jQuery(this);
- jqcur.context = this.ownerDocument || this;
-
- for ( cur = event.target; cur != this; cur = cur.parentNode || this ) {
- selMatch = {};
- matches = [];
- jqcur[0] = cur;
- for ( i = 0; i < delegateCount; i++ ) {
- handleObj = handlers[ i ];
- sel = handleObj.selector;
-
- if ( selMatch[ sel ] === undefined ) {
- selMatch[ sel ] = (
- handleObj.quick ? quickIs( cur, handleObj.quick ) : jqcur.is( sel )
- );
- }
- if ( selMatch[ sel ] ) {
- matches.push( handleObj );
- }
- }
- if ( matches.length ) {
- handlerQueue.push({ elem: cur, matches: matches });
- }
- }
- }
-
- // Add the remaining (directly-bound) handlers
- if ( handlers.length > delegateCount ) {
- handlerQueue.push({ elem: this, matches: handlers.slice( delegateCount ) });
- }
-
- // Run delegates first; they may want to stop propagation beneath us
- for ( i = 0; i < handlerQueue.length && !event.isPropagationStopped(); i++ ) {
- matched = handlerQueue[ i ];
- event.currentTarget = matched.elem;
-
- for ( j = 0; j < matched.matches.length && !event.isImmediatePropagationStopped(); j++ ) {
- handleObj = matched.matches[ j ];
-
- // Triggered event must either 1) be non-exclusive and have no namespace, or
- // 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace).
- if ( run_all || (!event.namespace && !handleObj.namespace) || event.namespace_re && event.namespace_re.test( handleObj.namespace ) ) {
-
- event.data = handleObj.data;
- event.handleObj = handleObj;
-
- ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler )
- .apply( matched.elem, args );
-
- if ( ret !== undefined ) {
- event.result = ret;
- if ( ret === false ) {
- event.preventDefault();
- event.stopPropagation();
- }
- }
- }
- }
- }
-
- return event.result;
- },
-
- // Includes some event props shared by KeyEvent and MouseEvent
- // *** attrChange attrName relatedNode srcElement are not normalized, non-W3C, deprecated, will be removed in 1.8 ***
- props: "attrChange attrName relatedNode srcElement altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),
-
- fixHooks: {},
-
- keyHooks: {
- props: "char charCode key keyCode".split(" "),
- filter: function( event, original ) {
-
- // Add which for key events
- if ( event.which == null ) {
- event.which = original.charCode != null ? original.charCode : original.keyCode;
- }
-
- return event;
- }
- },
-
- mouseHooks: {
- props: "button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),
- filter: function( event, original ) {
- var eventDoc, doc, body,
- button = original.button,
- fromElement = original.fromElement;
-
- // Calculate pageX/Y if missing and clientX/Y available
- if ( event.pageX == null && original.clientX != null ) {
- eventDoc = event.target.ownerDocument || document;
- doc = eventDoc.documentElement;
- body = eventDoc.body;
-
- event.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 );
- event.pageY = original.clientY + ( doc && doc.scrollTop || body && body.scrollTop || 0 ) - ( doc && doc.clientTop || body && body.clientTop || 0 );
- }
-
- // Add relatedTarget, if necessary
- if ( !event.relatedTarget && fromElement ) {
- event.relatedTarget = fromElement === event.target ? original.toElement : fromElement;
- }
-
- // Add which for click: 1 === left; 2 === middle; 3 === right
- // Note: button is not normalized, so don't use it
- if ( !event.which && button !== undefined ) {
- event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) );
- }
-
- return event;
- }
- },
-
- fix: function( event ) {
- if ( event[ jQuery.expando ] ) {
- return event;
- }
-
- // Create a writable copy of the event object and normalize some properties
- var i, prop,
- originalEvent = event,
- fixHook = jQuery.event.fixHooks[ event.type ] || {},
- copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props;
-
- event = jQuery.Event( originalEvent );
-
- for ( i = copy.length; i; ) {
- prop = copy[ --i ];
- event[ prop ] = originalEvent[ prop ];
- }
-
- // Fix target property, if necessary (#1925, IE 6/7/8 & Safari2)
- if ( !event.target ) {
- event.target = originalEvent.srcElement || document;
- }
-
- // Target should not be a text node (#504, Safari)
- if ( event.target.nodeType === 3 ) {
- event.target = event.target.parentNode;
- }
-
- // For mouse/key events; add metaKey if it's not there (#3368, IE6/7/8)
- if ( event.metaKey === undefined ) {
- event.metaKey = event.ctrlKey;
- }
-
- return fixHook.filter? fixHook.filter( event, originalEvent ) : event;
- },
-
- special: {
- ready: {
- // Make sure the ready event is setup
- setup: jQuery.bindReady
- },
-
- load: {
- // Prevent triggered image.load events from bubbling to window.load
- noBubble: true
- },
-
- focus: {
- delegateType: "focusin"
- },
- blur: {
- delegateType: "focusout"
- },
-
- beforeunload: {
- setup: function( data, namespaces, eventHandle ) {
- // We only want to do this special case on windows
- if ( jQuery.isWindow( this ) ) {
- this.onbeforeunload = eventHandle;
- }
- },
-
- teardown: function( namespaces, eventHandle ) {
- if ( this.onbeforeunload === eventHandle ) {
- this.onbeforeunload = null;
- }
- }
- }
- },
-
- simulate: function( type, elem, event, bubble ) {
- // Piggyback on a donor event to simulate a different one.
- // Fake originalEvent to avoid donor's stopPropagation, but if the
- // simulated event prevents default then we do the same on the donor.
- var e = jQuery.extend(
- new jQuery.Event(),
- event,
- { type: type,
- isSimulated: true,
- originalEvent: {}
- }
- );
- if ( bubble ) {
- jQuery.event.trigger( e, null, elem );
- } else {
- jQuery.event.dispatch.call( elem, e );
- }
- if ( e.isDefaultPrevented() ) {
- event.preventDefault();
- }
- }
-};
-
-// Some plugins are using, but it's undocumented/deprecated and will be removed.
-// The 1.7 special event interface should provide all the hooks needed now.
-jQuery.event.handle = jQuery.event.dispatch;
-
-jQuery.removeEvent = document.removeEventListener ?
- function( elem, type, handle ) {
- if ( elem.removeEventListener ) {
- elem.removeEventListener( type, handle, false );
- }
- } :
- function( elem, type, handle ) {
- if ( elem.detachEvent ) {
- elem.detachEvent( "on" + type, handle );
- }
- };
-
-jQuery.Event = function( src, props ) {
- // Allow instantiation without the 'new' keyword
- if ( !(this instanceof jQuery.Event) ) {
- return new jQuery.Event( src, props );
- }
-
- // Event object
- if ( src && src.type ) {
- this.originalEvent = src;
- this.type = src.type;
-
- // Events bubbling up the document may have been marked as prevented
- // by a handler lower down the tree; reflect the correct value.
- this.isDefaultPrevented = ( src.defaultPrevented || src.returnValue === false ||
- src.getPreventDefault && src.getPreventDefault() ) ? returnTrue : returnFalse;
-
- // Event type
- } else {
- this.type = src;
- }
-
- // Put explicitly provided properties onto the event object
- if ( props ) {
- jQuery.extend( this, props );
- }
-
- // Create a timestamp if incoming event doesn't have one
- this.timeStamp = src && src.timeStamp || jQuery.now();
-
- // Mark it as fixed
- this[ jQuery.expando ] = true;
-};
-
-function returnFalse() {
- return false;
-}
-function returnTrue() {
- return true;
-}
-
-// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding
-// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
-jQuery.Event.prototype = {
- preventDefault: function() {
- this.isDefaultPrevented = returnTrue;
-
- var e = this.originalEvent;
- if ( !e ) {
- return;
- }
-
- // if preventDefault exists run it on the original event
- if ( e.preventDefault ) {
- e.preventDefault();
-
- // otherwise set the returnValue property of the original event to false (IE)
- } else {
- e.returnValue = false;
- }
- },
- stopPropagation: function() {
- this.isPropagationStopped = returnTrue;
-
- var e = this.originalEvent;
- if ( !e ) {
- return;
- }
- // if stopPropagation exists run it on the original event
- if ( e.stopPropagation ) {
- e.stopPropagation();
- }
- // otherwise set the cancelBubble property of the original event to true (IE)
- e.cancelBubble = true;
- },
- stopImmediatePropagation: function() {
- this.isImmediatePropagationStopped = returnTrue;
- this.stopPropagation();
- },
- isDefaultPrevented: returnFalse,
- isPropagationStopped: returnFalse,
- isImmediatePropagationStopped: returnFalse
-};
-
-// Create mouseenter/leave events using mouseover/out and event-time checks
-jQuery.each({
- mouseenter: "mouseover",
- mouseleave: "mouseout"
-}, function( orig, fix ) {
- jQuery.event.special[ orig ] = {
- delegateType: fix,
- bindType: fix,
-
- handle: function( event ) {
- var target = this,
- related = event.relatedTarget,
- handleObj = event.handleObj,
- selector = handleObj.selector,
- ret;
-
- // For mousenter/leave call the handler if related is outside the target.
- // NB: No relatedTarget if the mouse left/entered the browser window
- if ( !related || (related !== target && !jQuery.contains( target, related )) ) {
- event.type = handleObj.origType;
- ret = handleObj.handler.apply( this, arguments );
- event.type = fix;
- }
- return ret;
- }
- };
-});
-
-// IE submit delegation
-if ( !jQuery.support.submitBubbles ) {
-
- jQuery.event.special.submit = {
- setup: function() {
- // Only need this for delegated form submit events
- if ( jQuery.nodeName( this, "form" ) ) {
- return false;
- }
-
- // Lazy-add a submit handler when a descendant form may potentially be submitted
- jQuery.event.add( this, "click._submit keypress._submit", function( e ) {
- // Node name check avoids a VML-related crash in IE (#9807)
- var elem = e.target,
- form = jQuery.nodeName( elem, "input" ) || jQuery.nodeName( elem, "button" ) ? elem.form : undefined;
- if ( form && !form._submit_attached ) {
- jQuery.event.add( form, "submit._submit", function( event ) {
- // If form was submitted by the user, bubble the event up the tree
- if ( this.parentNode && !event.isTrigger ) {
- jQuery.event.simulate( "submit", this.parentNode, event, true );
- }
- });
- form._submit_attached = true;
- }
- });
- // return undefined since we don't need an event listener
- },
-
- teardown: function() {
- // Only need this for delegated form submit events
- if ( jQuery.nodeName( this, "form" ) ) {
- return false;
- }
-
- // Remove delegated handlers; cleanData eventually reaps submit handlers attached above
- jQuery.event.remove( this, "._submit" );
- }
- };
-}
-
-// IE change delegation and checkbox/radio fix
-if ( !jQuery.support.changeBubbles ) {
-
- jQuery.event.special.change = {
-
- setup: function() {
-
- if ( rformElems.test( this.nodeName ) ) {
- // IE doesn't fire change on a check/radio until blur; trigger it on click
- // after a propertychange. Eat the blur-change in special.change.handle.
- // This still fires onchange a second time for check/radio after blur.
- if ( this.type === "checkbox" || this.type === "radio" ) {
- jQuery.event.add( this, "propertychange._change", function( event ) {
- if ( event.originalEvent.propertyName === "checked" ) {
- this._just_changed = true;
- }
- });
- jQuery.event.add( this, "click._change", function( event ) {
- if ( this._just_changed && !event.isTrigger ) {
- this._just_changed = false;
- jQuery.event.simulate( "change", this, event, true );
- }
- });
- }
- return false;
- }
- // Delegated event; lazy-add a change handler on descendant inputs
- jQuery.event.add( this, "beforeactivate._change", function( e ) {
- var elem = e.target;
-
- if ( rformElems.test( elem.nodeName ) && !elem._change_attached ) {
- jQuery.event.add( elem, "change._change", function( event ) {
- if ( this.parentNode && !event.isSimulated && !event.isTrigger ) {
- jQuery.event.simulate( "change", this.parentNode, event, true );
- }
- });
- elem._change_attached = true;
- }
- });
- },
-
- handle: function( event ) {
- var elem = event.target;
-
- // Swallow native change events from checkbox/radio, we already triggered them above
- if ( this !== elem || event.isSimulated || event.isTrigger || (elem.type !== "radio" && elem.type !== "checkbox") ) {
- return event.handleObj.handler.apply( this, arguments );
- }
- },
-
- teardown: function() {
- jQuery.event.remove( this, "._change" );
-
- return rformElems.test( this.nodeName );
- }
- };
-}
-
-// Create "bubbling" focus and blur events
-if ( !jQuery.support.focusinBubbles ) {
- jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) {
-
- // Attach a single capturing handler while someone wants focusin/focusout
- var attaches = 0,
- handler = function( event ) {
- jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true );
- };
-
- jQuery.event.special[ fix ] = {
- setup: function() {
- if ( attaches++ === 0 ) {
- document.addEventListener( orig, handler, true );
- }
- },
- teardown: function() {
- if ( --attaches === 0 ) {
- document.removeEventListener( orig, handler, true );
- }
- }
- };
- });
-}
-
-jQuery.fn.extend({
-
- on: function( types, selector, data, fn, /*INTERNAL*/ one ) {
- var origFn, type;
-
- // Types can be a map of types/handlers
- if ( typeof types === "object" ) {
- // ( types-Object, selector, data )
- if ( typeof selector !== "string" ) {
- // ( types-Object, data )
- data = selector;
- selector = undefined;
- }
- for ( type in types ) {
- this.on( type, selector, data, types[ type ], one );
- }
- return this;
- }
-
- if ( data == null && fn == null ) {
- // ( types, fn )
- fn = selector;
- data = selector = undefined;
- } else if ( fn == null ) {
- if ( typeof selector === "string" ) {
- // ( types, selector, fn )
- fn = data;
- data = undefined;
- } else {
- // ( types, data, fn )
- fn = data;
- data = selector;
- selector = undefined;
- }
- }
- if ( fn === false ) {
- fn = returnFalse;
- } else if ( !fn ) {
- return this;
- }
-
- if ( one === 1 ) {
- origFn = fn;
- fn = function( event ) {
- // Can use an empty set, since event contains the info
- jQuery().off( event );
- return origFn.apply( this, arguments );
- };
- // Use same guid so caller can remove using origFn
- fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ );
- }
- return this.each( function() {
- jQuery.event.add( this, types, fn, data, selector );
- });
- },
- one: function( types, selector, data, fn ) {
- return this.on.call( this, types, selector, data, fn, 1 );
- },
- off: function( types, selector, fn ) {
- if ( types && types.preventDefault && types.handleObj ) {
- // ( event ) dispatched jQuery.Event
- var handleObj = types.handleObj;
- jQuery( types.delegateTarget ).off(
- handleObj.namespace? handleObj.type + "." + handleObj.namespace : handleObj.type,
- handleObj.selector,
- handleObj.handler
- );
- return this;
- }
- if ( typeof types === "object" ) {
- // ( types-object [, selector] )
- for ( var type in types ) {
- this.off( type, selector, types[ type ] );
- }
- return this;
- }
- if ( selector === false || typeof selector === "function" ) {
- // ( types [, fn] )
- fn = selector;
- selector = undefined;
- }
- if ( fn === false ) {
- fn = returnFalse;
- }
- return this.each(function() {
- jQuery.event.remove( this, types, fn, selector );
- });
- },
-
- bind: function( types, data, fn ) {
- return this.on( types, null, data, fn );
- },
- unbind: function( types, fn ) {
- return this.off( types, null, fn );
- },
-
- live: function( types, data, fn ) {
- jQuery( this.context ).on( types, this.selector, data, fn );
- return this;
- },
- die: function( types, fn ) {
- jQuery( this.context ).off( types, this.selector || "**", fn );
- return this;
- },
-
- delegate: function( selector, types, data, fn ) {
- return this.on( types, selector, data, fn );
- },
- undelegate: function( selector, types, fn ) {
- // ( namespace ) or ( selector, types [, fn] )
- return arguments.length == 1? this.off( selector, "**" ) : this.off( types, selector, fn );
- },
-
- trigger: function( type, data ) {
- return this.each(function() {
- jQuery.event.trigger( type, data, this );
- });
- },
- triggerHandler: function( type, data ) {
- if ( this[0] ) {
- return jQuery.event.trigger( type, data, this[0], true );
- }
- },
-
- toggle: function( fn ) {
- // Save reference to arguments for access in closure
- var args = arguments,
- guid = fn.guid || jQuery.guid++,
- i = 0,
- toggler = function( event ) {
- // Figure out which function to execute
- var lastToggle = ( jQuery._data( this, "lastToggle" + fn.guid ) || 0 ) % i;
- jQuery._data( this, "lastToggle" + fn.guid, lastToggle + 1 );
-
- // Make sure that clicks stop
- event.preventDefault();
-
- // and execute the function
- return args[ lastToggle ].apply( this, arguments ) || false;
- };
-
- // link all the functions, so any of them can unbind this click handler
- toggler.guid = guid;
- while ( i < args.length ) {
- args[ i++ ].guid = guid;
- }
-
- return this.click( toggler );
- },
-
- hover: function( fnOver, fnOut ) {
- return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver );
- }
-});
-
-jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " +
- "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " +
- "change select submit keydown keypress keyup error contextmenu").split(" "), function( i, name ) {
-
- // Handle event binding
- jQuery.fn[ name ] = function( data, fn ) {
- if ( fn == null ) {
- fn = data;
- data = null;
- }
-
- return arguments.length > 0 ?
- this.on( name, null, data, fn ) :
- this.trigger( name );
- };
-
- if ( jQuery.attrFn ) {
- jQuery.attrFn[ name ] = true;
- }
-
- if ( rkeyEvent.test( name ) ) {
- jQuery.event.fixHooks[ name ] = jQuery.event.keyHooks;
- }
-
- if ( rmouseEvent.test( name ) ) {
- jQuery.event.fixHooks[ name ] = jQuery.event.mouseHooks;
- }
-});
-
-
-
-/*!
- * Sizzle CSS Selector Engine
- * Copyright 2011, The Dojo Foundation
- * Released under the MIT, BSD, and GPL Licenses.
- * More information: http://sizzlejs.com/
- */
-(function(){
-
-var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,
- expando = "sizcache" + (Math.random() + '').replace('.', ''),
- done = 0,
- toString = Object.prototype.toString,
- hasDuplicate = false,
- baseHasDuplicate = true,
- rBackslash = /\\/g,
- rReturn = /\r\n/g,
- rNonWord = /\W/;
-
-// Here we check if the JavaScript engine is using some sort of
-// optimization where it does not always call our comparision
-// function. If that is the case, discard the hasDuplicate value.
-// Thus far that includes Google Chrome.
-[0, 0].sort(function() {
- baseHasDuplicate = false;
- return 0;
-});
-
-var Sizzle = function( selector, context, results, seed ) {
- results = results || [];
- context = context || document;
-
- var origContext = context;
-
- if ( context.nodeType !== 1 && context.nodeType !== 9 ) {
- return [];
- }
-
- if ( !selector || typeof selector !== "string" ) {
- return results;
- }
-
- var m, set, checkSet, extra, ret, cur, pop, i,
- prune = true,
- contextXML = Sizzle.isXML( context ),
- parts = [],
- soFar = selector;
-
- // Reset the position of the chunker regexp (start from head)
- do {
- chunker.exec( "" );
- m = chunker.exec( soFar );
-
- if ( m ) {
- soFar = m[3];
-
- parts.push( m[1] );
-
- if ( m[2] ) {
- extra = m[3];
- break;
- }
- }
- } while ( m );
-
- if ( parts.length > 1 && origPOS.exec( selector ) ) {
-
- if ( parts.length === 2 && Expr.relative[ parts[0] ] ) {
- set = posProcess( parts[0] + parts[1], context, seed );
-
- } else {
- set = Expr.relative[ parts[0] ] ?
- [ context ] :
- Sizzle( parts.shift(), context );
-
- while ( parts.length ) {
- selector = parts.shift();
-
- if ( Expr.relative[ selector ] ) {
- selector += parts.shift();
- }
-
- set = posProcess( selector, set, seed );
- }
- }
-
- } else {
- // Take a shortcut and set the context if the root selector is an ID
- // (but not if it'll be faster if the inner selector is an ID)
- if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML &&
- Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) {
-
- ret = Sizzle.find( parts.shift(), context, contextXML );
- context = ret.expr ?
- Sizzle.filter( ret.expr, ret.set )[0] :
- ret.set[0];
- }
-
- if ( context ) {
- ret = seed ?
- { expr: parts.pop(), set: makeArray(seed) } :
- Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML );
-
- set = ret.expr ?
- Sizzle.filter( ret.expr, ret.set ) :
- ret.set;
-
- if ( parts.length > 0 ) {
- checkSet = makeArray( set );
-
- } else {
- prune = false;
- }
-
- while ( parts.length ) {
- cur = parts.pop();
- pop = cur;
-
- if ( !Expr.relative[ cur ] ) {
- cur = "";
- } else {
- pop = parts.pop();
- }
-
- if ( pop == null ) {
- pop = context;
- }
-
- Expr.relative[ cur ]( checkSet, pop, contextXML );
- }
-
- } else {
- checkSet = parts = [];
- }
- }
-
- if ( !checkSet ) {
- checkSet = set;
- }
-
- if ( !checkSet ) {
- Sizzle.error( cur || selector );
- }
-
- if ( toString.call(checkSet) === "[object Array]" ) {
- if ( !prune ) {
- results.push.apply( results, checkSet );
-
- } else if ( context && context.nodeType === 1 ) {
- for ( i = 0; checkSet[i] != null; i++ ) {
- if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) {
- results.push( set[i] );
- }
- }
-
- } else {
- for ( i = 0; checkSet[i] != null; i++ ) {
- if ( checkSet[i] && checkSet[i].nodeType === 1 ) {
- results.push( set[i] );
- }
- }
- }
-
- } else {
- makeArray( checkSet, results );
- }
-
- if ( extra ) {
- Sizzle( extra, origContext, results, seed );
- Sizzle.uniqueSort( results );
- }
-
- return results;
-};
-
-Sizzle.uniqueSort = function( results ) {
- if ( sortOrder ) {
- hasDuplicate = baseHasDuplicate;
- results.sort( sortOrder );
-
- if ( hasDuplicate ) {
- for ( var i = 1; i < results.length; i++ ) {
- if ( results[i] === results[ i - 1 ] ) {
- results.splice( i--, 1 );
- }
- }
- }
- }
-
- return results;
-};
-
-Sizzle.matches = function( expr, set ) {
- return Sizzle( expr, null, null, set );
-};
-
-Sizzle.matchesSelector = function( node, expr ) {
- return Sizzle( expr, null, null, [node] ).length > 0;
-};
-
-Sizzle.find = function( expr, context, isXML ) {
- var set, i, len, match, type, left;
-
- if ( !expr ) {
- return [];
- }
-
- for ( i = 0, len = Expr.order.length; i < len; i++ ) {
- type = Expr.order[i];
-
- if ( (match = Expr.leftMatch[ type ].exec( expr )) ) {
- left = match[1];
- match.splice( 1, 1 );
-
- if ( left.substr( left.length - 1 ) !== "\\" ) {
- match[1] = (match[1] || "").replace( rBackslash, "" );
- set = Expr.find[ type ]( match, context, isXML );
-
- if ( set != null ) {
- expr = expr.replace( Expr.match[ type ], "" );
- break;
- }
- }
- }
- }
-
- if ( !set ) {
- set = typeof context.getElementsByTagName !== "undefined" ?
- context.getElementsByTagName( "*" ) :
- [];
- }
-
- return { set: set, expr: expr };
-};
-
-Sizzle.filter = function( expr, set, inplace, not ) {
- var match, anyFound,
- type, found, item, filter, left,
- i, pass,
- old = expr,
- result = [],
- curLoop = set,
- isXMLFilter = set && set[0] && Sizzle.isXML( set[0] );
-
- while ( expr && set.length ) {
- for ( type in Expr.filter ) {
- if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) {
- filter = Expr.filter[ type ];
- left = match[1];
-
- anyFound = false;
-
- match.splice(1,1);
-
- if ( left.substr( left.length - 1 ) === "\\" ) {
- continue;
- }
-
- if ( curLoop === result ) {
- result = [];
- }
-
- if ( Expr.preFilter[ type ] ) {
- match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter );
-
- if ( !match ) {
- anyFound = found = true;
-
- } else if ( match === true ) {
- continue;
- }
- }
-
- if ( match ) {
- for ( i = 0; (item = curLoop[i]) != null; i++ ) {
- if ( item ) {
- found = filter( item, match, i, curLoop );
- pass = not ^ found;
-
- if ( inplace && found != null ) {
- if ( pass ) {
- anyFound = true;
-
- } else {
- curLoop[i] = false;
- }
-
- } else if ( pass ) {
- result.push( item );
- anyFound = true;
- }
- }
- }
- }
-
- if ( found !== undefined ) {
- if ( !inplace ) {
- curLoop = result;
- }
-
- expr = expr.replace( Expr.match[ type ], "" );
-
- if ( !anyFound ) {
- return [];
- }
-
- break;
- }
- }
- }
-
- // Improper expression
- if ( expr === old ) {
- if ( anyFound == null ) {
- Sizzle.error( expr );
-
- } else {
- break;
- }
- }
-
- old = expr;
- }
-
- return curLoop;
-};
-
-Sizzle.error = function( msg ) {
- throw new Error( "Syntax error, unrecognized expression: " + msg );
-};
-
-/**
- * Utility function for retreiving the text value of an array of DOM nodes
- * @param {Array|Element} elem
- */
-var getText = Sizzle.getText = function( elem ) {
- var i, node,
- nodeType = elem.nodeType,
- ret = "";
-
- if ( nodeType ) {
- if ( nodeType === 1 || nodeType === 9 ) {
- // Use textContent || innerText for elements
- if ( typeof elem.textContent === 'string' ) {
- return elem.textContent;
- } else if ( typeof elem.innerText === 'string' ) {
- // Replace IE's carriage returns
- return elem.innerText.replace( rReturn, '' );
- } else {
- // Traverse it's children
- for ( elem = elem.firstChild; elem; elem = elem.nextSibling) {
- ret += getText( elem );
- }
- }
- } else if ( nodeType === 3 || nodeType === 4 ) {
- return elem.nodeValue;
- }
- } else {
-
- // If no nodeType, this is expected to be an array
- for ( i = 0; (node = elem[i]); i++ ) {
- // Do not traverse comment nodes
- if ( node.nodeType !== 8 ) {
- ret += getText( node );
- }
- }
- }
- return ret;
-};
-
-var Expr = Sizzle.selectors = {
- order: [ "ID", "NAME", "TAG" ],
-
- match: {
- ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
- CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
- NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,
- ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/,
- TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,
- CHILD: /:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/,
- POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,
- PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/
- },
-
- leftMatch: {},
-
- attrMap: {
- "class": "className",
- "for": "htmlFor"
- },
-
- attrHandle: {
- href: function( elem ) {
- return elem.getAttribute( "href" );
- },
- type: function( elem ) {
- return elem.getAttribute( "type" );
- }
- },
-
- relative: {
- "+": function(checkSet, part){
- var isPartStr = typeof part === "string",
- isTag = isPartStr && !rNonWord.test( part ),
- isPartStrNotTag = isPartStr && !isTag;
-
- if ( isTag ) {
- part = part.toLowerCase();
- }
-
- for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) {
- if ( (elem = checkSet[i]) ) {
- while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {}
-
- checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ?
- elem || false :
- elem === part;
- }
- }
-
- if ( isPartStrNotTag ) {
- Sizzle.filter( part, checkSet, true );
- }
- },
-
- ">": function( checkSet, part ) {
- var elem,
- isPartStr = typeof part === "string",
- i = 0,
- l = checkSet.length;
-
- if ( isPartStr && !rNonWord.test( part ) ) {
- part = part.toLowerCase();
-
- for ( ; i < l; i++ ) {
- elem = checkSet[i];
-
- if ( elem ) {
- var parent = elem.parentNode;
- checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false;
- }
- }
-
- } else {
- for ( ; i < l; i++ ) {
- elem = checkSet[i];
-
- if ( elem ) {
- checkSet[i] = isPartStr ?
- elem.parentNode :
- elem.parentNode === part;
- }
- }
-
- if ( isPartStr ) {
- Sizzle.filter( part, checkSet, true );
- }
- }
- },
-
- "": function(checkSet, part, isXML){
- var nodeCheck,
- doneName = done++,
- checkFn = dirCheck;
-
- if ( typeof part === "string" && !rNonWord.test( part ) ) {
- part = part.toLowerCase();
- nodeCheck = part;
- checkFn = dirNodeCheck;
- }
-
- checkFn( "parentNode", part, doneName, checkSet, nodeCheck, isXML );
- },
-
- "~": function( checkSet, part, isXML ) {
- var nodeCheck,
- doneName = done++,
- checkFn = dirCheck;
-
- if ( typeof part === "string" && !rNonWord.test( part ) ) {
- part = part.toLowerCase();
- nodeCheck = part;
- checkFn = dirNodeCheck;
- }
-
- checkFn( "previousSibling", part, doneName, checkSet, nodeCheck, isXML );
- }
- },
-
- find: {
- ID: function( match, context, isXML ) {
- if ( typeof context.getElementById !== "undefined" && !isXML ) {
- var m = context.getElementById(match[1]);
- // Check parentNode to catch when Blackberry 4.6 returns
- // nodes that are no longer in the document #6963
- return m && m.parentNode ? [m] : [];
- }
- },
-
- NAME: function( match, context ) {
- if ( typeof context.getElementsByName !== "undefined" ) {
- var ret = [],
- results = context.getElementsByName( match[1] );
-
- for ( var i = 0, l = results.length; i < l; i++ ) {
- if ( results[i].getAttribute("name") === match[1] ) {
- ret.push( results[i] );
- }
- }
-
- return ret.length === 0 ? null : ret;
- }
- },
-
- TAG: function( match, context ) {
- if ( typeof context.getElementsByTagName !== "undefined" ) {
- return context.getElementsByTagName( match[1] );
- }
- }
- },
- preFilter: {
- CLASS: function( match, curLoop, inplace, result, not, isXML ) {
- match = " " + match[1].replace( rBackslash, "" ) + " ";
-
- if ( isXML ) {
- return match;
- }
-
- for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) {
- if ( elem ) {
- if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n\r]/g, " ").indexOf(match) >= 0) ) {
- if ( !inplace ) {
- result.push( elem );
- }
-
- } else if ( inplace ) {
- curLoop[i] = false;
- }
- }
- }
-
- return false;
- },
-
- ID: function( match ) {
- return match[1].replace( rBackslash, "" );
- },
-
- TAG: function( match, curLoop ) {
- return match[1].replace( rBackslash, "" ).toLowerCase();
- },
-
- CHILD: function( match ) {
- if ( match[1] === "nth" ) {
- if ( !match[2] ) {
- Sizzle.error( match[0] );
- }
-
- match[2] = match[2].replace(/^\+|\s*/g, '');
-
- // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6'
- var test = /(-?)(\d*)(?:n([+\-]?\d*))?/.exec(
- match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" ||
- !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]);
-
- // calculate the numbers (first)n+(last) including if they are negative
- match[2] = (test[1] + (test[2] || 1)) - 0;
- match[3] = test[3] - 0;
- }
- else if ( match[2] ) {
- Sizzle.error( match[0] );
- }
-
- // TODO: Move to normal caching system
- match[0] = done++;
-
- return match;
- },
-
- ATTR: function( match, curLoop, inplace, result, not, isXML ) {
- var name = match[1] = match[1].replace( rBackslash, "" );
-
- if ( !isXML && Expr.attrMap[name] ) {
- match[1] = Expr.attrMap[name];
- }
-
- // Handle if an un-quoted value was used
- match[4] = ( match[4] || match[5] || "" ).replace( rBackslash, "" );
-
- if ( match[2] === "~=" ) {
- match[4] = " " + match[4] + " ";
- }
-
- return match;
- },
-
- PSEUDO: function( match, curLoop, inplace, result, not ) {
- if ( match[1] === "not" ) {
- // If we're dealing with a complex expression, or a simple one
- if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) {
- match[3] = Sizzle(match[3], null, null, curLoop);
-
- } else {
- var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not);
-
- if ( !inplace ) {
- result.push.apply( result, ret );
- }
-
- return false;
- }
-
- } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) {
- return true;
- }
-
- return match;
- },
-
- POS: function( match ) {
- match.unshift( true );
-
- return match;
- }
- },
-
- filters: {
- enabled: function( elem ) {
- return elem.disabled === false && elem.type !== "hidden";
- },
-
- disabled: function( elem ) {
- return elem.disabled === true;
- },
-
- checked: function( elem ) {
- return elem.checked === true;
- },
-
- selected: function( elem ) {
- // Accessing this property makes selected-by-default
- // options in Safari work properly
- if ( elem.parentNode ) {
- elem.parentNode.selectedIndex;
- }
-
- return elem.selected === true;
- },
-
- parent: function( elem ) {
- return !!elem.firstChild;
- },
-
- empty: function( elem ) {
- return !elem.firstChild;
- },
-
- has: function( elem, i, match ) {
- return !!Sizzle( match[3], elem ).length;
- },
-
- header: function( elem ) {
- return (/h\d/i).test( elem.nodeName );
- },
-
- text: function( elem ) {
- var attr = elem.getAttribute( "type" ), type = elem.type;
- // IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc)
- // use getAttribute instead to test this case
- return elem.nodeName.toLowerCase() === "input" && "text" === type && ( attr === type || attr === null );
- },
-
- radio: function( elem ) {
- return elem.nodeName.toLowerCase() === "input" && "radio" === elem.type;
- },
-
- checkbox: function( elem ) {
- return elem.nodeName.toLowerCase() === "input" && "checkbox" === elem.type;
- },
-
- file: function( elem ) {
- return elem.nodeName.toLowerCase() === "input" && "file" === elem.type;
- },
-
- password: function( elem ) {
- return elem.nodeName.toLowerCase() === "input" && "password" === elem.type;
- },
-
- submit: function( elem ) {
- var name = elem.nodeName.toLowerCase();
- return (name === "input" || name === "button") && "submit" === elem.type;
- },
-
- image: function( elem ) {
- return elem.nodeName.toLowerCase() === "input" && "image" === elem.type;
- },
-
- reset: function( elem ) {
- var name = elem.nodeName.toLowerCase();
- return (name === "input" || name === "button") && "reset" === elem.type;
- },
-
- button: function( elem ) {
- var name = elem.nodeName.toLowerCase();
- return name === "input" && "button" === elem.type || name === "button";
- },
-
- input: function( elem ) {
- return (/input|select|textarea|button/i).test( elem.nodeName );
- },
-
- focus: function( elem ) {
- return elem === elem.ownerDocument.activeElement;
- }
- },
- setFilters: {
- first: function( elem, i ) {
- return i === 0;
- },
-
- last: function( elem, i, match, array ) {
- return i === array.length - 1;
- },
-
- even: function( elem, i ) {
- return i % 2 === 0;
- },
-
- odd: function( elem, i ) {
- return i % 2 === 1;
- },
-
- lt: function( elem, i, match ) {
- return i < match[3] - 0;
- },
-
- gt: function( elem, i, match ) {
- return i > match[3] - 0;
- },
-
- nth: function( elem, i, match ) {
- return match[3] - 0 === i;
- },
-
- eq: function( elem, i, match ) {
- return match[3] - 0 === i;
- }
- },
- filter: {
- PSEUDO: function( elem, match, i, array ) {
- var name = match[1],
- filter = Expr.filters[ name ];
-
- if ( filter ) {
- return filter( elem, i, match, array );
-
- } else if ( name === "contains" ) {
- return (elem.textContent || elem.innerText || getText([ elem ]) || "").indexOf(match[3]) >= 0;
-
- } else if ( name === "not" ) {
- var not = match[3];
-
- for ( var j = 0, l = not.length; j < l; j++ ) {
- if ( not[j] === elem ) {
- return false;
- }
- }
-
- return true;
-
- } else {
- Sizzle.error( name );
- }
- },
-
- CHILD: function( elem, match ) {
- var first, last,
- doneName, parent, cache,
- count, diff,
- type = match[1],
- node = elem;
-
- switch ( type ) {
- case "only":
- case "first":
- while ( (node = node.previousSibling) ) {
- if ( node.nodeType === 1 ) {
- return false;
- }
- }
-
- if ( type === "first" ) {
- return true;
- }
-
- node = elem;
-
- case "last":
- while ( (node = node.nextSibling) ) {
- if ( node.nodeType === 1 ) {
- return false;
- }
- }
-
- return true;
-
- case "nth":
- first = match[2];
- last = match[3];
-
- if ( first === 1 && last === 0 ) {
- return true;
- }
-
- doneName = match[0];
- parent = elem.parentNode;
-
- if ( parent && (parent[ expando ] !== doneName || !elem.nodeIndex) ) {
- count = 0;
-
- for ( node = parent.firstChild; node; node = node.nextSibling ) {
- if ( node.nodeType === 1 ) {
- node.nodeIndex = ++count;
- }
- }
-
- parent[ expando ] = doneName;
- }
-
- diff = elem.nodeIndex - last;
-
- if ( first === 0 ) {
- return diff === 0;
-
- } else {
- return ( diff % first === 0 && diff / first >= 0 );
- }
- }
- },
-
- ID: function( elem, match ) {
- return elem.nodeType === 1 && elem.getAttribute("id") === match;
- },
-
- TAG: function( elem, match ) {
- return (match === "*" && elem.nodeType === 1) || !!elem.nodeName && elem.nodeName.toLowerCase() === match;
- },
-
- CLASS: function( elem, match ) {
- return (" " + (elem.className || elem.getAttribute("class")) + " ")
- .indexOf( match ) > -1;
- },
-
- ATTR: function( elem, match ) {
- var name = match[1],
- result = Sizzle.attr ?
- Sizzle.attr( elem, name ) :
- Expr.attrHandle[ name ] ?
- Expr.attrHandle[ name ]( elem ) :
- elem[ name ] != null ?
- elem[ name ] :
- elem.getAttribute( name ),
- value = result + "",
- type = match[2],
- check = match[4];
-
- return result == null ?
- type === "!=" :
- !type && Sizzle.attr ?
- result != null :
- type === "=" ?
- value === check :
- type === "*=" ?
- value.indexOf(check) >= 0 :
- type === "~=" ?
- (" " + value + " ").indexOf(check) >= 0 :
- !check ?
- value && result !== false :
- type === "!=" ?
- value !== check :
- type === "^=" ?
- value.indexOf(check) === 0 :
- type === "$=" ?
- value.substr(value.length - check.length) === check :
- type === "|=" ?
- value === check || value.substr(0, check.length + 1) === check + "-" :
- false;
- },
-
- POS: function( elem, match, i, array ) {
- var name = match[2],
- filter = Expr.setFilters[ name ];
-
- if ( filter ) {
- return filter( elem, i, match, array );
- }
- }
- }
-};
-
-var origPOS = Expr.match.POS,
- fescape = function(all, num){
- return "\\" + (num - 0 + 1);
- };
-
-for ( var type in Expr.match ) {
- Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) );
- Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) );
-}
-
-var makeArray = function( array, results ) {
- array = Array.prototype.slice.call( array, 0 );
-
- if ( results ) {
- results.push.apply( results, array );
- return results;
- }
-
- return array;
-};
-
-// Perform a simple check to determine if the browser is capable of
-// converting a NodeList to an array using builtin methods.
-// Also verifies that the returned array holds DOM nodes
-// (which is not the case in the Blackberry browser)
-try {
- Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType;
-
-// Provide a fallback method if it does not work
-} catch( e ) {
- makeArray = function( array, results ) {
- var i = 0,
- ret = results || [];
-
- if ( toString.call(array) === "[object Array]" ) {
- Array.prototype.push.apply( ret, array );
-
- } else {
- if ( typeof array.length === "number" ) {
- for ( var l = array.length; i < l; i++ ) {
- ret.push( array[i] );
- }
-
- } else {
- for ( ; array[i]; i++ ) {
- ret.push( array[i] );
- }
- }
- }
-
- return ret;
- };
-}
-
-var sortOrder, siblingCheck;
-
-if ( document.documentElement.compareDocumentPosition ) {
- sortOrder = function( a, b ) {
- if ( a === b ) {
- hasDuplicate = true;
- return 0;
- }
-
- if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) {
- return a.compareDocumentPosition ? -1 : 1;
- }
-
- return a.compareDocumentPosition(b) & 4 ? -1 : 1;
- };
-
-} else {
- sortOrder = function( a, b ) {
- // The nodes are identical, we can exit early
- if ( a === b ) {
- hasDuplicate = true;
- return 0;
-
- // Fallback to using sourceIndex (in IE) if it's available on both nodes
- } else if ( a.sourceIndex && b.sourceIndex ) {
- return a.sourceIndex - b.sourceIndex;
- }
-
- var al, bl,
- ap = [],
- bp = [],
- aup = a.parentNode,
- bup = b.parentNode,
- cur = aup;
-
- // If the nodes are siblings (or identical) we can do a quick check
- if ( aup === bup ) {
- return siblingCheck( a, b );
-
- // If no parents were found then the nodes are disconnected
- } else if ( !aup ) {
- return -1;
-
- } else if ( !bup ) {
- return 1;
- }
-
- // Otherwise they're somewhere else in the tree so we need
- // to build up a full list of the parentNodes for comparison
- while ( cur ) {
- ap.unshift( cur );
- cur = cur.parentNode;
- }
-
- cur = bup;
-
- while ( cur ) {
- bp.unshift( cur );
- cur = cur.parentNode;
- }
-
- al = ap.length;
- bl = bp.length;
-
- // Start walking down the tree looking for a discrepancy
- for ( var i = 0; i < al && i < bl; i++ ) {
- if ( ap[i] !== bp[i] ) {
- return siblingCheck( ap[i], bp[i] );
- }
- }
-
- // We ended someplace up the tree so do a sibling check
- return i === al ?
- siblingCheck( a, bp[i], -1 ) :
- siblingCheck( ap[i], b, 1 );
- };
-
- siblingCheck = function( a, b, ret ) {
- if ( a === b ) {
- return ret;
- }
-
- var cur = a.nextSibling;
-
- while ( cur ) {
- if ( cur === b ) {
- return -1;
- }
-
- cur = cur.nextSibling;
- }
-
- return 1;
- };
-}
-
-// Check to see if the browser returns elements by name when
-// querying by getElementById (and provide a workaround)
-(function(){
- // We're going to inject a fake input element with a specified name
- var form = document.createElement("div"),
- id = "script" + (new Date()).getTime(),
- root = document.documentElement;
-
- form.innerHTML = "<a name='" + id + "'/>";
-
- // Inject it into the root element, check its status, and remove it quickly
- root.insertBefore( form, root.firstChild );
-
- // The workaround has to do additional checks after a getElementById
- // Which slows things down for other browsers (hence the branching)
- if ( document.getElementById( id ) ) {
- Expr.find.ID = function( match, context, isXML ) {
- if ( typeof context.getElementById !== "undefined" && !isXML ) {
- var m = context.getElementById(match[1]);
-
- return m ?
- m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ?
- [m] :
- undefined :
- [];
- }
- };
-
- Expr.filter.ID = function( elem, match ) {
- var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id");
-
- return elem.nodeType === 1 && node && node.nodeValue === match;
- };
- }
-
- root.removeChild( form );
-
- // release memory in IE
- root = form = null;
-})();
-
-(function(){
- // Check to see if the browser returns only elements
- // when doing getElementsByTagName("*")
-
- // Create a fake element
- var div = document.createElement("div");
- div.appendChild( document.createComment("") );
-
- // Make sure no comments are found
- if ( div.getElementsByTagName("*").length > 0 ) {
- Expr.find.TAG = function( match, context ) {
- var results = context.getElementsByTagName( match[1] );
-
- // Filter out possible comments
- if ( match[1] === "*" ) {
- var tmp = [];
-
- for ( var i = 0; results[i]; i++ ) {
- if ( results[i].nodeType === 1 ) {
- tmp.push( results[i] );
- }
- }
-
- results = tmp;
- }
-
- return results;
- };
- }
-
- // Check to see if an attribute returns normalized href attributes
- div.innerHTML = "<a href='#'></a>";
-
- if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" &&
- div.firstChild.getAttribute("href") !== "#" ) {
-
- Expr.attrHandle.href = function( elem ) {
- return elem.getAttribute( "href", 2 );
- };
- }
-
- // release memory in IE
- div = null;
-})();
-
-if ( document.querySelectorAll ) {
- (function(){
- var oldSizzle = Sizzle,
- div = document.createElement("div"),
- id = "__sizzle__";
-
- div.innerHTML = "<p class='TEST'></p>";
-
- // Safari can't handle uppercase or unicode characters when
- // in quirks mode.
- if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) {
- return;
- }
-
- Sizzle = function( query, context, extra, seed ) {
- context = context || document;
-
- // Only use querySelectorAll on non-XML documents
- // (ID selectors don't work in non-HTML documents)
- if ( !seed && !Sizzle.isXML(context) ) {
- // See if we find a selector to speed up
- var match = /^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec( query );
-
- if ( match && (context.nodeType === 1 || context.nodeType === 9) ) {
- // Speed-up: Sizzle("TAG")
- if ( match[1] ) {
- return makeArray( context.getElementsByTagName( query ), extra );
-
- // Speed-up: Sizzle(".CLASS")
- } else if ( match[2] && Expr.find.CLASS && context.getElementsByClassName ) {
- return makeArray( context.getElementsByClassName( match[2] ), extra );
- }
- }
-
- if ( context.nodeType === 9 ) {
- // Speed-up: Sizzle("body")
- // The body element only exists once, optimize finding it
- if ( query === "body" && context.body ) {
- return makeArray( [ context.body ], extra );
-
- // Speed-up: Sizzle("#ID")
- } else if ( match && match[3] ) {
- var elem = context.getElementById( match[3] );
-
- // Check parentNode to catch when Blackberry 4.6 returns
- // nodes that are no longer in the document #6963
- if ( elem && elem.parentNode ) {
- // Handle the case where IE and Opera return items
- // by name instead of ID
- if ( elem.id === match[3] ) {
- return makeArray( [ elem ], extra );
- }
-
- } else {
- return makeArray( [], extra );
- }
- }
-
- try {
- return makeArray( context.querySelectorAll(query), extra );
- } catch(qsaError) {}
-
- // qSA works strangely on Element-rooted queries
- // We can work around this by specifying an extra ID on the root
- // and working up from there (Thanks to Andrew Dupont for the technique)
- // IE 8 doesn't work on object elements
- } else if ( context.nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) {
- var oldContext = context,
- old = context.getAttribute( "id" ),
- nid = old || id,
- hasParent = context.parentNode,
- relativeHierarchySelector = /^\s*[+~]/.test( query );
-
- if ( !old ) {
- context.setAttribute( "id", nid );
- } else {
- nid = nid.replace( /'/g, "\\$&" );
- }
- if ( relativeHierarchySelector && hasParent ) {
- context = context.parentNode;
- }
-
- try {
- if ( !relativeHierarchySelector || hasParent ) {
- return makeArray( context.querySelectorAll( "[id='" + nid + "'] " + query ), extra );
- }
-
- } catch(pseudoError) {
- } finally {
- if ( !old ) {
- oldContext.removeAttribute( "id" );
- }
- }
- }
- }
-
- return oldSizzle(query, context, extra, seed);
- };
-
- for ( var prop in oldSizzle ) {
- Sizzle[ prop ] = oldSizzle[ prop ];
- }
-
- // release memory in IE
- div = null;
- })();
-}
-
-(function(){
- var html = document.documentElement,
- matches = html.matchesSelector || html.mozMatchesSelector || html.webkitMatchesSelector || html.msMatchesSelector;
-
- if ( matches ) {
- // Check to see if it's possible to do matchesSelector
- // on a disconnected node (IE 9 fails this)
- var disconnectedMatch = !matches.call( document.createElement( "div" ), "div" ),
- pseudoWorks = false;
-
- try {
- // This should fail with an exception
- // Gecko does not error, returns false instead
- matches.call( document.documentElement, "[test!='']:sizzle" );
-
- } catch( pseudoError ) {
- pseudoWorks = true;
- }
-
- Sizzle.matchesSelector = function( node, expr ) {
- // Make sure that attribute selectors are quoted
- expr = expr.replace(/\=\s*([^'"\]]*)\s*\]/g, "='$1']");
-
- if ( !Sizzle.isXML( node ) ) {
- try {
- if ( pseudoWorks || !Expr.match.PSEUDO.test( expr ) && !/!=/.test( expr ) ) {
- var ret = matches.call( node, expr );
-
- // IE 9's matchesSelector returns false on disconnected nodes
- if ( ret || !disconnectedMatch ||
- // As well, disconnected nodes are said to be in a document
- // fragment in IE 9, so check for that
- node.document && node.document.nodeType !== 11 ) {
- return ret;
- }
- }
- } catch(e) {}
- }
-
- return Sizzle(expr, null, null, [node]).length > 0;
- };
- }
-})();
-
-(function(){
- var div = document.createElement("div");
-
- div.innerHTML = "<div class='test e'></div><div class='test'></div>";
-
- // Opera can't find a second classname (in 9.6)
- // Also, make sure that getElementsByClassName actually exists
- if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) {
- return;
- }
-
- // Safari caches class attributes, doesn't catch changes (in 3.2)
- div.lastChild.className = "e";
-
- if ( div.getElementsByClassName("e").length === 1 ) {
- return;
- }
-
- Expr.order.splice(1, 0, "CLASS");
- Expr.find.CLASS = function( match, context, isXML ) {
- if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) {
- return context.getElementsByClassName(match[1]);
- }
- };
-
- // release memory in IE
- div = null;
-})();
-
-function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
- for ( var i = 0, l = checkSet.length; i < l; i++ ) {
- var elem = checkSet[i];
-
- if ( elem ) {
- var match = false;
-
- elem = elem[dir];
-
- while ( elem ) {
- if ( elem[ expando ] === doneName ) {
- match = checkSet[elem.sizset];
- break;
- }
-
- if ( elem.nodeType === 1 && !isXML ){
- elem[ expando ] = doneName;
- elem.sizset = i;
- }
-
- if ( elem.nodeName.toLowerCase() === cur ) {
- match = elem;
- break;
- }
-
- elem = elem[dir];
- }
-
- checkSet[i] = match;
- }
- }
-}
-
-function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
- for ( var i = 0, l = checkSet.length; i < l; i++ ) {
- var elem = checkSet[i];
-
- if ( elem ) {
- var match = false;
-
- elem = elem[dir];
-
- while ( elem ) {
- if ( elem[ expando ] === doneName ) {
- match = checkSet[elem.sizset];
- break;
- }
-
- if ( elem.nodeType === 1 ) {
- if ( !isXML ) {
- elem[ expando ] = doneName;
- elem.sizset = i;
- }
-
- if ( typeof cur !== "string" ) {
- if ( elem === cur ) {
- match = true;
- break;
- }
-
- } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) {
- match = elem;
- break;
- }
- }
-
- elem = elem[dir];
- }
-
- checkSet[i] = match;
- }
- }
-}
-
-if ( document.documentElement.contains ) {
- Sizzle.contains = function( a, b ) {
- return a !== b && (a.contains ? a.contains(b) : true);
- };
-
-} else if ( document.documentElement.compareDocumentPosition ) {
- Sizzle.contains = function( a, b ) {
- return !!(a.compareDocumentPosition(b) & 16);
- };
-
-} else {
- Sizzle.contains = function() {
- return false;
- };
-}
-
-Sizzle.isXML = function( elem ) {
- // documentElement is verified for cases where it doesn't yet exist
- // (such as loading iframes in IE - #4833)
- var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement;
-
- return documentElement ? documentElement.nodeName !== "HTML" : false;
-};
-
-var posProcess = function( selector, context, seed ) {
- var match,
- tmpSet = [],
- later = "",
- root = context.nodeType ? [context] : context;
-
- // Position selectors must be done after the filter
- // And so must :not(positional) so we move all PSEUDOs to the end
- while ( (match = Expr.match.PSEUDO.exec( selector )) ) {
- later += match[0];
- selector = selector.replace( Expr.match.PSEUDO, "" );
- }
-
- selector = Expr.relative[selector] ? selector + "*" : selector;
-
- for ( var i = 0, l = root.length; i < l; i++ ) {
- Sizzle( selector, root[i], tmpSet, seed );
- }
-
- return Sizzle.filter( later, tmpSet );
-};
-
-// EXPOSE
-// Override sizzle attribute retrieval
-Sizzle.attr = jQuery.attr;
-Sizzle.selectors.attrMap = {};
-jQuery.find = Sizzle;
-jQuery.expr = Sizzle.selectors;
-jQuery.expr[":"] = jQuery.expr.filters;
-jQuery.unique = Sizzle.uniqueSort;
-jQuery.text = Sizzle.getText;
-jQuery.isXMLDoc = Sizzle.isXML;
-jQuery.contains = Sizzle.contains;
-
-
-})();
-
-
-var runtil = /Until$/,
- rparentsprev = /^(?:parents|prevUntil|prevAll)/,
- // Note: This RegExp should be improved, or likely pulled from Sizzle
- rmultiselector = /,/,
- isSimple = /^.[^:#\[\.,]*$/,
- slice = Array.prototype.slice,
- POS = jQuery.expr.match.POS,
- // methods guaranteed to produce a unique set when starting from a unique set
- guaranteedUnique = {
- children: true,
- contents: true,
- next: true,
- prev: true
- };
-
-jQuery.fn.extend({
- find: function( selector ) {
- var self = this,
- i, l;
-
- if ( typeof selector !== "string" ) {
- return jQuery( selector ).filter(function() {
- for ( i = 0, l = self.length; i < l; i++ ) {
- if ( jQuery.contains( self[ i ], this ) ) {
- return true;
- }
- }
- });
- }
-
- var ret = this.pushStack( "", "find", selector ),
- length, n, r;
-
- for ( i = 0, l = this.length; i < l; i++ ) {
- length = ret.length;
- jQuery.find( selector, this[i], ret );
-
- if ( i > 0 ) {
- // Make sure that the results are unique
- for ( n = length; n < ret.length; n++ ) {
- for ( r = 0; r < length; r++ ) {
- if ( ret[r] === ret[n] ) {
- ret.splice(n--, 1);
- break;
- }
- }
- }
- }
- }
-
- return ret;
- },
-
- has: function( target ) {
- var targets = jQuery( target );
- return this.filter(function() {
- for ( var i = 0, l = targets.length; i < l; i++ ) {
- if ( jQuery.contains( this, targets[i] ) ) {
- return true;
- }
- }
- });
- },
-
- not: function( selector ) {
- return this.pushStack( winnow(this, selector, false), "not", selector);
- },
-
- filter: function( selector ) {
- return this.pushStack( winnow(this, selector, true), "filter", selector );
- },
-
- is: function( selector ) {
- return !!selector && (
- typeof selector === "string" ?
- // If this is a positional selector, check membership in the returned set
- // so $("p:first").is("p:last") won't return true for a doc with two "p".
- POS.test( selector ) ?
- jQuery( selector, this.context ).index( this[0] ) >= 0 :
- jQuery.filter( selector, this ).length > 0 :
- this.filter( selector ).length > 0 );
- },
-
- closest: function( selectors, context ) {
- var ret = [], i, l, cur = this[0];
-
- // Array (deprecated as of jQuery 1.7)
- if ( jQuery.isArray( selectors ) ) {
- var level = 1;
-
- while ( cur && cur.ownerDocument && cur !== context ) {
- for ( i = 0; i < selectors.length; i++ ) {
-
- if ( jQuery( cur ).is( selectors[ i ] ) ) {
- ret.push({ selector: selectors[ i ], elem: cur, level: level });
- }
- }
-
- cur = cur.parentNode;
- level++;
- }
-
- return ret;
- }
-
- // String
- var pos = POS.test( selectors ) || typeof selectors !== "string" ?
- jQuery( selectors, context || this.context ) :
- 0;
-
- for ( i = 0, l = this.length; i < l; i++ ) {
- cur = this[i];
-
- while ( cur ) {
- if ( pos ? pos.index(cur) > -1 : jQuery.find.matchesSelector(cur, selectors) ) {
- ret.push( cur );
- break;
-
- } else {
- cur = cur.parentNode;
- if ( !cur || !cur.ownerDocument || cur === context || cur.nodeType === 11 ) {
- break;
- }
- }
- }
- }
-
- ret = ret.length > 1 ? jQuery.unique( ret ) : ret;
-
- return this.pushStack( ret, "closest", selectors );
- },
-
- // Determine the position of an element within
- // the matched set of elements
- index: function( elem ) {
-
- // No argument, return index in parent
- if ( !elem ) {
- return ( this[0] && this[0].parentNode ) ? this.prevAll().length : -1;
- }
-
- // index in selector
- if ( typeof elem === "string" ) {
- return jQuery.inArray( this[0], jQuery( elem ) );
- }
-
- // Locate the position of the desired element
- return jQuery.inArray(
- // If it receives a jQuery object, the first element is used
- elem.jquery ? elem[0] : elem, this );
- },
-
- add: function( selector, context ) {
- var set = typeof selector === "string" ?
- jQuery( selector, context ) :
- jQuery.makeArray( selector && selector.nodeType ? [ selector ] : selector ),
- all = jQuery.merge( this.get(), set );
-
- return this.pushStack( isDisconnected( set[0] ) || isDisconnected( all[0] ) ?
- all :
- jQuery.unique( all ) );
- },
-
- andSelf: function() {
- return this.add( this.prevObject );
- }
-});
-
-// A painfully simple check to see if an element is disconnected
-// from a document (should be improved, where feasible).
-function isDisconnected( node ) {
- return !node || !node.parentNode || node.parentNode.nodeType === 11;
-}
-
-jQuery.each({
- parent: function( elem ) {
- var parent = elem.parentNode;
- return parent && parent.nodeType !== 11 ? parent : null;
- },
- parents: function( elem ) {
- return jQuery.dir( elem, "parentNode" );
- },
- parentsUntil: function( elem, i, until ) {
- return jQuery.dir( elem, "parentNode", until );
- },
- next: function( elem ) {
- return jQuery.nth( elem, 2, "nextSibling" );
- },
- prev: function( elem ) {
- return jQuery.nth( elem, 2, "previousSibling" );
- },
- nextAll: function( elem ) {
- return jQuery.dir( elem, "nextSibling" );
- },
- prevAll: function( elem ) {
- return jQuery.dir( elem, "previousSibling" );
- },
- nextUntil: function( elem, i, until ) {
- return jQuery.dir( elem, "nextSibling", until );
- },
- prevUntil: function( elem, i, until ) {
- return jQuery.dir( elem, "previousSibling", until );
- },
- siblings: function( elem ) {
- return jQuery.sibling( elem.parentNode.firstChild, elem );
- },
- children: function( elem ) {
- return jQuery.sibling( elem.firstChild );
- },
- contents: function( elem ) {
- return jQuery.nodeName( elem, "iframe" ) ?
- elem.contentDocument || elem.contentWindow.document :
- jQuery.makeArray( elem.childNodes );
- }
-}, function( name, fn ) {
- jQuery.fn[ name ] = function( until, selector ) {
- var ret = jQuery.map( this, fn, until );
-
- if ( !runtil.test( name ) ) {
- selector = until;
- }
-
- if ( selector && typeof selector === "string" ) {
- ret = jQuery.filter( selector, ret );
- }
-
- ret = this.length > 1 && !guaranteedUnique[ name ] ? jQuery.unique( ret ) : ret;
-
- if ( (this.length > 1 || rmultiselector.test( selector )) && rparentsprev.test( name ) ) {
- ret = ret.reverse();
- }
-
- return this.pushStack( ret, name, slice.call( arguments ).join(",") );
- };
-});
-
-jQuery.extend({
- filter: function( expr, elems, not ) {
- if ( not ) {
- expr = ":not(" + expr + ")";
- }
-
- return elems.length === 1 ?
- jQuery.find.matchesSelector(elems[0], expr) ? [ elems[0] ] : [] :
- jQuery.find.matches(expr, elems);
- },
-
- dir: function( elem, dir, until ) {
- var matched = [],
- cur = elem[ dir ];
-
- while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) {
- if ( cur.nodeType === 1 ) {
- matched.push( cur );
- }
- cur = cur[dir];
- }
- return matched;
- },
-
- nth: function( cur, result, dir, elem ) {
- result = result || 1;
- var num = 0;
-
- for ( ; cur; cur = cur[dir] ) {
- if ( cur.nodeType === 1 && ++num === result ) {
- break;
- }
- }
-
- return cur;
- },
-
- sibling: function( n, elem ) {
- var r = [];
-
- for ( ; n; n = n.nextSibling ) {
- if ( n.nodeType === 1 && n !== elem ) {
- r.push( n );
- }
- }
-
- return r;
- }
-});
-
-// Implement the identical functionality for filter and not
-function winnow( elements, qualifier, keep ) {
-
- // Can't pass null or undefined to indexOf in Firefox 4
- // Set to 0 to skip string check
- qualifier = qualifier || 0;
-
- if ( jQuery.isFunction( qualifier ) ) {
- return jQuery.grep(elements, function( elem, i ) {
- var retVal = !!qualifier.call( elem, i, elem );
- return retVal === keep;
- });
-
- } else if ( qualifier.nodeType ) {
- return jQuery.grep(elements, function( elem, i ) {
- return ( elem === qualifier ) === keep;
- });
-
- } else if ( typeof qualifier === "string" ) {
- var filtered = jQuery.grep(elements, function( elem ) {
- return elem.nodeType === 1;
- });
-
- if ( isSimple.test( qualifier ) ) {
- return jQuery.filter(qualifier, filtered, !keep);
- } else {
- qualifier = jQuery.filter( qualifier, filtered );
- }
- }
-
- return jQuery.grep(elements, function( elem, i ) {
- return ( jQuery.inArray( elem, qualifier ) >= 0 ) === keep;
- });
-}
-
-
-
-
-function createSafeFragment( document ) {
- var list = nodeNames.split( "|" ),
- safeFrag = document.createDocumentFragment();
-
- if ( safeFrag.createElement ) {
- while ( list.length ) {
- safeFrag.createElement(
- list.pop()
- );
- }
- }
- return safeFrag;
-}
-
-var nodeNames = "abbr|article|aside|audio|canvas|datalist|details|figcaption|figure|footer|" +
- "header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",
- rinlinejQuery = / jQuery\d+="(?:\d+|null)"/g,
- rleadingWhitespace = /^\s+/,
- rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,
- rtagName = /<([\w:]+)/,
- rtbody = /<tbody/i,
- rhtml = /<|&#?\w+;/,
- rnoInnerhtml = /<(?:script|style)/i,
- rnocache = /<(?:script|object|embed|option|style)/i,
- rnoshimcache = new RegExp("<(?:" + nodeNames + ")", "i"),
- // checked="checked" or checked
- rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i,
- rscriptType = /\/(java|ecma)script/i,
- rcleanScript = /^\s*<!(?:\[CDATA\[|\-\-)/,
- wrapMap = {
- option: [ 1, "<select multiple='multiple'>", "</select>" ],
- legend: [ 1, "<fieldset>", "</fieldset>" ],
- thead: [ 1, "<table>", "</table>" ],
- tr: [ 2, "<table><tbody>", "</tbody></table>" ],
- td: [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ],
- col: [ 2, "<table><tbody></tbody><colgroup>", "</colgroup></table>" ],
- area: [ 1, "<map>", "</map>" ],
- _default: [ 0, "", "" ]
- },
- safeFragment = createSafeFragment( document );
-
-wrapMap.optgroup = wrapMap.option;
-wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;
-wrapMap.th = wrapMap.td;
-
-// IE can't serialize <link> and <script> tags normally
-if ( !jQuery.support.htmlSerialize ) {
- wrapMap._default = [ 1, "div<div>", "</div>" ];
-}
-
-jQuery.fn.extend({
- text: function( text ) {
- if ( jQuery.isFunction(text) ) {
- return this.each(function(i) {
- var self = jQuery( this );
-
- self.text( text.call(this, i, self.text()) );
- });
- }
-
- if ( typeof text !== "object" && text !== undefined ) {
- return this.empty().append( (this[0] && this[0].ownerDocument || document).createTextNode( text ) );
- }
-
- return jQuery.text( this );
- },
-
- wrapAll: function( html ) {
- if ( jQuery.isFunction( html ) ) {
- return this.each(function(i) {
- jQuery(this).wrapAll( html.call(this, i) );
- });
- }
-
- if ( this[0] ) {
- // The elements to wrap the target around
- var wrap = jQuery( html, this[0].ownerDocument ).eq(0).clone(true);
-
- if ( this[0].parentNode ) {
- wrap.insertBefore( this[0] );
- }
-
- wrap.map(function() {
- var elem = this;
-
- while ( elem.firstChild && elem.firstChild.nodeType === 1 ) {
- elem = elem.firstChild;
- }
-
- return elem;
- }).append( this );
- }
-
- return this;
- },
-
- wrapInner: function( html ) {
- if ( jQuery.isFunction( html ) ) {
- return this.each(function(i) {
- jQuery(this).wrapInner( html.call(this, i) );
- });
- }
-
- return this.each(function() {
- var self = jQuery( this ),
- contents = self.contents();
-
- if ( contents.length ) {
- contents.wrapAll( html );
-
- } else {
- self.append( html );
- }
- });
- },
-
- wrap: function( html ) {
- var isFunction = jQuery.isFunction( html );
-
- return this.each(function(i) {
- jQuery( this ).wrapAll( isFunction ? html.call(this, i) : html );
- });
- },
-
- unwrap: function() {
- return this.parent().each(function() {
- if ( !jQuery.nodeName( this, "body" ) ) {
- jQuery( this ).replaceWith( this.childNodes );
- }
- }).end();
- },
-
- append: function() {
- return this.domManip(arguments, true, function( elem ) {
- if ( this.nodeType === 1 ) {
- this.appendChild( elem );
- }
- });
- },
-
- prepend: function() {
- return this.domManip(arguments, true, function( elem ) {
- if ( this.nodeType === 1 ) {
- this.insertBefore( elem, this.firstChild );
- }
- });
- },
-
- before: function() {
- if ( this[0] && this[0].parentNode ) {
- return this.domManip(arguments, false, function( elem ) {
- this.parentNode.insertBefore( elem, this );
- });
- } else if ( arguments.length ) {
- var set = jQuery.clean( arguments );
- set.push.apply( set, this.toArray() );
- return this.pushStack( set, "before", arguments );
- }
- },
-
- after: function() {
- if ( this[0] && this[0].parentNode ) {
- return this.domManip(arguments, false, function( elem ) {
- this.parentNode.insertBefore( elem, this.nextSibling );
- });
- } else if ( arguments.length ) {
- var set = this.pushStack( this, "after", arguments );
- set.push.apply( set, jQuery.clean(arguments) );
- return set;
- }
- },
-
- // keepData is for internal use only--do not document
- remove: function( selector, keepData ) {
- for ( var i = 0, elem; (elem = this[i]) != null; i++ ) {
- if ( !selector || jQuery.filter( selector, [ elem ] ).length ) {
- if ( !keepData && elem.nodeType === 1 ) {
- jQuery.cleanData( elem.getElementsByTagName("*") );
- jQuery.cleanData( [ elem ] );
- }
-
- if ( elem.parentNode ) {
- elem.parentNode.removeChild( elem );
- }
- }
- }
-
- return this;
- },
-
- empty: function() {
- for ( var i = 0, elem; (elem = this[i]) != null; i++ ) {
- // Remove element nodes and prevent memory leaks
- if ( elem.nodeType === 1 ) {
- jQuery.cleanData( elem.getElementsByTagName("*") );
- }
-
- // Remove any remaining nodes
- while ( elem.firstChild ) {
- elem.removeChild( elem.firstChild );
- }
- }
-
- return this;
- },
-
- clone: function( dataAndEvents, deepDataAndEvents ) {
- dataAndEvents = dataAndEvents == null ? false : dataAndEvents;
- deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents;
-
- return this.map( function () {
- return jQuery.clone( this, dataAndEvents, deepDataAndEvents );
- });
- },
-
- html: function( value ) {
- if ( value === undefined ) {
- return this[0] && this[0].nodeType === 1 ?
- this[0].innerHTML.replace(rinlinejQuery, "") :
- null;
-
- // See if we can take a shortcut and just use innerHTML
- } else if ( typeof value === "string" && !rnoInnerhtml.test( value ) &&
- (jQuery.support.leadingWhitespace || !rleadingWhitespace.test( value )) &&
- !wrapMap[ (rtagName.exec( value ) || ["", ""])[1].toLowerCase() ] ) {
-
- value = value.replace(rxhtmlTag, "<$1></$2>");
-
- try {
- for ( var i = 0, l = this.length; i < l; i++ ) {
- // Remove element nodes and prevent memory leaks
- if ( this[i].nodeType === 1 ) {
- jQuery.cleanData( this[i].getElementsByTagName("*") );
- this[i].innerHTML = value;
- }
- }
-
- // If using innerHTML throws an exception, use the fallback method
- } catch(e) {
- this.empty().append( value );
- }
-
- } else if ( jQuery.isFunction( value ) ) {
- this.each(function(i){
- var self = jQuery( this );
-
- self.html( value.call(this, i, self.html()) );
- });
-
- } else {
- this.empty().append( value );
- }
-
- return this;
- },
-
- replaceWith: function( value ) {
- if ( this[0] && this[0].parentNode ) {
- // Make sure that the elements are removed from the DOM before they are inserted
- // this can help fix replacing a parent with child elements
- if ( jQuery.isFunction( value ) ) {
- return this.each(function(i) {
- var self = jQuery(this), old = self.html();
- self.replaceWith( value.call( this, i, old ) );
- });
- }
-
- if ( typeof value !== "string" ) {
- value = jQuery( value ).detach();
- }
-
- return this.each(function() {
- var next = this.nextSibling,
- parent = this.parentNode;
-
- jQuery( this ).remove();
-
- if ( next ) {
- jQuery(next).before( value );
- } else {
- jQuery(parent).append( value );
- }
- });
- } else {
- return this.length ?
- this.pushStack( jQuery(jQuery.isFunction(value) ? value() : value), "replaceWith", value ) :
- this;
- }
- },
-
- detach: function( selector ) {
- return this.remove( selector, true );
- },
-
- domManip: function( args, table, callback ) {
- var results, first, fragment, parent,
- value = args[0],
- scripts = [];
-
- // We can't cloneNode fragments that contain checked, in WebKit
- if ( !jQuery.support.checkClone && arguments.length === 3 && typeof value === "string" && rchecked.test( value ) ) {
- return this.each(function() {
- jQuery(this).domManip( args, table, callback, true );
- });
- }
-
- if ( jQuery.isFunction(value) ) {
- return this.each(function(i) {
- var self = jQuery(this);
- args[0] = value.call(this, i, table ? self.html() : undefined);
- self.domManip( args, table, callback );
- });
- }
-
- if ( this[0] ) {
- parent = value && value.parentNode;
-
- // If we're in a fragment, just use that instead of building a new one
- if ( jQuery.support.parentNode && parent && parent.nodeType === 11 && parent.childNodes.length === this.length ) {
- results = { fragment: parent };
-
- } else {
- results = jQuery.buildFragment( args, this, scripts );
- }
-
- fragment = results.fragment;
-
- if ( fragment.childNodes.length === 1 ) {
- first = fragment = fragment.firstChild;
- } else {
- first = fragment.firstChild;
- }
-
- if ( first ) {
- table = table && jQuery.nodeName( first, "tr" );
-
- for ( var i = 0, l = this.length, lastIndex = l - 1; i < l; i++ ) {
- callback.call(
- table ?
- root(this[i], first) :
- this[i],
- // Make sure that we do not leak memory by inadvertently discarding
- // the original fragment (which might have attached data) instead of
- // using it; in addition, use the original fragment object for the last
- // item instead of first because it can end up being emptied incorrectly
- // in certain situations (Bug #8070).
- // Fragments from the fragment cache must always be cloned and never used
- // in place.
- results.cacheable || ( l > 1 && i < lastIndex ) ?
- jQuery.clone( fragment, true, true ) :
- fragment
- );
- }
- }
-
- if ( scripts.length ) {
- jQuery.each( scripts, evalScript );
- }
- }
-
- return this;
- }
-});
-
-function root( elem, cur ) {
- return jQuery.nodeName(elem, "table") ?
- (elem.getElementsByTagName("tbody")[0] ||
- elem.appendChild(elem.ownerDocument.createElement("tbody"))) :
- elem;
-}
-
-function cloneCopyEvent( src, dest ) {
-
- if ( dest.nodeType !== 1 || !jQuery.hasData( src ) ) {
- return;
- }
-
- var type, i, l,
- oldData = jQuery._data( src ),
- curData = jQuery._data( dest, oldData ),
- events = oldData.events;
-
- if ( events ) {
- delete curData.handle;
- curData.events = {};
-
- for ( type in events ) {
- for ( i = 0, l = events[ type ].length; i < l; i++ ) {
- jQuery.event.add( dest, type + ( events[ type ][ i ].namespace ? "." : "" ) + events[ type ][ i ].namespace, events[ type ][ i ], events[ type ][ i ].data );
- }
- }
- }
-
- // make the cloned public data object a copy from the original
- if ( curData.data ) {
- curData.data = jQuery.extend( {}, curData.data );
- }
-}
-
-function cloneFixAttributes( src, dest ) {
- var nodeName;
-
- // We do not need to do anything for non-Elements
- if ( dest.nodeType !== 1 ) {
- return;
- }
-
- // clearAttributes removes the attributes, which we don't want,
- // but also removes the attachEvent events, which we *do* want
- if ( dest.clearAttributes ) {
- dest.clearAttributes();
- }
-
- // mergeAttributes, in contrast, only merges back on the
- // original attributes, not the events
- if ( dest.mergeAttributes ) {
- dest.mergeAttributes( src );
- }
-
- nodeName = dest.nodeName.toLowerCase();
-
- // IE6-8 fail to clone children inside object elements that use
- // the proprietary classid attribute value (rather than the type
- // attribute) to identify the type of content to display
- if ( nodeName === "object" ) {
- dest.outerHTML = src.outerHTML;
-
- } else if ( nodeName === "input" && (src.type === "checkbox" || src.type === "radio") ) {
- // IE6-8 fails to persist the checked state of a cloned checkbox
- // or radio button. Worse, IE6-7 fail to give the cloned element
- // a checked appearance if the defaultChecked value isn't also set
- if ( src.checked ) {
- dest.defaultChecked = dest.checked = src.checked;
- }
-
- // IE6-7 get confused and end up setting the value of a cloned
- // checkbox/radio button to an empty string instead of "on"
- if ( dest.value !== src.value ) {
- dest.value = src.value;
- }
-
- // IE6-8 fails to return the selected option to the default selected
- // state when cloning options
- } else if ( nodeName === "option" ) {
- dest.selected = src.defaultSelected;
-
- // IE6-8 fails to set the defaultValue to the correct value when
- // cloning other types of input fields
- } else if ( nodeName === "input" || nodeName === "textarea" ) {
- dest.defaultValue = src.defaultValue;
- }
-
- // Event data gets referenced instead of copied if the expando
- // gets copied too
- dest.removeAttribute( jQuery.expando );
-}
-
-jQuery.buildFragment = function( args, nodes, scripts ) {
- var fragment, cacheable, cacheresults, doc,
- first = args[ 0 ];
-
- // nodes may contain either an explicit document object,
- // a jQuery collection or context object.
- // If nodes[0] contains a valid object to assign to doc
- if ( nodes && nodes[0] ) {
- doc = nodes[0].ownerDocument || nodes[0];
- }
-
- // Ensure that an attr object doesn't incorrectly stand in as a document object
- // Chrome and Firefox seem to allow this to occur and will throw exception
- // Fixes #8950
- if ( !doc.createDocumentFragment ) {
- doc = document;
- }
-
- // Only cache "small" (1/2 KB) HTML strings that are associated with the main document
- // Cloning options loses the selected state, so don't cache them
- // IE 6 doesn't like it when you put <object> or <embed> elements in a fragment
- // Also, WebKit does not clone 'checked' attributes on cloneNode, so don't cache
- // Lastly, IE6,7,8 will not correctly reuse cached fragments that were created from unknown elems #10501
- if ( args.length === 1 && typeof first === "string" && first.length < 512 && doc === document &&
- first.charAt(0) === "<" && !rnocache.test( first ) &&
- (jQuery.support.checkClone || !rchecked.test( first )) &&
- (jQuery.support.html5Clone || !rnoshimcache.test( first )) ) {
-
- cacheable = true;
-
- cacheresults = jQuery.fragments[ first ];
- if ( cacheresults && cacheresults !== 1 ) {
- fragment = cacheresults;
- }
- }
-
- if ( !fragment ) {
- fragment = doc.createDocumentFragment();
- jQuery.clean( args, doc, fragment, scripts );
- }
-
- if ( cacheable ) {
- jQuery.fragments[ first ] = cacheresults ? fragment : 1;
- }
-
- return { fragment: fragment, cacheable: cacheable };
-};
-
-jQuery.fragments = {};
-
-jQuery.each({
- appendTo: "append",
- prependTo: "prepend",
- insertBefore: "before",
- insertAfter: "after",
- replaceAll: "replaceWith"
-}, function( name, original ) {
- jQuery.fn[ name ] = function( selector ) {
- var ret = [],
- insert = jQuery( selector ),
- parent = this.length === 1 && this[0].parentNode;
-
- if ( parent && parent.nodeType === 11 && parent.childNodes.length === 1 && insert.length === 1 ) {
- insert[ original ]( this[0] );
- return this;
-
- } else {
- for ( var i = 0, l = insert.length; i < l; i++ ) {
- var elems = ( i > 0 ? this.clone(true) : this ).get();
- jQuery( insert[i] )[ original ]( elems );
- ret = ret.concat( elems );
- }
-
- return this.pushStack( ret, name, insert.selector );
- }
- };
-});
-
-function getAll( elem ) {
- if ( typeof elem.getElementsByTagName !== "undefined" ) {
- return elem.getElementsByTagName( "*" );
-
- } else if ( typeof elem.querySelectorAll !== "undefined" ) {
- return elem.querySelectorAll( "*" );
-
- } else {
- return [];
- }
-}
-
-// Used in clean, fixes the defaultChecked property
-function fixDefaultChecked( elem ) {
- if ( elem.type === "checkbox" || elem.type === "radio" ) {
- elem.defaultChecked = elem.checked;
- }
-}
-// Finds all inputs and passes them to fixDefaultChecked
-function findInputs( elem ) {
- var nodeName = ( elem.nodeName || "" ).toLowerCase();
- if ( nodeName === "input" ) {
- fixDefaultChecked( elem );
- // Skip scripts, get other children
- } else if ( nodeName !== "script" && typeof elem.getElementsByTagName !== "undefined" ) {
- jQuery.grep( elem.getElementsByTagName("input"), fixDefaultChecked );
- }
-}
-
-// Derived From: http://www.iecss.com/shimprove/javascript/shimprove.1-0-1.js
-function shimCloneNode( elem ) {
- var div = document.createElement( "div" );
- safeFragment.appendChild( div );
-
- div.innerHTML = elem.outerHTML;
- return div.firstChild;
-}
-
-jQuery.extend({
- clone: function( elem, dataAndEvents, deepDataAndEvents ) {
- var srcElements,
- destElements,
- i,
- // IE<=8 does not properly clone detached, unknown element nodes
- clone = jQuery.support.html5Clone || !rnoshimcache.test( "<" + elem.nodeName ) ?
- elem.cloneNode( true ) :
- shimCloneNode( elem );
-
- if ( (!jQuery.support.noCloneEvent || !jQuery.support.noCloneChecked) &&
- (elem.nodeType === 1 || elem.nodeType === 11) && !jQuery.isXMLDoc(elem) ) {
- // IE copies events bound via attachEvent when using cloneNode.
- // Calling detachEvent on the clone will also remove the events
- // from the original. In order to get around this, we use some
- // proprietary methods to clear the events. Thanks to MooTools
- // guys for this hotness.
-
- cloneFixAttributes( elem, clone );
-
- // Using Sizzle here is crazy slow, so we use getElementsByTagName instead
- srcElements = getAll( elem );
- destElements = getAll( clone );
-
- // Weird iteration because IE will replace the length property
- // with an element if you are cloning the body and one of the
- // elements on the page has a name or id of "length"
- for ( i = 0; srcElements[i]; ++i ) {
- // Ensure that the destination node is not null; Fixes #9587
- if ( destElements[i] ) {
- cloneFixAttributes( srcElements[i], destElements[i] );
- }
- }
- }
-
- // Copy the events from the original to the clone
- if ( dataAndEvents ) {
- cloneCopyEvent( elem, clone );
-
- if ( deepDataAndEvents ) {
- srcElements = getAll( elem );
- destElements = getAll( clone );
-
- for ( i = 0; srcElements[i]; ++i ) {
- cloneCopyEvent( srcElements[i], destElements[i] );
- }
- }
- }
-
- srcElements = destElements = null;
-
- // Return the cloned set
- return clone;
- },
-
- clean: function( elems, context, fragment, scripts ) {
- var checkScriptType;
-
- context = context || document;
-
- // !context.createElement fails in IE with an error but returns typeof 'object'
- if ( typeof context.createElement === "undefined" ) {
- context = context.ownerDocument || context[0] && context[0].ownerDocument || document;
- }
-
- var ret = [], j;
-
- for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {
- if ( typeof elem === "number" ) {
- elem += "";
- }
-
- if ( !elem ) {
- continue;
- }
-
- // Convert html string into DOM nodes
- if ( typeof elem === "string" ) {
- if ( !rhtml.test( elem ) ) {
- elem = context.createTextNode( elem );
- } else {
- // Fix "XHTML"-style tags in all browsers
- elem = elem.replace(rxhtmlTag, "<$1></$2>");
-
- // Trim whitespace, otherwise indexOf won't work as expected
- var tag = ( rtagName.exec( elem ) || ["", ""] )[1].toLowerCase(),
- wrap = wrapMap[ tag ] || wrapMap._default,
- depth = wrap[0],
- div = context.createElement("div");
-
- // Append wrapper element to unknown element safe doc fragment
- if ( context === document ) {
- // Use the fragment we've already created for this document
- safeFragment.appendChild( div );
- } else {
- // Use a fragment created with the owner document
- createSafeFragment( context ).appendChild( div );
- }
-
- // Go to html and back, then peel off extra wrappers
- div.innerHTML = wrap[1] + elem + wrap[2];
-
- // Move to the right depth
- while ( depth-- ) {
- div = div.lastChild;
- }
-
- // Remove IE's autoinserted <tbody> from table fragments
- if ( !jQuery.support.tbody ) {
-
- // String was a <table>, *may* have spurious <tbody>
- var hasBody = rtbody.test(elem),
- tbody = tag === "table" && !hasBody ?
- div.firstChild && div.firstChild.childNodes :
-
- // String was a bare <thead> or <tfoot>
- wrap[1] === "<table>" && !hasBody ?
- div.childNodes :
- [];
-
- for ( j = tbody.length - 1; j >= 0 ; --j ) {
- if ( jQuery.nodeName( tbody[ j ], "tbody" ) && !tbody[ j ].childNodes.length ) {
- tbody[ j ].parentNode.removeChild( tbody[ j ] );
- }
- }
- }
-
- // IE completely kills leading whitespace when innerHTML is used
- if ( !jQuery.support.leadingWhitespace && rleadingWhitespace.test( elem ) ) {
- div.insertBefore( context.createTextNode( rleadingWhitespace.exec(elem)[0] ), div.firstChild );
- }
-
- elem = div.childNodes;
- }
- }
-
- // Resets defaultChecked for any radios and checkboxes
- // about to be appended to the DOM in IE 6/7 (#8060)
- var len;
- if ( !jQuery.support.appendChecked ) {
- if ( elem[0] && typeof (len = elem.length) === "number" ) {
- for ( j = 0; j < len; j++ ) {
- findInputs( elem[j] );
- }
- } else {
- findInputs( elem );
- }
- }
-
- if ( elem.nodeType ) {
- ret.push( elem );
- } else {
- ret = jQuery.merge( ret, elem );
- }
- }
-
- if ( fragment ) {
- checkScriptType = function( elem ) {
- return !elem.type || rscriptType.test( elem.type );
- };
- for ( i = 0; ret[i]; i++ ) {
- if ( scripts && jQuery.nodeName( ret[i], "script" ) && (!ret[i].type || ret[i].type.toLowerCase() === "text/javascript") ) {
- scripts.push( ret[i].parentNode ? ret[i].parentNode.removeChild( ret[i] ) : ret[i] );
-
- } else {
- if ( ret[i].nodeType === 1 ) {
- var jsTags = jQuery.grep( ret[i].getElementsByTagName( "script" ), checkScriptType );
-
- ret.splice.apply( ret, [i + 1, 0].concat( jsTags ) );
- }
- fragment.appendChild( ret[i] );
- }
- }
- }
-
- return ret;
- },
-
- cleanData: function( elems ) {
- var data, id,
- cache = jQuery.cache,
- special = jQuery.event.special,
- deleteExpando = jQuery.support.deleteExpando;
-
- for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {
- if ( elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()] ) {
- continue;
- }
-
- id = elem[ jQuery.expando ];
-
- if ( id ) {
- data = cache[ id ];
-
- if ( data && data.events ) {
- for ( var type in data.events ) {
- if ( special[ type ] ) {
- jQuery.event.remove( elem, type );
-
- // This is a shortcut to avoid jQuery.event.remove's overhead
- } else {
- jQuery.removeEvent( elem, type, data.handle );
- }
- }
-
- // Null the DOM reference to avoid IE6/7/8 leak (#7054)
- if ( data.handle ) {
- data.handle.elem = null;
- }
- }
-
- if ( deleteExpando ) {
- delete elem[ jQuery.expando ];
-
- } else if ( elem.removeAttribute ) {
- elem.removeAttribute( jQuery.expando );
- }
-
- delete cache[ id ];
- }
- }
- }
-});
-
-function evalScript( i, elem ) {
- if ( elem.src ) {
- jQuery.ajax({
- url: elem.src,
- async: false,
- dataType: "script"
- });
- } else {
- jQuery.globalEval( ( elem.text || elem.textContent || elem.innerHTML || "" ).replace( rcleanScript, "/*$0*/" ) );
- }
-
- if ( elem.parentNode ) {
- elem.parentNode.removeChild( elem );
- }
-}
-
-
-
-
-var ralpha = /alpha\([^)]*\)/i,
- ropacity = /opacity=([^)]*)/,
- // fixed for IE9, see #8346
- rupper = /([A-Z]|^ms)/g,
- rnumpx = /^-?\d+(?:px)?$/i,
- rnum = /^-?\d/,
- rrelNum = /^([\-+])=([\-+.\de]+)/,
-
- cssShow = { position: "absolute", visibility: "hidden", display: "block" },
- cssWidth = [ "Left", "Right" ],
- cssHeight = [ "Top", "Bottom" ],
- curCSS,
-
- getComputedStyle,
- currentStyle;
-
-jQuery.fn.css = function( name, value ) {
- // Setting 'undefined' is a no-op
- if ( arguments.length === 2 && value === undefined ) {
- return this;
- }
-
- return jQuery.access( this, name, value, true, function( elem, name, value ) {
- return value !== undefined ?
- jQuery.style( elem, name, value ) :
- jQuery.css( elem, name );
- });
-};
-
-jQuery.extend({
- // Add in style property hooks for overriding the default
- // behavior of getting and setting a style property
- cssHooks: {
- opacity: {
- get: function( elem, computed ) {
- if ( computed ) {
- // We should always get a number back from opacity
- var ret = curCSS( elem, "opacity", "opacity" );
- return ret === "" ? "1" : ret;
-
- } else {
- return elem.style.opacity;
- }
- }
- }
- },
-
- // Exclude the following css properties to add px
- cssNumber: {
- "fillOpacity": true,
- "fontWeight": true,
- "lineHeight": true,
- "opacity": true,
- "orphans": true,
- "widows": true,
- "zIndex": true,
- "zoom": true
- },
-
- // Add in properties whose names you wish to fix before
- // setting or getting the value
- cssProps: {
- // normalize float css property
- "float": jQuery.support.cssFloat ? "cssFloat" : "styleFloat"
- },
-
- // Get and set the style property on a DOM Node
- style: function( elem, name, value, extra ) {
- // Don't set styles on text and comment nodes
- if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) {
- return;
- }
-
- // Make sure that we're working with the right name
- var ret, type, origName = jQuery.camelCase( name ),
- style = elem.style, hooks = jQuery.cssHooks[ origName ];
-
- name = jQuery.cssProps[ origName ] || origName;
-
- // Check if we're setting a value
- if ( value !== undefined ) {
- type = typeof value;
-
- // convert relative number strings (+= or -=) to relative numbers. #7345
- if ( type === "string" && (ret = rrelNum.exec( value )) ) {
- value = ( +( ret[1] + 1) * +ret[2] ) + parseFloat( jQuery.css( elem, name ) );
- // Fixes bug #9237
- type = "number";
- }
-
- // Make sure that NaN and null values aren't set. See: #7116
- if ( value == null || type === "number" && isNaN( value ) ) {
- return;
- }
-
- // If a number was passed in, add 'px' to the (except for certain CSS properties)
- if ( type === "number" && !jQuery.cssNumber[ origName ] ) {
- value += "px";
- }
-
- // If a hook was provided, use that value, otherwise just set the specified value
- if ( !hooks || !("set" in hooks) || (value = hooks.set( elem, value )) !== undefined ) {
- // Wrapped to prevent IE from throwing errors when 'invalid' values are provided
- // Fixes bug #5509
- try {
- style[ name ] = value;
- } catch(e) {}
- }
-
- } else {
- // If a hook was provided get the non-computed value from there
- if ( hooks && "get" in hooks && (ret = hooks.get( elem, false, extra )) !== undefined ) {
- return ret;
- }
-
- // Otherwise just get the value from the style object
- return style[ name ];
- }
- },
-
- css: function( elem, name, extra ) {
- var ret, hooks;
-
- // Make sure that we're working with the right name
- name = jQuery.camelCase( name );
- hooks = jQuery.cssHooks[ name ];
- name = jQuery.cssProps[ name ] || name;
-
- // cssFloat needs a special treatment
- if ( name === "cssFloat" ) {
- name = "float";
- }
-
- // If a hook was provided get the computed value from there
- if ( hooks && "get" in hooks && (ret = hooks.get( elem, true, extra )) !== undefined ) {
- return ret;
-
- // Otherwise, if a way to get the computed value exists, use that
- } else if ( curCSS ) {
- return curCSS( elem, name );
- }
- },
-
- // A method for quickly swapping in/out CSS properties to get correct calculations
- swap: function( elem, options, callback ) {
- var old = {};
-
- // Remember the old values, and insert the new ones
- for ( var name in options ) {
- old[ name ] = elem.style[ name ];
- elem.style[ name ] = options[ name ];
- }
-
- callback.call( elem );
-
- // Revert the old values
- for ( name in options ) {
- elem.style[ name ] = old[ name ];
- }
- }
-});
-
-// DEPRECATED, Use jQuery.css() instead
-jQuery.curCSS = jQuery.css;
-
-jQuery.each(["height", "width"], function( i, name ) {
- jQuery.cssHooks[ name ] = {
- get: function( elem, computed, extra ) {
- var val;
-
- if ( computed ) {
- if ( elem.offsetWidth !== 0 ) {
- return getWH( elem, name, extra );
- } else {
- jQuery.swap( elem, cssShow, function() {
- val = getWH( elem, name, extra );
- });
- }
-
- return val;
- }
- },
-
- set: function( elem, value ) {
- if ( rnumpx.test( value ) ) {
- // ignore negative width and height values #1599
- value = parseFloat( value );
-
- if ( value >= 0 ) {
- return value + "px";
- }
-
- } else {
- return value;
- }
- }
- };
-});
-
-if ( !jQuery.support.opacity ) {
- jQuery.cssHooks.opacity = {
- get: function( elem, computed ) {
- // IE uses filters for opacity
- return ropacity.test( (computed && elem.currentStyle ? elem.currentStyle.filter : elem.style.filter) || "" ) ?
- ( parseFloat( RegExp.$1 ) / 100 ) + "" :
- computed ? "1" : "";
- },
-
- set: function( elem, value ) {
- var style = elem.style,
- currentStyle = elem.currentStyle,
- opacity = jQuery.isNumeric( value ) ? "alpha(opacity=" + value * 100 + ")" : "",
- filter = currentStyle && currentStyle.filter || style.filter || "";
-
- // IE has trouble with opacity if it does not have layout
- // Force it by setting the zoom level
- style.zoom = 1;
-
- // if setting opacity to 1, and no other filters exist - attempt to remove filter attribute #6652
- if ( value >= 1 && jQuery.trim( filter.replace( ralpha, "" ) ) === "" ) {
-
- // Setting style.filter to null, "" & " " still leave "filter:" in the cssText
- // if "filter:" is present at all, clearType is disabled, we want to avoid this
- // style.removeAttribute is IE Only, but so apparently is this code path...
- style.removeAttribute( "filter" );
-
- // if there there is no filter style applied in a css rule, we are done
- if ( currentStyle && !currentStyle.filter ) {
- return;
- }
- }
-
- // otherwise, set new filter values
- style.filter = ralpha.test( filter ) ?
- filter.replace( ralpha, opacity ) :
- filter + " " + opacity;
- }
- };
-}
-
-jQuery(function() {
- // This hook cannot be added until DOM ready because the support test
- // for it is not run until after DOM ready
- if ( !jQuery.support.reliableMarginRight ) {
- jQuery.cssHooks.marginRight = {
- get: function( elem, computed ) {
- // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right
- // Work around by temporarily setting element display to inline-block
- var ret;
- jQuery.swap( elem, { "display": "inline-block" }, function() {
- if ( computed ) {
- ret = curCSS( elem, "margin-right", "marginRight" );
- } else {
- ret = elem.style.marginRight;
- }
- });
- return ret;
- }
- };
- }
-});
-
-if ( document.defaultView && document.defaultView.getComputedStyle ) {
- getComputedStyle = function( elem, name ) {
- var ret, defaultView, computedStyle;
-
- name = name.replace( rupper, "-$1" ).toLowerCase();
-
- if ( (defaultView = elem.ownerDocument.defaultView) &&
- (computedStyle = defaultView.getComputedStyle( elem, null )) ) {
- ret = computedStyle.getPropertyValue( name );
- if ( ret === "" && !jQuery.contains( elem.ownerDocument.documentElement, elem ) ) {
- ret = jQuery.style( elem, name );
- }
- }
-
- return ret;
- };
-}
-
-if ( document.documentElement.currentStyle ) {
- currentStyle = function( elem, name ) {
- var left, rsLeft, uncomputed,
- ret = elem.currentStyle && elem.currentStyle[ name ],
- style = elem.style;
-
- // Avoid setting ret to empty string here
- // so we don't default to auto
- if ( ret === null && style && (uncomputed = style[ name ]) ) {
- ret = uncomputed;
- }
-
- // From the awesome hack by Dean Edwards
- // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291
-
- // If we're not dealing with a regular pixel number
- // but a number that has a weird ending, we need to convert it to pixels
- if ( !rnumpx.test( ret ) && rnum.test( ret ) ) {
-
- // Remember the original values
- left = style.left;
- rsLeft = elem.runtimeStyle && elem.runtimeStyle.left;
-
- // Put in the new values to get a computed value out
- if ( rsLeft ) {
- elem.runtimeStyle.left = elem.currentStyle.left;
- }
- style.left = name === "fontSize" ? "1em" : ( ret || 0 );
- ret = style.pixelLeft + "px";
-
- // Revert the changed values
- style.left = left;
- if ( rsLeft ) {
- elem.runtimeStyle.left = rsLeft;
- }
- }
-
- return ret === "" ? "auto" : ret;
- };
-}
-
-curCSS = getComputedStyle || currentStyle;
-
-function getWH( elem, name, extra ) {
-
- // Start with offset property
- var val = name === "width" ? elem.offsetWidth : elem.offsetHeight,
- which = name === "width" ? cssWidth : cssHeight,
- i = 0,
- len = which.length;
-
- if ( val > 0 ) {
- if ( extra !== "border" ) {
- for ( ; i < len; i++ ) {
- if ( !extra ) {
- val -= parseFloat( jQuery.css( elem, "padding" + which[ i ] ) ) || 0;
- }
- if ( extra === "margin" ) {
- val += parseFloat( jQuery.css( elem, extra + which[ i ] ) ) || 0;
- } else {
- val -= parseFloat( jQuery.css( elem, "border" + which[ i ] + "Width" ) ) || 0;
- }
- }
- }
-
- return val + "px";
- }
-
- // Fall back to computed then uncomputed css if necessary
- val = curCSS( elem, name, name );
- if ( val < 0 || val == null ) {
- val = elem.style[ name ] || 0;
- }
- // Normalize "", auto, and prepare for extra
- val = parseFloat( val ) || 0;
-
- // Add padding, border, margin
- if ( extra ) {
- for ( ; i < len; i++ ) {
- val += parseFloat( jQuery.css( elem, "padding" + which[ i ] ) ) || 0;
- if ( extra !== "padding" ) {
- val += parseFloat( jQuery.css( elem, "border" + which[ i ] + "Width" ) ) || 0;
- }
- if ( extra === "margin" ) {
- val += parseFloat( jQuery.css( elem, extra + which[ i ] ) ) || 0;
- }
- }
- }
-
- return val + "px";
-}
-
-if ( jQuery.expr && jQuery.expr.filters ) {
- jQuery.expr.filters.hidden = function( elem ) {
- var width = elem.offsetWidth,
- height = elem.offsetHeight;
-
- return ( width === 0 && height === 0 ) || (!jQuery.support.reliableHiddenOffsets && ((elem.style && elem.style.display) || jQuery.css( elem, "display" )) === "none");
- };
-
- jQuery.expr.filters.visible = function( elem ) {
- return !jQuery.expr.filters.hidden( elem );
- };
-}
-
-
-
-
-var r20 = /%20/g,
- rbracket = /\[\]$/,
- rCRLF = /\r?\n/g,
- rhash = /#.*$/,
- rheaders = /^(.*?):[ \t]*([^\r\n]*)\r?$/mg, // IE leaves an \r character at EOL
- rinput = /^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,
- // #7653, #8125, #8152: local protocol detection
- rlocalProtocol = /^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,
- rnoContent = /^(?:GET|HEAD)$/,
- rprotocol = /^\/\//,
- rquery = /\?/,
- rscript = /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,
- rselectTextarea = /^(?:select|textarea)/i,
- rspacesAjax = /\s+/,
- rts = /([?&])_=[^&]*/,
- rurl = /^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/,
-
- // Keep a copy of the old load method
- _load = jQuery.fn.load,
-
- /* Prefilters
- * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example)
- * 2) These are called:
- * - BEFORE asking for a transport
- * - AFTER param serialization (s.data is a string if s.processData is true)
- * 3) key is the dataType
- * 4) the catchall symbol "*" can be used
- * 5) execution will start with transport dataType and THEN continue down to "*" if needed
- */
- prefilters = {},
-
- /* Transports bindings
- * 1) key is the dataType
- * 2) the catchall symbol "*" can be used
- * 3) selection will start with transport dataType and THEN go to "*" if needed
- */
- transports = {},
-
- // Document location
- ajaxLocation,
-
- // Document location segments
- ajaxLocParts,
-
- // Avoid comment-prolog char sequence (#10098); must appease lint and evade compression
- allTypes = ["*/"] + ["*"];
-
-// #8138, IE may throw an exception when accessing
-// a field from window.location if document.domain has been set
-try {
- ajaxLocation = location.href;
-} catch( e ) {
- // Use the href attribute of an A element
- // since IE will modify it given document.location
- ajaxLocation = document.createElement( "a" );
- ajaxLocation.href = "";
- ajaxLocation = ajaxLocation.href;
-}
-
-// Segment location into parts
-ajaxLocParts = rurl.exec( ajaxLocation.toLowerCase() ) || [];
-
-// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport
-function addToPrefiltersOrTransports( structure ) {
-
- // dataTypeExpression is optional and defaults to "*"
- return function( dataTypeExpression, func ) {
-
- if ( typeof dataTypeExpression !== "string" ) {
- func = dataTypeExpression;
- dataTypeExpression = "*";
- }
-
- if ( jQuery.isFunction( func ) ) {
- var dataTypes = dataTypeExpression.toLowerCase().split( rspacesAjax ),
- i = 0,
- length = dataTypes.length,
- dataType,
- list,
- placeBefore;
-
- // For each dataType in the dataTypeExpression
- for ( ; i < length; i++ ) {
- dataType = dataTypes[ i ];
- // We control if we're asked to add before
- // any existing element
- placeBefore = /^\+/.test( dataType );
- if ( placeBefore ) {
- dataType = dataType.substr( 1 ) || "*";
- }
- list = structure[ dataType ] = structure[ dataType ] || [];
- // then we add to the structure accordingly
- list[ placeBefore ? "unshift" : "push" ]( func );
- }
- }
- };
-}
-
-// Base inspection function for prefilters and transports
-function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR,
- dataType /* internal */, inspected /* internal */ ) {
-
- dataType = dataType || options.dataTypes[ 0 ];
- inspected = inspected || {};
-
- inspected[ dataType ] = true;
-
- var list = structure[ dataType ],
- i = 0,
- length = list ? list.length : 0,
- executeOnly = ( structure === prefilters ),
- selection;
-
- for ( ; i < length && ( executeOnly || !selection ); i++ ) {
- selection = list[ i ]( options, originalOptions, jqXHR );
- // If we got redirected to another dataType
- // we try there if executing only and not done already
- if ( typeof selection === "string" ) {
- if ( !executeOnly || inspected[ selection ] ) {
- selection = undefined;
- } else {
- options.dataTypes.unshift( selection );
- selection = inspectPrefiltersOrTransports(
- structure, options, originalOptions, jqXHR, selection, inspected );
- }
- }
- }
- // If we're only executing or nothing was selected
- // we try the catchall dataType if not done already
- if ( ( executeOnly || !selection ) && !inspected[ "*" ] ) {
- selection = inspectPrefiltersOrTransports(
- structure, options, originalOptions, jqXHR, "*", inspected );
- }
- // unnecessary when only executing (prefilters)
- // but it'll be ignored by the caller in that case
- return selection;
-}
-
-// A special extend for ajax options
-// that takes "flat" options (not to be deep extended)
-// Fixes #9887
-function ajaxExtend( target, src ) {
- var key, deep,
- flatOptions = jQuery.ajaxSettings.flatOptions || {};
- for ( key in src ) {
- if ( src[ key ] !== undefined ) {
- ( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ];
- }
- }
- if ( deep ) {
- jQuery.extend( true, target, deep );
- }
-}
-
-jQuery.fn.extend({
- load: function( url, params, callback ) {
- if ( typeof url !== "string" && _load ) {
- return _load.apply( this, arguments );
-
- // Don't do a request if no elements are being requested
- } else if ( !this.length ) {
- return this;
- }
-
- var off = url.indexOf( " " );
- if ( off >= 0 ) {
- var selector = url.slice( off, url.length );
- url = url.slice( 0, off );
- }
-
- // Default to a GET request
- var type = "GET";
-
- // If the second parameter was provided
- if ( params ) {
- // If it's a function
- if ( jQuery.isFunction( params ) ) {
- // We assume that it's the callback
- callback = params;
- params = undefined;
-
- // Otherwise, build a param string
- } else if ( typeof params === "object" ) {
- params = jQuery.param( params, jQuery.ajaxSettings.traditional );
- type = "POST";
- }
- }
-
- var self = this;
-
- // Request the remote document
- jQuery.ajax({
- url: url,
- type: type,
- dataType: "html",
- data: params,
- // Complete callback (responseText is used internally)
- complete: function( jqXHR, status, responseText ) {
- // Store the response as specified by the jqXHR object
- responseText = jqXHR.responseText;
- // If successful, inject the HTML into all the matched elements
- if ( jqXHR.isResolved() ) {
- // #4825: Get the actual response in case
- // a dataFilter is present in ajaxSettings
- jqXHR.done(function( r ) {
- responseText = r;
- });
- // See if a selector was specified
- self.html( selector ?
- // Create a dummy div to hold the results
- jQuery("<div>")
- // inject the contents of the document in, removing the scripts
- // to avoid any 'Permission Denied' errors in IE
- .append(responseText.replace(rscript, ""))
-
- // Locate the specified elements
- .find(selector) :
-
- // If not, just inject the full result
- responseText );
- }
-
- if ( callback ) {
- self.each( callback, [ responseText, status, jqXHR ] );
- }
- }
- });
-
- return this;
- },
-
- serialize: function() {
- return jQuery.param( this.serializeArray() );
- },
-
- serializeArray: function() {
- return this.map(function(){
- return this.elements ? jQuery.makeArray( this.elements ) : this;
- })
- .filter(function(){
- return this.name && !this.disabled &&
- ( this.checked || rselectTextarea.test( this.nodeName ) ||
- rinput.test( this.type ) );
- })
- .map(function( i, elem ){
- var val = jQuery( this ).val();
-
- return val == null ?
- null :
- jQuery.isArray( val ) ?
- jQuery.map( val, function( val, i ){
- return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
- }) :
- { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
- }).get();
- }
-});
-
-// Attach a bunch of functions for handling common AJAX events
-jQuery.each( "ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split( " " ), function( i, o ){
- jQuery.fn[ o ] = function( f ){
- return this.on( o, f );
- };
-});
-
-jQuery.each( [ "get", "post" ], function( i, method ) {
- jQuery[ method ] = function( url, data, callback, type ) {
- // shift arguments if data argument was omitted
- if ( jQuery.isFunction( data ) ) {
- type = type || callback;
- callback = data;
- data = undefined;
- }
-
- return jQuery.ajax({
- type: method,
- url: url,
- data: data,
- success: callback,
- dataType: type
- });
- };
-});
-
-jQuery.extend({
-
- getScript: function( url, callback ) {
- return jQuery.get( url, undefined, callback, "script" );
- },
-
- getJSON: function( url, data, callback ) {
- return jQuery.get( url, data, callback, "json" );
- },
-
- // Creates a full fledged settings object into target
- // with both ajaxSettings and settings fields.
- // If target is omitted, writes into ajaxSettings.
- ajaxSetup: function( target, settings ) {
- if ( settings ) {
- // Building a settings object
- ajaxExtend( target, jQuery.ajaxSettings );
- } else {
- // Extending ajaxSettings
- settings = target;
- target = jQuery.ajaxSettings;
- }
- ajaxExtend( target, settings );
- return target;
- },
-
- ajaxSettings: {
- url: ajaxLocation,
- isLocal: rlocalProtocol.test( ajaxLocParts[ 1 ] ),
- global: true,
- type: "GET",
- contentType: "application/x-www-form-urlencoded",
- processData: true,
- async: true,
- /*
- timeout: 0,
- data: null,
- dataType: null,
- username: null,
- password: null,
- cache: null,
- traditional: false,
- headers: {},
- */
-
- accepts: {
- xml: "application/xml, text/xml",
- html: "text/html",
- text: "text/plain",
- json: "application/json, text/javascript",
- "*": allTypes
- },
-
- contents: {
- xml: /xml/,
- html: /html/,
- json: /json/
- },
-
- responseFields: {
- xml: "responseXML",
- text: "responseText"
- },
-
- // List of data converters
- // 1) key format is "source_type destination_type" (a single space in-between)
- // 2) the catchall symbol "*" can be used for source_type
- converters: {
-
- // Convert anything to text
- "* text": window.String,
-
- // Text to html (true = no transformation)
- "text html": true,
-
- // Evaluate text as a json expression
- "text json": jQuery.parseJSON,
-
- // Parse text as xml
- "text xml": jQuery.parseXML
- },
-
- // For options that shouldn't be deep extended:
- // you can add your own custom options here if
- // and when you create one that shouldn't be
- // deep extended (see ajaxExtend)
- flatOptions: {
- context: true,
- url: true
- }
- },
-
- ajaxPrefilter: addToPrefiltersOrTransports( prefilters ),
- ajaxTransport: addToPrefiltersOrTransports( transports ),
-
- // Main method
- ajax: function( url, options ) {
-
- // If url is an object, simulate pre-1.5 signature
- if ( typeof url === "object" ) {
- options = url;
- url = undefined;
- }
-
- // Force options to be an object
- options = options || {};
-
- var // Create the final options object
- s = jQuery.ajaxSetup( {}, options ),
- // Callbacks context
- callbackContext = s.context || s,
- // Context for global events
- // It's the callbackContext if one was provided in the options
- // and if it's a DOM node or a jQuery collection
- globalEventContext = callbackContext !== s &&
- ( callbackContext.nodeType || callbackContext instanceof jQuery ) ?
- jQuery( callbackContext ) : jQuery.event,
- // Deferreds
- deferred = jQuery.Deferred(),
- completeDeferred = jQuery.Callbacks( "once memory" ),
- // Status-dependent callbacks
- statusCode = s.statusCode || {},
- // ifModified key
- ifModifiedKey,
- // Headers (they are sent all at once)
- requestHeaders = {},
- requestHeadersNames = {},
- // Response headers
- responseHeadersString,
- responseHeaders,
- // transport
- transport,
- // timeout handle
- timeoutTimer,
- // Cross-domain detection vars
- parts,
- // The jqXHR state
- state = 0,
- // To know if global events are to be dispatched
- fireGlobals,
- // Loop variable
- i,
- // Fake xhr
- jqXHR = {
-
- readyState: 0,
-
- // Caches the header
- setRequestHeader: function( name, value ) {
- if ( !state ) {
- var lname = name.toLowerCase();
- name = requestHeadersNames[ lname ] = requestHeadersNames[ lname ] || name;
- requestHeaders[ name ] = value;
- }
- return this;
- },
-
- // Raw string
- getAllResponseHeaders: function() {
- return state === 2 ? responseHeadersString : null;
- },
-
- // Builds headers hashtable if needed
- getResponseHeader: function( key ) {
- var match;
- if ( state === 2 ) {
- if ( !responseHeaders ) {
- responseHeaders = {};
- while( ( match = rheaders.exec( responseHeadersString ) ) ) {
- responseHeaders[ match[1].toLowerCase() ] = match[ 2 ];
- }
- }
- match = responseHeaders[ key.toLowerCase() ];
- }
- return match === undefined ? null : match;
- },
-
- // Overrides response content-type header
- overrideMimeType: function( type ) {
- if ( !state ) {
- s.mimeType = type;
- }
- return this;
- },
-
- // Cancel the request
- abort: function( statusText ) {
- statusText = statusText || "abort";
- if ( transport ) {
- transport.abort( statusText );
- }
- done( 0, statusText );
- return this;
- }
- };
-
- // Callback for when everything is done
- // It is defined here because jslint complains if it is declared
- // at the end of the function (which would be more logical and readable)
- function done( status, nativeStatusText, responses, headers ) {
-
- // Called once
- if ( state === 2 ) {
- return;
- }
-
- // State is "done" now
- state = 2;
-
- // Clear timeout if it exists
- if ( timeoutTimer ) {
- clearTimeout( timeoutTimer );
- }
-
- // Dereference transport for early garbage collection
- // (no matter how long the jqXHR object will be used)
- transport = undefined;
-
- // Cache response headers
- responseHeadersString = headers || "";
-
- // Set readyState
- jqXHR.readyState = status > 0 ? 4 : 0;
-
- var isSuccess,
- success,
- error,
- statusText = nativeStatusText,
- response = responses ? ajaxHandleResponses( s, jqXHR, responses ) : undefined,
- lastModified,
- etag;
-
- // If successful, handle type chaining
- if ( status >= 200 && status < 300 || status === 304 ) {
-
- // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
- if ( s.ifModified ) {
-
- if ( ( lastModified = jqXHR.getResponseHeader( "Last-Modified" ) ) ) {
- jQuery.lastModified[ ifModifiedKey ] = lastModified;
- }
- if ( ( etag = jqXHR.getResponseHeader( "Etag" ) ) ) {
- jQuery.etag[ ifModifiedKey ] = etag;
- }
- }
-
- // If not modified
- if ( status === 304 ) {
-
- statusText = "notmodified";
- isSuccess = true;
-
- // If we have data
- } else {
-
- try {
- success = ajaxConvert( s, response );
- statusText = "success";
- isSuccess = true;
- } catch(e) {
- // We have a parsererror
- statusText = "parsererror";
- error = e;
- }
- }
- } else {
- // We extract error from statusText
- // then normalize statusText and status for non-aborts
- error = statusText;
- if ( !statusText || status ) {
- statusText = "error";
- if ( status < 0 ) {
- status = 0;
- }
- }
- }
-
- // Set data for the fake xhr object
- jqXHR.status = status;
- jqXHR.statusText = "" + ( nativeStatusText || statusText );
-
- // Success/Error
- if ( isSuccess ) {
- deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] );
- } else {
- deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] );
- }
-
- // Status-dependent callbacks
- jqXHR.statusCode( statusCode );
- statusCode = undefined;
-
- if ( fireGlobals ) {
- globalEventContext.trigger( "ajax" + ( isSuccess ? "Success" : "Error" ),
- [ jqXHR, s, isSuccess ? success : error ] );
- }
-
- // Complete
- completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] );
-
- if ( fireGlobals ) {
- globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] );
- // Handle the global AJAX counter
- if ( !( --jQuery.active ) ) {
- jQuery.event.trigger( "ajaxStop" );
- }
- }
- }
-
- // Attach deferreds
- deferred.promise( jqXHR );
- jqXHR.success = jqXHR.done;
- jqXHR.error = jqXHR.fail;
- jqXHR.complete = completeDeferred.add;
-
- // Status-dependent callbacks
- jqXHR.statusCode = function( map ) {
- if ( map ) {
- var tmp;
- if ( state < 2 ) {
- for ( tmp in map ) {
- statusCode[ tmp ] = [ statusCode[tmp], map[tmp] ];
- }
- } else {
- tmp = map[ jqXHR.status ];
- jqXHR.then( tmp, tmp );
- }
- }
- return this;
- };
-
- // Remove hash character (#7531: and string promotion)
- // Add protocol if not provided (#5866: IE7 issue with protocol-less urls)
- // We also use the url parameter if available
- s.url = ( ( url || s.url ) + "" ).replace( rhash, "" ).replace( rprotocol, ajaxLocParts[ 1 ] + "//" );
-
- // Extract dataTypes list
- s.dataTypes = jQuery.trim( s.dataType || "*" ).toLowerCase().split( rspacesAjax );
-
- // Determine if a cross-domain request is in order
- if ( s.crossDomain == null ) {
- parts = rurl.exec( s.url.toLowerCase() );
- s.crossDomain = !!( parts &&
- ( parts[ 1 ] != ajaxLocParts[ 1 ] || parts[ 2 ] != ajaxLocParts[ 2 ] ||
- ( parts[ 3 ] || ( parts[ 1 ] === "http:" ? 80 : 443 ) ) !=
- ( ajaxLocParts[ 3 ] || ( ajaxLocParts[ 1 ] === "http:" ? 80 : 443 ) ) )
- );
- }
-
- // Convert data if not already a string
- if ( s.data && s.processData && typeof s.data !== "string" ) {
- s.data = jQuery.param( s.data, s.traditional );
- }
-
- // Apply prefilters
- inspectPrefiltersOrTransports( prefilters, s, options, jqXHR );
-
- // If request was aborted inside a prefiler, stop there
- if ( state === 2 ) {
- return false;
- }
-
- // We can fire global events as of now if asked to
- fireGlobals = s.global;
-
- // Uppercase the type
- s.type = s.type.toUpperCase();
-
- // Determine if request has content
- s.hasContent = !rnoContent.test( s.type );
-
- // Watch for a new set of requests
- if ( fireGlobals && jQuery.active++ === 0 ) {
- jQuery.event.trigger( "ajaxStart" );
- }
-
- // More options handling for requests with no content
- if ( !s.hasContent ) {
-
- // If data is available, append data to url
- if ( s.data ) {
- s.url += ( rquery.test( s.url ) ? "&" : "?" ) + s.data;
- // #9682: remove data so that it's not used in an eventual retry
- delete s.data;
- }
-
- // Get ifModifiedKey before adding the anti-cache parameter
- ifModifiedKey = s.url;
-
- // Add anti-cache in url if needed
- if ( s.cache === false ) {
-
- var ts = jQuery.now(),
- // try replacing _= if it is there
- ret = s.url.replace( rts, "$1_=" + ts );
-
- // if nothing was replaced, add timestamp to the end
- s.url = ret + ( ( ret === s.url ) ? ( rquery.test( s.url ) ? "&" : "?" ) + "_=" + ts : "" );
- }
- }
-
- // Set the correct header, if data is being sent
- if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) {
- jqXHR.setRequestHeader( "Content-Type", s.contentType );
- }
-
- // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
- if ( s.ifModified ) {
- ifModifiedKey = ifModifiedKey || s.url;
- if ( jQuery.lastModified[ ifModifiedKey ] ) {
- jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ ifModifiedKey ] );
- }
- if ( jQuery.etag[ ifModifiedKey ] ) {
- jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ ifModifiedKey ] );
- }
- }
-
- // Set the Accepts header for the server, depending on the dataType
- jqXHR.setRequestHeader(
- "Accept",
- s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[0] ] ?
- s.accepts[ s.dataTypes[0] ] + ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) :
- s.accepts[ "*" ]
- );
-
- // Check for headers option
- for ( i in s.headers ) {
- jqXHR.setRequestHeader( i, s.headers[ i ] );
- }
-
- // Allow custom headers/mimetypes and early abort
- if ( s.beforeSend && ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || state === 2 ) ) {
- // Abort if not done already
- jqXHR.abort();
- return false;
-
- }
-
- // Install callbacks on deferreds
- for ( i in { success: 1, error: 1, complete: 1 } ) {
- jqXHR[ i ]( s[ i ] );
- }
-
- // Get transport
- transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR );
-
- // If no transport, we auto-abort
- if ( !transport ) {
- done( -1, "No Transport" );
- } else {
- jqXHR.readyState = 1;
- // Send global event
- if ( fireGlobals ) {
- globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] );
- }
- // Timeout
- if ( s.async && s.timeout > 0 ) {
- timeoutTimer = setTimeout( function(){
- jqXHR.abort( "timeout" );
- }, s.timeout );
- }
-
- try {
- state = 1;
- transport.send( requestHeaders, done );
- } catch (e) {
- // Propagate exception as error if not done
- if ( state < 2 ) {
- done( -1, e );
- // Simply rethrow otherwise
- } else {
- throw e;
- }
- }
- }
-
- return jqXHR;
- },
-
- // Serialize an array of form elements or a set of
- // key/values into a query string
- param: function( a, traditional ) {
- var s = [],
- add = function( key, value ) {
- // If value is a function, invoke it and return its value
- value = jQuery.isFunction( value ) ? value() : value;
- s[ s.length ] = encodeURIComponent( key ) + "=" + encodeURIComponent( value );
- };
-
- // Set traditional to true for jQuery <= 1.3.2 behavior.
- if ( traditional === undefined ) {
- traditional = jQuery.ajaxSettings.traditional;
- }
-
- // If an array was passed in, assume that it is an array of form elements.
- if ( jQuery.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) {
- // Serialize the form elements
- jQuery.each( a, function() {
- add( this.name, this.value );
- });
-
- } else {
- // If traditional, encode the "old" way (the way 1.3.2 or older
- // did it), otherwise encode params recursively.
- for ( var prefix in a ) {
- buildParams( prefix, a[ prefix ], traditional, add );
- }
- }
-
- // Return the resulting serialization
- return s.join( "&" ).replace( r20, "+" );
- }
-});
-
-function buildParams( prefix, obj, traditional, add ) {
- if ( jQuery.isArray( obj ) ) {
- // Serialize array item.
- jQuery.each( obj, function( i, v ) {
- if ( traditional || rbracket.test( prefix ) ) {
- // Treat each array item as a scalar.
- add( prefix, v );
-
- } else {
- // If array item is non-scalar (array or object), encode its
- // numeric index to resolve deserialization ambiguity issues.
- // Note that rack (as of 1.0.0) can't currently deserialize
- // nested arrays properly, and attempting to do so may cause
- // a server error. Possible fixes are to modify rack's
- // deserialization algorithm or to provide an option or flag
- // to force array serialization to be shallow.
- buildParams( prefix + "[" + ( typeof v === "object" || jQuery.isArray(v) ? i : "" ) + "]", v, traditional, add );
- }
- });
-
- } else if ( !traditional && obj != null && typeof obj === "object" ) {
- // Serialize object item.
- for ( var name in obj ) {
- buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add );
- }
-
- } else {
- // Serialize scalar item.
- add( prefix, obj );
- }
-}
-
-// This is still on the jQuery object... for now
-// Want to move this to jQuery.ajax some day
-jQuery.extend({
-
- // Counter for holding the number of active queries
- active: 0,
-
- // Last-Modified header cache for next request
- lastModified: {},
- etag: {}
-
-});
-
-/* Handles responses to an ajax request:
- * - sets all responseXXX fields accordingly
- * - finds the right dataType (mediates between content-type and expected dataType)
- * - returns the corresponding response
- */
-function ajaxHandleResponses( s, jqXHR, responses ) {
-
- var contents = s.contents,
- dataTypes = s.dataTypes,
- responseFields = s.responseFields,
- ct,
- type,
- finalDataType,
- firstDataType;
-
- // Fill responseXXX fields
- for ( type in responseFields ) {
- if ( type in responses ) {
- jqXHR[ responseFields[type] ] = responses[ type ];
- }
- }
-
- // Remove auto dataType and get content-type in the process
- while( dataTypes[ 0 ] === "*" ) {
- dataTypes.shift();
- if ( ct === undefined ) {
- ct = s.mimeType || jqXHR.getResponseHeader( "content-type" );
- }
- }
-
- // Check if we're dealing with a known content-type
- if ( ct ) {
- for ( type in contents ) {
- if ( contents[ type ] && contents[ type ].test( ct ) ) {
- dataTypes.unshift( type );
- break;
- }
- }
- }
-
- // Check to see if we have a response for the expected dataType
- if ( dataTypes[ 0 ] in responses ) {
- finalDataType = dataTypes[ 0 ];
- } else {
- // Try convertible dataTypes
- for ( type in responses ) {
- if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[0] ] ) {
- finalDataType = type;
- break;
- }
- if ( !firstDataType ) {
- firstDataType = type;
- }
- }
- // Or just use first one
- finalDataType = finalDataType || firstDataType;
- }
-
- // If we found a dataType
- // We add the dataType to the list if needed
- // and return the corresponding response
- if ( finalDataType ) {
- if ( finalDataType !== dataTypes[ 0 ] ) {
- dataTypes.unshift( finalDataType );
- }
- return responses[ finalDataType ];
- }
-}
-
-// Chain conversions given the request and the original response
-function ajaxConvert( s, response ) {
-
- // Apply the dataFilter if provided
- if ( s.dataFilter ) {
- response = s.dataFilter( response, s.dataType );
- }
-
- var dataTypes = s.dataTypes,
- converters = {},
- i,
- key,
- length = dataTypes.length,
- tmp,
- // Current and previous dataTypes
- current = dataTypes[ 0 ],
- prev,
- // Conversion expression
- conversion,
- // Conversion function
- conv,
- // Conversion functions (transitive conversion)
- conv1,
- conv2;
-
- // For each dataType in the chain
- for ( i = 1; i < length; i++ ) {
-
- // Create converters map
- // with lowercased keys
- if ( i === 1 ) {
- for ( key in s.converters ) {
- if ( typeof key === "string" ) {
- converters[ key.toLowerCase() ] = s.converters[ key ];
- }
- }
- }
-
- // Get the dataTypes
- prev = current;
- current = dataTypes[ i ];
-
- // If current is auto dataType, update it to prev
- if ( current === "*" ) {
- current = prev;
- // If no auto and dataTypes are actually different
- } else if ( prev !== "*" && prev !== current ) {
-
- // Get the converter
- conversion = prev + " " + current;
- conv = converters[ conversion ] || converters[ "* " + current ];
-
- // If there is no direct converter, search transitively
- if ( !conv ) {
- conv2 = undefined;
- for ( conv1 in converters ) {
- tmp = conv1.split( " " );
- if ( tmp[ 0 ] === prev || tmp[ 0 ] === "*" ) {
- conv2 = converters[ tmp[1] + " " + current ];
- if ( conv2 ) {
- conv1 = converters[ conv1 ];
- if ( conv1 === true ) {
- conv = conv2;
- } else if ( conv2 === true ) {
- conv = conv1;
- }
- break;
- }
- }
- }
- }
- // If we found no converter, dispatch an error
- if ( !( conv || conv2 ) ) {
- jQuery.error( "No conversion from " + conversion.replace(" "," to ") );
- }
- // If found converter is not an equivalence
- if ( conv !== true ) {
- // Convert with 1 or 2 converters accordingly
- response = conv ? conv( response ) : conv2( conv1(response) );
- }
- }
- }
- return response;
-}
-
-
-
-
-var jsc = jQuery.now(),
- jsre = /(\=)\?(&|$)|\?\?/i;
-
-// Default jsonp settings
-jQuery.ajaxSetup({
- jsonp: "callback",
- jsonpCallback: function() {
- return jQuery.expando + "_" + ( jsc++ );
- }
-});
-
-// Detect, normalize options and install callbacks for jsonp requests
-jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, jqXHR ) {
-
- var inspectData = s.contentType === "application/x-www-form-urlencoded" &&
- ( typeof s.data === "string" );
-
- if ( s.dataTypes[ 0 ] === "jsonp" ||
- s.jsonp !== false && ( jsre.test( s.url ) ||
- inspectData && jsre.test( s.data ) ) ) {
-
- var responseContainer,
- jsonpCallback = s.jsonpCallback =
- jQuery.isFunction( s.jsonpCallback ) ? s.jsonpCallback() : s.jsonpCallback,
- previous = window[ jsonpCallback ],
- url = s.url,
- data = s.data,
- replace = "$1" + jsonpCallback + "$2";
-
- if ( s.jsonp !== false ) {
- url = url.replace( jsre, replace );
- if ( s.url === url ) {
- if ( inspectData ) {
- data = data.replace( jsre, replace );
- }
- if ( s.data === data ) {
- // Add callback manually
- url += (/\?/.test( url ) ? "&" : "?") + s.jsonp + "=" + jsonpCallback;
- }
- }
- }
-
- s.url = url;
- s.data = data;
-
- // Install callback
- window[ jsonpCallback ] = function( response ) {
- responseContainer = [ response ];
- };
-
- // Clean-up function
- jqXHR.always(function() {
- // Set callback back to previous value
- window[ jsonpCallback ] = previous;
- // Call if it was a function and we have a response
- if ( responseContainer && jQuery.isFunction( previous ) ) {
- window[ jsonpCallback ]( responseContainer[ 0 ] );
- }
- });
-
- // Use data converter to retrieve json after script execution
- s.converters["script json"] = function() {
- if ( !responseContainer ) {
- jQuery.error( jsonpCallback + " was not called" );
- }
- return responseContainer[ 0 ];
- };
-
- // force json dataType
- s.dataTypes[ 0 ] = "json";
-
- // Delegate to script
- return "script";
- }
-});
-
-
-
-
-// Install script dataType
-jQuery.ajaxSetup({
- accepts: {
- script: "text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"
- },
- contents: {
- script: /javascript|ecmascript/
- },
- converters: {
- "text script": function( text ) {
- jQuery.globalEval( text );
- return text;
- }
- }
-});
-
-// Handle cache's special case and global
-jQuery.ajaxPrefilter( "script", function( s ) {
- if ( s.cache === undefined ) {
- s.cache = false;
- }
- if ( s.crossDomain ) {
- s.type = "GET";
- s.global = false;
- }
-});
-
-// Bind script tag hack transport
-jQuery.ajaxTransport( "script", function(s) {
-
- // This transport only deals with cross domain requests
- if ( s.crossDomain ) {
-
- var script,
- head = document.head || document.getElementsByTagName( "head" )[0] || document.documentElement;
-
- return {
-
- send: function( _, callback ) {
-
- script = document.createElement( "script" );
-
- script.async = "async";
-
- if ( s.scriptCharset ) {
- script.charset = s.scriptCharset;
- }
-
- script.src = s.url;
-
- // Attach handlers for all browsers
- script.onload = script.onreadystatechange = function( _, isAbort ) {
-
- if ( isAbort || !script.readyState || /loaded|complete/.test( script.readyState ) ) {
-
- // Handle memory leak in IE
- script.onload = script.onreadystatechange = null;
-
- // Remove the script
- if ( head && script.parentNode ) {
- head.removeChild( script );
- }
-
- // Dereference the script
- script = undefined;
-
- // Callback if not abort
- if ( !isAbort ) {
- callback( 200, "success" );
- }
- }
- };
- // Use insertBefore instead of appendChild to circumvent an IE6 bug.
- // This arises when a base node is used (#2709 and #4378).
- head.insertBefore( script, head.firstChild );
- },
-
- abort: function() {
- if ( script ) {
- script.onload( 0, 1 );
- }
- }
- };
- }
-});
-
-
-
-
-var // #5280: Internet Explorer will keep connections alive if we don't abort on unload
- xhrOnUnloadAbort = window.ActiveXObject ? function() {
- // Abort all pending requests
- for ( var key in xhrCallbacks ) {
- xhrCallbacks[ key ]( 0, 1 );
- }
- } : false,
- xhrId = 0,
- xhrCallbacks;
-
-// Functions to create xhrs
-function createStandardXHR() {
- try {
- return new window.XMLHttpRequest();
- } catch( e ) {}
-}
-
-function createActiveXHR() {
- try {
- return new window.ActiveXObject( "Microsoft.XMLHTTP" );
- } catch( e ) {}
-}
-
-// Create the request object
-// (This is still attached to ajaxSettings for backward compatibility)
-jQuery.ajaxSettings.xhr = window.ActiveXObject ?
- /* Microsoft failed to properly
- * implement the XMLHttpRequest in IE7 (can't request local files),
- * so we use the ActiveXObject when it is available
- * Additionally XMLHttpRequest can be disabled in IE7/IE8 so
- * we need a fallback.
- */
- function() {
- return !this.isLocal && createStandardXHR() || createActiveXHR();
- } :
- // For all other browsers, use the standard XMLHttpRequest object
- createStandardXHR;
-
-// Determine support properties
-(function( xhr ) {
- jQuery.extend( jQuery.support, {
- ajax: !!xhr,
- cors: !!xhr && ( "withCredentials" in xhr )
- });
-})( jQuery.ajaxSettings.xhr() );
-
-// Create transport if the browser can provide an xhr
-if ( jQuery.support.ajax ) {
-
- jQuery.ajaxTransport(function( s ) {
- // Cross domain only allowed if supported through XMLHttpRequest
- if ( !s.crossDomain || jQuery.support.cors ) {
-
- var callback;
-
- return {
- send: function( headers, complete ) {
-
- // Get a new xhr
- var xhr = s.xhr(),
- handle,
- i;
-
- // Open the socket
- // Passing null username, generates a login popup on Opera (#2865)
- if ( s.username ) {
- xhr.open( s.type, s.url, s.async, s.username, s.password );
- } else {
- xhr.open( s.type, s.url, s.async );
- }
-
- // Apply custom fields if provided
- if ( s.xhrFields ) {
- for ( i in s.xhrFields ) {
- xhr[ i ] = s.xhrFields[ i ];
- }
- }
-
- // Override mime type if needed
- if ( s.mimeType && xhr.overrideMimeType ) {
- xhr.overrideMimeType( s.mimeType );
- }
-
- // X-Requested-With header
- // For cross-domain requests, seeing as conditions for a preflight are
- // akin to a jigsaw puzzle, we simply never set it to be sure.
- // (it can always be set on a per-request basis or even using ajaxSetup)
- // For same-domain requests, won't change header if already provided.
- if ( !s.crossDomain && !headers["X-Requested-With"] ) {
- headers[ "X-Requested-With" ] = "XMLHttpRequest";
- }
-
- // Need an extra try/catch for cross domain requests in Firefox 3
- try {
- for ( i in headers ) {
- xhr.setRequestHeader( i, headers[ i ] );
- }
- } catch( _ ) {}
-
- // Do send the request
- // This may raise an exception which is actually
- // handled in jQuery.ajax (so no try/catch here)
- xhr.send( ( s.hasContent && s.data ) || null );
-
- // Listener
- callback = function( _, isAbort ) {
-
- var status,
- statusText,
- responseHeaders,
- responses,
- xml;
-
- // Firefox throws exceptions when accessing properties
- // of an xhr when a network error occured
- // http://helpful.knobs-dials.com/index.php/Component_returned_failure_code:_0x80040111_(NS_ERROR_NOT_AVAILABLE)
- try {
-
- // Was never called and is aborted or complete
- if ( callback && ( isAbort || xhr.readyState === 4 ) ) {
-
- // Only called once
- callback = undefined;
-
- // Do not keep as active anymore
- if ( handle ) {
- xhr.onreadystatechange = jQuery.noop;
- if ( xhrOnUnloadAbort ) {
- delete xhrCallbacks[ handle ];
- }
- }
-
- // If it's an abort
- if ( isAbort ) {
- // Abort it manually if needed
- if ( xhr.readyState !== 4 ) {
- xhr.abort();
- }
- } else {
- status = xhr.status;
- responseHeaders = xhr.getAllResponseHeaders();
- responses = {};
- xml = xhr.responseXML;
-
- // Construct response list
- if ( xml && xml.documentElement /* #4958 */ ) {
- responses.xml = xml;
- }
- responses.text = xhr.responseText;
-
- // Firefox throws an exception when accessing
- // statusText for faulty cross-domain requests
- try {
- statusText = xhr.statusText;
- } catch( e ) {
- // We normalize with Webkit giving an empty statusText
- statusText = "";
- }
-
- // Filter status for non standard behaviors
-
- // If the request is local and we have data: assume a success
- // (success with no data won't get notified, that's the best we
- // can do given current implementations)
- if ( !status && s.isLocal && !s.crossDomain ) {
- status = responses.text ? 200 : 404;
- // IE - #1450: sometimes returns 1223 when it should be 204
- } else if ( status === 1223 ) {
- status = 204;
- }
- }
- }
- } catch( firefoxAccessException ) {
- if ( !isAbort ) {
- complete( -1, firefoxAccessException );
- }
- }
-
- // Call complete if needed
- if ( responses ) {
- complete( status, statusText, responses, responseHeaders );
- }
- };
-
- // if we're in sync mode or it's in cache
- // and has been retrieved directly (IE6 & IE7)
- // we need to manually fire the callback
- if ( !s.async || xhr.readyState === 4 ) {
- callback();
- } else {
- handle = ++xhrId;
- if ( xhrOnUnloadAbort ) {
- // Create the active xhrs callbacks list if needed
- // and attach the unload handler
- if ( !xhrCallbacks ) {
- xhrCallbacks = {};
- jQuery( window ).unload( xhrOnUnloadAbort );
- }
- // Add to list of active xhrs callbacks
- xhrCallbacks[ handle ] = callback;
- }
- xhr.onreadystatechange = callback;
- }
- },
-
- abort: function() {
- if ( callback ) {
- callback(0,1);
- }
- }
- };
- }
- });
-}
-
-
-
-
-var elemdisplay = {},
- iframe, iframeDoc,
- rfxtypes = /^(?:toggle|show|hide)$/,
- rfxnum = /^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,
- timerId,
- fxAttrs = [
- // height animations
- [ "height", "marginTop", "marginBottom", "paddingTop", "paddingBottom" ],
- // width animations
- [ "width", "marginLeft", "marginRight", "paddingLeft", "paddingRight" ],
- // opacity animations
- [ "opacity" ]
- ],
- fxNow;
-
-jQuery.fn.extend({
- show: function( speed, easing, callback ) {
- var elem, display;
-
- if ( speed || speed === 0 ) {
- return this.animate( genFx("show", 3), speed, easing, callback );
-
- } else {
- for ( var i = 0, j = this.length; i < j; i++ ) {
- elem = this[ i ];
-
- if ( elem.style ) {
- display = elem.style.display;
-
- // Reset the inline display of this element to learn if it is
- // being hidden by cascaded rules or not
- if ( !jQuery._data(elem, "olddisplay") && display === "none" ) {
- display = elem.style.display = "";
- }
-
- // Set elements which have been overridden with display: none
- // in a stylesheet to whatever the default browser style is
- // for such an element
- if ( display === "" && jQuery.css(elem, "display") === "none" ) {
- jQuery._data( elem, "olddisplay", defaultDisplay(elem.nodeName) );
- }
- }
- }
-
- // Set the display of most of the elements in a second loop
- // to avoid the constant reflow
- for ( i = 0; i < j; i++ ) {
- elem = this[ i ];
-
- if ( elem.style ) {
- display = elem.style.display;
-
- if ( display === "" || display === "none" ) {
- elem.style.display = jQuery._data( elem, "olddisplay" ) || "";
- }
- }
- }
-
- return this;
- }
- },
-
- hide: function( speed, easing, callback ) {
- if ( speed || speed === 0 ) {
- return this.animate( genFx("hide", 3), speed, easing, callback);
-
- } else {
- var elem, display,
- i = 0,
- j = this.length;
-
- for ( ; i < j; i++ ) {
- elem = this[i];
- if ( elem.style ) {
- display = jQuery.css( elem, "display" );
-
- if ( display !== "none" && !jQuery._data( elem, "olddisplay" ) ) {
- jQuery._data( elem, "olddisplay", display );
- }
- }
- }
-
- // Set the display of the elements in a second loop
- // to avoid the constant reflow
- for ( i = 0; i < j; i++ ) {
- if ( this[i].style ) {
- this[i].style.display = "none";
- }
- }
-
- return this;
- }
- },
-
- // Save the old toggle function
- _toggle: jQuery.fn.toggle,
-
- toggle: function( fn, fn2, callback ) {
- var bool = typeof fn === "boolean";
-
- if ( jQuery.isFunction(fn) && jQuery.isFunction(fn2) ) {
- this._toggle.apply( this, arguments );
-
- } else if ( fn == null || bool ) {
- this.each(function() {
- var state = bool ? fn : jQuery(this).is(":hidden");
- jQuery(this)[ state ? "show" : "hide" ]();
- });
-
- } else {
- this.animate(genFx("toggle", 3), fn, fn2, callback);
- }
-
- return this;
- },
-
- fadeTo: function( speed, to, easing, callback ) {
- return this.filter(":hidden").css("opacity", 0).show().end()
- .animate({opacity: to}, speed, easing, callback);
- },
-
- animate: function( prop, speed, easing, callback ) {
- var optall = jQuery.speed( speed, easing, callback );
-
- if ( jQuery.isEmptyObject( prop ) ) {
- return this.each( optall.complete, [ false ] );
- }
-
- // Do not change referenced properties as per-property easing will be lost
- prop = jQuery.extend( {}, prop );
-
- function doAnimation() {
- // XXX 'this' does not always have a nodeName when running the
- // test suite
-
- if ( optall.queue === false ) {
- jQuery._mark( this );
- }
-
- var opt = jQuery.extend( {}, optall ),
- isElement = this.nodeType === 1,
- hidden = isElement && jQuery(this).is(":hidden"),
- name, val, p, e,
- parts, start, end, unit,
- method;
-
- // will store per property easing and be used to determine when an animation is complete
- opt.animatedProperties = {};
-
- for ( p in prop ) {
-
- // property name normalization
- name = jQuery.camelCase( p );
- if ( p !== name ) {
- prop[ name ] = prop[ p ];
- delete prop[ p ];
- }
-
- val = prop[ name ];
-
- // easing resolution: per property > opt.specialEasing > opt.easing > 'swing' (default)
- if ( jQuery.isArray( val ) ) {
- opt.animatedProperties[ name ] = val[ 1 ];
- val = prop[ name ] = val[ 0 ];
- } else {
- opt.animatedProperties[ name ] = opt.specialEasing && opt.specialEasing[ name ] || opt.easing || 'swing';
- }
-
- if ( val === "hide" && hidden || val === "show" && !hidden ) {
- return opt.complete.call( this );
- }
-
- if ( isElement && ( name === "height" || name === "width" ) ) {
- // Make sure that nothing sneaks out
- // Record all 3 overflow attributes because IE does not
- // change the overflow attribute when overflowX and
- // overflowY are set to the same value
- opt.overflow = [ this.style.overflow, this.style.overflowX, this.style.overflowY ];
-
- // Set display property to inline-block for height/width
- // animations on inline elements that are having width/height animated
- if ( jQuery.css( this, "display" ) === "inline" &&
- jQuery.css( this, "float" ) === "none" ) {
-
- // inline-level elements accept inline-block;
- // block-level elements need to be inline with layout
- if ( !jQuery.support.inlineBlockNeedsLayout || defaultDisplay( this.nodeName ) === "inline" ) {
- this.style.display = "inline-block";
-
- } else {
- this.style.zoom = 1;
- }
- }
- }
- }
-
- if ( opt.overflow != null ) {
- this.style.overflow = "hidden";
- }
-
- for ( p in prop ) {
- e = new jQuery.fx( this, opt, p );
- val = prop[ p ];
-
- if ( rfxtypes.test( val ) ) {
-
- // Tracks whether to show or hide based on private
- // data attached to the element
- method = jQuery._data( this, "toggle" + p ) || ( val === "toggle" ? hidden ? "show" : "hide" : 0 );
- if ( method ) {
- jQuery._data( this, "toggle" + p, method === "show" ? "hide" : "show" );
- e[ method ]();
- } else {
- e[ val ]();
- }
-
- } else {
- parts = rfxnum.exec( val );
- start = e.cur();
-
- if ( parts ) {
- end = parseFloat( parts[2] );
- unit = parts[3] || ( jQuery.cssNumber[ p ] ? "" : "px" );
-
- // We need to compute starting value
- if ( unit !== "px" ) {
- jQuery.style( this, p, (end || 1) + unit);
- start = ( (end || 1) / e.cur() ) * start;
- jQuery.style( this, p, start + unit);
- }
-
- // If a +=/-= token was provided, we're doing a relative animation
- if ( parts[1] ) {
- end = ( (parts[ 1 ] === "-=" ? -1 : 1) * end ) + start;
- }
-
- e.custom( start, end, unit );
-
- } else {
- e.custom( start, val, "" );
- }
- }
- }
-
- // For JS strict compliance
- return true;
- }
-
- return optall.queue === false ?
- this.each( doAnimation ) :
- this.queue( optall.queue, doAnimation );
- },
-
- stop: function( type, clearQueue, gotoEnd ) {
- if ( typeof type !== "string" ) {
- gotoEnd = clearQueue;
- clearQueue = type;
- type = undefined;
- }
- if ( clearQueue && type !== false ) {
- this.queue( type || "fx", [] );
- }
-
- return this.each(function() {
- var index,
- hadTimers = false,
- timers = jQuery.timers,
- data = jQuery._data( this );
-
- // clear marker counters if we know they won't be
- if ( !gotoEnd ) {
- jQuery._unmark( true, this );
- }
-
- function stopQueue( elem, data, index ) {
- var hooks = data[ index ];
- jQuery.removeData( elem, index, true );
- hooks.stop( gotoEnd );
- }
-
- if ( type == null ) {
- for ( index in data ) {
- if ( data[ index ] && data[ index ].stop && index.indexOf(".run") === index.length - 4 ) {
- stopQueue( this, data, index );
- }
- }
- } else if ( data[ index = type + ".run" ] && data[ index ].stop ){
- stopQueue( this, data, index );
- }
-
- for ( index = timers.length; index--; ) {
- if ( timers[ index ].elem === this && (type == null || timers[ index ].queue === type) ) {
- if ( gotoEnd ) {
-
- // force the next step to be the last
- timers[ index ]( true );
- } else {
- timers[ index ].saveState();
- }
- hadTimers = true;
- timers.splice( index, 1 );
- }
- }
-
- // start the next in the queue if the last step wasn't forced
- // timers currently will call their complete callbacks, which will dequeue
- // but only if they were gotoEnd
- if ( !( gotoEnd && hadTimers ) ) {
- jQuery.dequeue( this, type );
- }
- });
- }
-
-});
-
-// Animations created synchronously will run synchronously
-function createFxNow() {
- setTimeout( clearFxNow, 0 );
- return ( fxNow = jQuery.now() );
-}
-
-function clearFxNow() {
- fxNow = undefined;
-}
-
-// Generate parameters to create a standard animation
-function genFx( type, num ) {
- var obj = {};
-
- jQuery.each( fxAttrs.concat.apply([], fxAttrs.slice( 0, num )), function() {
- obj[ this ] = type;
- });
-
- return obj;
-}
-
-// Generate shortcuts for custom animations
-jQuery.each({
- slideDown: genFx( "show", 1 ),
- slideUp: genFx( "hide", 1 ),
- slideToggle: genFx( "toggle", 1 ),
- fadeIn: { opacity: "show" },
- fadeOut: { opacity: "hide" },
- fadeToggle: { opacity: "toggle" }
-}, function( name, props ) {
- jQuery.fn[ name ] = function( speed, easing, callback ) {
- return this.animate( props, speed, easing, callback );
- };
-});
-
-jQuery.extend({
- speed: function( speed, easing, fn ) {
- var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : {
- complete: fn || !fn && easing ||
- jQuery.isFunction( speed ) && speed,
- duration: speed,
- easing: fn && easing || easing && !jQuery.isFunction( easing ) && easing
- };
-
- opt.duration = jQuery.fx.off ? 0 : typeof opt.duration === "number" ? opt.duration :
- opt.duration in jQuery.fx.speeds ? jQuery.fx.speeds[ opt.duration ] : jQuery.fx.speeds._default;
-
- // normalize opt.queue - true/undefined/null -> "fx"
- if ( opt.queue == null || opt.queue === true ) {
- opt.queue = "fx";
- }
-
- // Queueing
- opt.old = opt.complete;
-
- opt.complete = function( noUnmark ) {
- if ( jQuery.isFunction( opt.old ) ) {
- opt.old.call( this );
- }
-
- if ( opt.queue ) {
- jQuery.dequeue( this, opt.queue );
- } else if ( noUnmark !== false ) {
- jQuery._unmark( this );
- }
- };
-
- return opt;
- },
-
- easing: {
- linear: function( p, n, firstNum, diff ) {
- return firstNum + diff * p;
- },
- swing: function( p, n, firstNum, diff ) {
- return ( ( -Math.cos( p*Math.PI ) / 2 ) + 0.5 ) * diff + firstNum;
- }
- },
-
- timers: [],
-
- fx: function( elem, options, prop ) {
- this.options = options;
- this.elem = elem;
- this.prop = prop;
-
- options.orig = options.orig || {};
- }
-
-});
-
-jQuery.fx.prototype = {
- // Simple function for setting a style value
- update: function() {
- if ( this.options.step ) {
- this.options.step.call( this.elem, this.now, this );
- }
-
- ( jQuery.fx.step[ this.prop ] || jQuery.fx.step._default )( this );
- },
-
- // Get the current size
- cur: function() {
- if ( this.elem[ this.prop ] != null && (!this.elem.style || this.elem.style[ this.prop ] == null) ) {
- return this.elem[ this.prop ];
- }
-
- var parsed,
- r = jQuery.css( this.elem, this.prop );
- // Empty strings, null, undefined and "auto" are converted to 0,
- // complex values such as "rotate(1rad)" are returned as is,
- // simple values such as "10px" are parsed to Float.
- return isNaN( parsed = parseFloat( r ) ) ? !r || r === "auto" ? 0 : r : parsed;
- },
-
- // Start an animation from one number to another
- custom: function( from, to, unit ) {
- var self = this,
- fx = jQuery.fx;
-
- this.startTime = fxNow || createFxNow();
- this.end = to;
- this.now = this.start = from;
- this.pos = this.state = 0;
- this.unit = unit || this.unit || ( jQuery.cssNumber[ this.prop ] ? "" : "px" );
-
- function t( gotoEnd ) {
- return self.step( gotoEnd );
- }
-
- t.queue = this.options.queue;
- t.elem = this.elem;
- t.saveState = function() {
- if ( self.options.hide && jQuery._data( self.elem, "fxshow" + self.prop ) === undefined ) {
- jQuery._data( self.elem, "fxshow" + self.prop, self.start );
- }
- };
-
- if ( t() && jQuery.timers.push(t) && !timerId ) {
- timerId = setInterval( fx.tick, fx.interval );
- }
- },
-
- // Simple 'show' function
- show: function() {
- var dataShow = jQuery._data( this.elem, "fxshow" + this.prop );
-
- // Remember where we started, so that we can go back to it later
- this.options.orig[ this.prop ] = dataShow || jQuery.style( this.elem, this.prop );
- this.options.show = true;
-
- // Begin the animation
- // Make sure that we start at a small width/height to avoid any flash of content
- if ( dataShow !== undefined ) {
- // This show is picking up where a previous hide or show left off
- this.custom( this.cur(), dataShow );
- } else {
- this.custom( this.prop === "width" || this.prop === "height" ? 1 : 0, this.cur() );
- }
-
- // Start by showing the element
- jQuery( this.elem ).show();
- },
-
- // Simple 'hide' function
- hide: function() {
- // Remember where we started, so that we can go back to it later
- this.options.orig[ this.prop ] = jQuery._data( this.elem, "fxshow" + this.prop ) || jQuery.style( this.elem, this.prop );
- this.options.hide = true;
-
- // Begin the animation
- this.custom( this.cur(), 0 );
- },
-
- // Each step of an animation
- step: function( gotoEnd ) {
- var p, n, complete,
- t = fxNow || createFxNow(),
- done = true,
- elem = this.elem,
- options = this.options;
-
- if ( gotoEnd || t >= options.duration + this.startTime ) {
- this.now = this.end;
- this.pos = this.state = 1;
- this.update();
-
- options.animatedProperties[ this.prop ] = true;
-
- for ( p in options.animatedProperties ) {
- if ( options.animatedProperties[ p ] !== true ) {
- done = false;
- }
- }
-
- if ( done ) {
- // Reset the overflow
- if ( options.overflow != null && !jQuery.support.shrinkWrapBlocks ) {
-
- jQuery.each( [ "", "X", "Y" ], function( index, value ) {
- elem.style[ "overflow" + value ] = options.overflow[ index ];
- });
- }
-
- // Hide the element if the "hide" operation was done
- if ( options.hide ) {
- jQuery( elem ).hide();
- }
-
- // Reset the properties, if the item has been hidden or shown
- if ( options.hide || options.show ) {
- for ( p in options.animatedProperties ) {
- jQuery.style( elem, p, options.orig[ p ] );
- jQuery.removeData( elem, "fxshow" + p, true );
- // Toggle data is no longer needed
- jQuery.removeData( elem, "toggle" + p, true );
- }
- }
-
- // Execute the complete function
- // in the event that the complete function throws an exception
- // we must ensure it won't be called twice. #5684
-
- complete = options.complete;
- if ( complete ) {
-
- options.complete = false;
- complete.call( elem );
- }
- }
-
- return false;
-
- } else {
- // classical easing cannot be used with an Infinity duration
- if ( options.duration == Infinity ) {
- this.now = t;
- } else {
- n = t - this.startTime;
- this.state = n / options.duration;
-
- // Perform the easing function, defaults to swing
- this.pos = jQuery.easing[ options.animatedProperties[this.prop] ]( this.state, n, 0, 1, options.duration );
- this.now = this.start + ( (this.end - this.start) * this.pos );
- }
- // Perform the next step of the animation
- this.update();
- }
-
- return true;
- }
-};
-
-jQuery.extend( jQuery.fx, {
- tick: function() {
- var timer,
- timers = jQuery.timers,
- i = 0;
-
- for ( ; i < timers.length; i++ ) {
- timer = timers[ i ];
- // Checks the timer has not already been removed
- if ( !timer() && timers[ i ] === timer ) {
- timers.splice( i--, 1 );
- }
- }
-
- if ( !timers.length ) {
- jQuery.fx.stop();
- }
- },
-
- interval: 13,
-
- stop: function() {
- clearInterval( timerId );
- timerId = null;
- },
-
- speeds: {
- slow: 600,
- fast: 200,
- // Default speed
- _default: 400
- },
-
- step: {
- opacity: function( fx ) {
- jQuery.style( fx.elem, "opacity", fx.now );
- },
-
- _default: function( fx ) {
- if ( fx.elem.style && fx.elem.style[ fx.prop ] != null ) {
- fx.elem.style[ fx.prop ] = fx.now + fx.unit;
- } else {
- fx.elem[ fx.prop ] = fx.now;
- }
- }
- }
-});
-
-// Adds width/height step functions
-// Do not set anything below 0
-jQuery.each([ "width", "height" ], function( i, prop ) {
- jQuery.fx.step[ prop ] = function( fx ) {
- jQuery.style( fx.elem, prop, Math.max(0, fx.now) + fx.unit );
- };
-});
-
-if ( jQuery.expr && jQuery.expr.filters ) {
- jQuery.expr.filters.animated = function( elem ) {
- return jQuery.grep(jQuery.timers, function( fn ) {
- return elem === fn.elem;
- }).length;
- };
-}
-
-// Try to restore the default display value of an element
-function defaultDisplay( nodeName ) {
-
- if ( !elemdisplay[ nodeName ] ) {
-
- var body = document.body,
- elem = jQuery( "<" + nodeName + ">" ).appendTo( body ),
- display = elem.css( "display" );
- elem.remove();
-
- // If the simple way fails,
- // get element's real default display by attaching it to a temp iframe
- if ( display === "none" || display === "" ) {
- // No iframe to use yet, so create it
- if ( !iframe ) {
- iframe = document.createElement( "iframe" );
- iframe.frameBorder = iframe.width = iframe.height = 0;
- }
-
- body.appendChild( iframe );
-
- // Create a cacheable copy of the iframe document on first call.
- // IE and Opera will allow us to reuse the iframeDoc without re-writing the fake HTML
- // document to it; WebKit & Firefox won't allow reusing the iframe document.
- if ( !iframeDoc || !iframe.createElement ) {
- iframeDoc = ( iframe.contentWindow || iframe.contentDocument ).document;
- iframeDoc.write( ( document.compatMode === "CSS1Compat" ? "<!doctype html>" : "" ) + "<html><body>" );
- iframeDoc.close();
- }
-
- elem = iframeDoc.createElement( nodeName );
-
- iframeDoc.body.appendChild( elem );
-
- display = jQuery.css( elem, "display" );
- body.removeChild( iframe );
- }
-
- // Store the correct default display
- elemdisplay[ nodeName ] = display;
- }
-
- return elemdisplay[ nodeName ];
-}
-
-
-
-
-var rtable = /^t(?:able|d|h)$/i,
- rroot = /^(?:body|html)$/i;
-
-if ( "getBoundingClientRect" in document.documentElement ) {
- jQuery.fn.offset = function( options ) {
- var elem = this[0], box;
-
- if ( options ) {
- return this.each(function( i ) {
- jQuery.offset.setOffset( this, options, i );
- });
- }
-
- if ( !elem || !elem.ownerDocument ) {
- return null;
- }
-
- if ( elem === elem.ownerDocument.body ) {
- return jQuery.offset.bodyOffset( elem );
- }
-
- try {
- box = elem.getBoundingClientRect();
- } catch(e) {}
-
- var doc = elem.ownerDocument,
- docElem = doc.documentElement;
-
- // Make sure we're not dealing with a disconnected DOM node
- if ( !box || !jQuery.contains( docElem, elem ) ) {
- return box ? { top: box.top, left: box.left } : { top: 0, left: 0 };
- }
-
- var body = doc.body,
- win = getWindow(doc),
- clientTop = docElem.clientTop || body.clientTop || 0,
- clientLeft = docElem.clientLeft || body.clientLeft || 0,
- scrollTop = win.pageYOffset || jQuery.support.boxModel && docElem.scrollTop || body.scrollTop,
- scrollLeft = win.pageXOffset || jQuery.support.boxModel && docElem.scrollLeft || body.scrollLeft,
- top = box.top + scrollTop - clientTop,
- left = box.left + scrollLeft - clientLeft;
-
- return { top: top, left: left };
- };
-
-} else {
- jQuery.fn.offset = function( options ) {
- var elem = this[0];
-
- if ( options ) {
- return this.each(function( i ) {
- jQuery.offset.setOffset( this, options, i );
- });
- }
-
- if ( !elem || !elem.ownerDocument ) {
- return null;
- }
-
- if ( elem === elem.ownerDocument.body ) {
- return jQuery.offset.bodyOffset( elem );
- }
-
- var computedStyle,
- offsetParent = elem.offsetParent,
- prevOffsetParent = elem,
- doc = elem.ownerDocument,
- docElem = doc.documentElement,
- body = doc.body,
- defaultView = doc.defaultView,
- prevComputedStyle = defaultView ? defaultView.getComputedStyle( elem, null ) : elem.currentStyle,
- top = elem.offsetTop,
- left = elem.offsetLeft;
-
- while ( (elem = elem.parentNode) && elem !== body && elem !== docElem ) {
- if ( jQuery.support.fixedPosition && prevComputedStyle.position === "fixed" ) {
- break;
- }
-
- computedStyle = defaultView ? defaultView.getComputedStyle(elem, null) : elem.currentStyle;
- top -= elem.scrollTop;
- left -= elem.scrollLeft;
-
- if ( elem === offsetParent ) {
- top += elem.offsetTop;
- left += elem.offsetLeft;
-
- if ( jQuery.support.doesNotAddBorder && !(jQuery.support.doesAddBorderForTableAndCells && rtable.test(elem.nodeName)) ) {
- top += parseFloat( computedStyle.borderTopWidth ) || 0;
- left += parseFloat( computedStyle.borderLeftWidth ) || 0;
- }
-
- prevOffsetParent = offsetParent;
- offsetParent = elem.offsetParent;
- }
-
- if ( jQuery.support.subtractsBorderForOverflowNotVisible && computedStyle.overflow !== "visible" ) {
- top += parseFloat( computedStyle.borderTopWidth ) || 0;
- left += parseFloat( computedStyle.borderLeftWidth ) || 0;
- }
-
- prevComputedStyle = computedStyle;
- }
-
- if ( prevComputedStyle.position === "relative" || prevComputedStyle.position === "static" ) {
- top += body.offsetTop;
- left += body.offsetLeft;
- }
-
- if ( jQuery.support.fixedPosition && prevComputedStyle.position === "fixed" ) {
- top += Math.max( docElem.scrollTop, body.scrollTop );
- left += Math.max( docElem.scrollLeft, body.scrollLeft );
- }
-
- return { top: top, left: left };
- };
-}
-
-jQuery.offset = {
-
- bodyOffset: function( body ) {
- var top = body.offsetTop,
- left = body.offsetLeft;
-
- if ( jQuery.support.doesNotIncludeMarginInBodyOffset ) {
- top += parseFloat( jQuery.css(body, "marginTop") ) || 0;
- left += parseFloat( jQuery.css(body, "marginLeft") ) || 0;
- }
-
- return { top: top, left: left };
- },
-
- setOffset: function( elem, options, i ) {
- var position = jQuery.css( elem, "position" );
-
- // set position first, in-case top/left are set even on static elem
- if ( position === "static" ) {
- elem.style.position = "relative";
- }
-
- var curElem = jQuery( elem ),
- curOffset = curElem.offset(),
- curCSSTop = jQuery.css( elem, "top" ),
- curCSSLeft = jQuery.css( elem, "left" ),
- calculatePosition = ( position === "absolute" || position === "fixed" ) && jQuery.inArray("auto", [curCSSTop, curCSSLeft]) > -1,
- props = {}, curPosition = {}, curTop, curLeft;
-
- // need to be able to calculate position if either top or left is auto and position is either absolute or fixed
- if ( calculatePosition ) {
- curPosition = curElem.position();
- curTop = curPosition.top;
- curLeft = curPosition.left;
- } else {
- curTop = parseFloat( curCSSTop ) || 0;
- curLeft = parseFloat( curCSSLeft ) || 0;
- }
-
- if ( jQuery.isFunction( options ) ) {
- options = options.call( elem, i, curOffset );
- }
-
- if ( options.top != null ) {
- props.top = ( options.top - curOffset.top ) + curTop;
- }
- if ( options.left != null ) {
- props.left = ( options.left - curOffset.left ) + curLeft;
- }
-
- if ( "using" in options ) {
- options.using.call( elem, props );
- } else {
- curElem.css( props );
- }
- }
-};
-
-
-jQuery.fn.extend({
-
- position: function() {
- if ( !this[0] ) {
- return null;
- }
-
- var elem = this[0],
-
- // Get *real* offsetParent
- offsetParent = this.offsetParent(),
-
- // Get correct offsets
- offset = this.offset(),
- parentOffset = rroot.test(offsetParent[0].nodeName) ? { top: 0, left: 0 } : offsetParent.offset();
-
- // Subtract element margins
- // note: when an element has margin: auto the offsetLeft and marginLeft
- // are the same in Safari causing offset.left to incorrectly be 0
- offset.top -= parseFloat( jQuery.css(elem, "marginTop") ) || 0;
- offset.left -= parseFloat( jQuery.css(elem, "marginLeft") ) || 0;
-
- // Add offsetParent borders
- parentOffset.top += parseFloat( jQuery.css(offsetParent[0], "borderTopWidth") ) || 0;
- parentOffset.left += parseFloat( jQuery.css(offsetParent[0], "borderLeftWidth") ) || 0;
-
- // Subtract the two offsets
- return {
- top: offset.top - parentOffset.top,
- left: offset.left - parentOffset.left
- };
- },
-
- offsetParent: function() {
- return this.map(function() {
- var offsetParent = this.offsetParent || document.body;
- while ( offsetParent && (!rroot.test(offsetParent.nodeName) && jQuery.css(offsetParent, "position") === "static") ) {
- offsetParent = offsetParent.offsetParent;
- }
- return offsetParent;
- });
- }
-});
-
-
-// Create scrollLeft and scrollTop methods
-jQuery.each( ["Left", "Top"], function( i, name ) {
- var method = "scroll" + name;
-
- jQuery.fn[ method ] = function( val ) {
- var elem, win;
-
- if ( val === undefined ) {
- elem = this[ 0 ];
-
- if ( !elem ) {
- return null;
- }
-
- win = getWindow( elem );
-
- // Return the scroll offset
- return win ? ("pageXOffset" in win) ? win[ i ? "pageYOffset" : "pageXOffset" ] :
- jQuery.support.boxModel && win.document.documentElement[ method ] ||
- win.document.body[ method ] :
- elem[ method ];
- }
-
- // Set the scroll offset
- return this.each(function() {
- win = getWindow( this );
-
- if ( win ) {
- win.scrollTo(
- !i ? val : jQuery( win ).scrollLeft(),
- i ? val : jQuery( win ).scrollTop()
- );
-
- } else {
- this[ method ] = val;
- }
- });
- };
-});
-
-function getWindow( elem ) {
- return jQuery.isWindow( elem ) ?
- elem :
- elem.nodeType === 9 ?
- elem.defaultView || elem.parentWindow :
- false;
-}
-
-
-
-
-// Create width, height, innerHeight, innerWidth, outerHeight and outerWidth methods
-jQuery.each([ "Height", "Width" ], function( i, name ) {
-
- var type = name.toLowerCase();
-
- // innerHeight and innerWidth
- jQuery.fn[ "inner" + name ] = function() {
- var elem = this[0];
- return elem ?
- elem.style ?
- parseFloat( jQuery.css( elem, type, "padding" ) ) :
- this[ type ]() :
- null;
- };
-
- // outerHeight and outerWidth
- jQuery.fn[ "outer" + name ] = function( margin ) {
- var elem = this[0];
- return elem ?
- elem.style ?
- parseFloat( jQuery.css( elem, type, margin ? "margin" : "border" ) ) :
- this[ type ]() :
- null;
- };
-
- jQuery.fn[ type ] = function( size ) {
- // Get window width or height
- var elem = this[0];
- if ( !elem ) {
- return size == null ? null : this;
- }
-
- if ( jQuery.isFunction( size ) ) {
- return this.each(function( i ) {
- var self = jQuery( this );
- self[ type ]( size.call( this, i, self[ type ]() ) );
- });
- }
-
- if ( jQuery.isWindow( elem ) ) {
- // Everyone else use document.documentElement or document.body depending on Quirks vs Standards mode
- // 3rd condition allows Nokia support, as it supports the docElem prop but not CSS1Compat
- var docElemProp = elem.document.documentElement[ "client" + name ],
- body = elem.document.body;
- return elem.document.compatMode === "CSS1Compat" && docElemProp ||
- body && body[ "client" + name ] || docElemProp;
-
- // Get document width or height
- } else if ( elem.nodeType === 9 ) {
- // Either scroll[Width/Height] or offset[Width/Height], whichever is greater
- return Math.max(
- elem.documentElement["client" + name],
- elem.body["scroll" + name], elem.documentElement["scroll" + name],
- elem.body["offset" + name], elem.documentElement["offset" + name]
- );
-
- // Get or set width or height on the element
- } else if ( size === undefined ) {
- var orig = jQuery.css( elem, type ),
- ret = parseFloat( orig );
-
- return jQuery.isNumeric( ret ) ? ret : orig;
-
- // Set the width or height on the element (default to pixels if value is unitless)
- } else {
- return this.css( type, typeof size === "string" ? size : size + "px" );
- }
- };
-
-});
-
-
-
-
-// Expose jQuery to the global object
-window.jQuery = window.$ = jQuery;
-
-// Expose jQuery as an AMD module, but only for AMD loaders that
-// understand the issues with loading multiple versions of jQuery
-// in a page that all might call define(). The loader will indicate
-// they have special allowances for multiple jQuery versions by
-// specifying define.amd.jQuery = true. Register as a named module,
-// since jQuery can be concatenated with other files that may use define,
-// but not use a proper concatenation script that understands anonymous
-// AMD modules. A named AMD is safest and most robust way to register.
-// Lowercase jquery is used because AMD module names are derived from
-// file names, and jQuery is normally delivered in a lowercase file name.
-// Do this after creating the global so that if an AMD module wants to call
-// noConflict to hide this version of jQuery, it will work.
-if ( typeof define === "function" && define.amd && define.amd.jQuery ) {
- define( "jquery", [], function () { return jQuery; } );
-}
-
-
-
-})( window );
diff --git a/src/site/resources/js/jquery.min.js b/src/site/resources/js/jquery.min.js
deleted file mode 100644
index 198b3ff..0000000
--- a/src/site/resources/js/jquery.min.js
+++ /dev/null
@@ -1,4 +0,0 @@
-/*! jQuery v1.7.1 jquery.com | jquery.org/license */
-(function(a,b){function cy(a){return f.isWindow(a)?a:a.nodeType===9?a.defaultView||a.parentWindow:!1}function cv(a){if(!ck[a]){var b=c.body,d=f("<"+a+">").appendTo(b),e=d.css("display");d.remove();if(e==="none"||e===""){cl||(cl=c.createElement("iframe"),cl.frameBorder=cl.width=cl.height=0),b.appendChild(cl);if(!cm||!cl.createElement)cm=(cl.contentWindow||cl.contentDocument).document,cm.write((c.compatMode==="CSS1Compat"?"<!doctype html>":"")+"<html><body>"),cm.close();d=cm.createElement(a),cm.body.appendChild(d),e=f.css(d,"display"),b.removeChild(cl)}ck[a]=e}return ck[a]}function cu(a,b){var c={};f.each(cq.concat.apply([],cq.slice(0,b)),function(){c[this]=a});return c}function ct(){cr=b}function cs(){setTimeout(ct,0);return cr=f.now()}function cj(){try{return new a.ActiveXObject("Microsoft.XMLHTTP")}catch(b){}}function ci(){try{return new a.XMLHttpRequest}catch(b){}}function cc(a,c){a.dataFilter&&(c=a.dataFilter(c,a.dataType));var d=a.dataTypes,e={},g,h,i=d.length,j,k=d[0],l,m,n,o,p;for(g=1;g<i;g++){if(g===1)for(h in a.converters)typeof h=="string"&&(e[h.toLowerCase()]=a.converters[h]);l=k,k=d[g];if(k==="*")k=l;else if(l!=="*"&&l!==k){m=l+" "+k,n=e[m]||e["* "+k];if(!n){p=b;for(o in e){j=o.split(" ");if(j[0]===l||j[0]==="*"){p=e[j[1]+" "+k];if(p){o=e[o],o===!0?n=p:p===!0&&(n=o);break}}}}!n&&!p&&f.error("No conversion from "+m.replace(" "," to ")),n!==!0&&(c=n?n(c):p(o(c)))}}return c}function cb(a,c,d){var e=a.contents,f=a.dataTypes,g=a.responseFields,h,i,j,k;for(i in g)i in d&&(c[g[i]]=d[i]);while(f[0]==="*")f.shift(),h===b&&(h=a.mimeType||c.getResponseHeader("content-type"));if(h)for(i in e)if(e[i]&&e[i].test(h)){f.unshift(i);break}if(f[0]in d)j=f[0];else{for(i in d){if(!f[0]||a.converters[i+" "+f[0]]){j=i;break}k||(k=i)}j=j||k}if(j){j!==f[0]&&f.unshift(j);return d[j]}}function ca(a,b,c,d){if(f.isArray(b))f.each(b,function(b,e){c||bE.test(a)?d(a,e):ca(a+"["+(typeof e=="object"||f.isArray(e)?b:"")+"]",e,c,d)});else if(!c&&b!=null&&typeof b=="object")for(var e in b)ca(a+"["+e+"]",b[e],c,d);else d(a,b)}function b_(a,c){var d,e,g=f.ajaxSettings.flatOptions||{};for(d in c)c[d]!==b&&((g[d]?a:e||(e={}))[d]=c[d]);e&&f.extend(!0,a,e)}function b$(a,c,d,e,f,g){f=f||c.dataTypes[0],g=g||{},g[f]=!0;var h=a[f],i=0,j=h?h.length:0,k=a===bT,l;for(;i<j&&(k||!l);i++)l=h[i](c,d,e),typeof l=="string"&&(!k||g[l]?l=b:(c.dataTypes.unshift(l),l=b$(a,c,d,e,l,g)));(k||!l)&&!g["*"]&&(l=b$(a,c,d,e,"*",g));return l}function bZ(a){return function(b,c){typeof b!="string"&&(c=b,b="*");if(f.isFunction(c)){var d=b.toLowerCase().split(bP),e=0,g=d.length,h,i,j;for(;e<g;e++)h=d[e],j=/^\+/.test(h),j&&(h=h.substr(1)||"*"),i=a[h]=a[h]||[],i[j?"unshift":"push"](c)}}}function bC(a,b,c){var d=b==="width"?a.offsetWidth:a.offsetHeight,e=b==="width"?bx:by,g=0,h=e.length;if(d>0){if(c!=="border")for(;g<h;g++)c||(d-=parseFloat(f.css(a,"padding"+e[g]))||0),c==="margin"?d+=parseFloat(f.css(a,c+e[g]))||0:d-=parseFloat(f.css(a,"border"+e[g]+"Width"))||0;return d+"px"}d=bz(a,b,b);if(d<0||d==null)d=a.style[b]||0;d=parseFloat(d)||0;if(c)for(;g<h;g++)d+=parseFloat(f.css(a,"padding"+e[g]))||0,c!=="padding"&&(d+=parseFloat(f.css(a,"border"+e[g]+"Width"))||0),c==="margin"&&(d+=parseFloat(f.css(a,c+e[g]))||0);return d+"px"}function bp(a,b){b.src?f.ajax({url:b.src,async:!1,dataType:"script"}):f.globalEval((b.text||b.textContent||b.innerHTML||"").replace(bf,"/*$0*/")),b.parentNode&&b.parentNode.removeChild(b)}function bo(a){var b=c.createElement("div");bh.appendChild(b),b.innerHTML=a.outerHTML;return b.firstChild}function bn(a){var b=(a.nodeName||"").toLowerCase();b==="input"?bm(a):b!=="script"&&typeof a.getElementsByTagName!="undefined"&&f.grep(a.getElementsByTagName("input"),bm)}function bm(a){if(a.type==="checkbox"||a.type==="radio")a.defaultChecked=a.checked}function bl(a){return typeof a.getElementsByTagName!="undefined"?a.getElementsByTagName("*"):typeof a.querySelectorAll!="undefined"?a.querySelectorAll("*"):[]}function bk(a,b){var c;if(b.nodeType===1){b.clearAttributes&&b.clearAttributes(),b.mergeAttributes&&b.mergeAttributes(a),c=b.nodeName.toLowerCase();if(c==="object")b.outerHTML=a.outerHTML;else if(c!=="input"||a.type!=="checkbox"&&a.type!=="radio"){if(c==="option")b.selected=a.defaultSelected;else if(c==="input"||c==="textarea")b.defaultValue=a.defaultValue}else a.checked&&(b.defaultChecked=b.checked=a.checked),b.value!==a.value&&(b.value=a.value);b.removeAttribute(f.expando)}}function bj(a,b){if(b.nodeType===1&&!!f.hasData(a)){var c,d,e,g=f._data(a),h=f._data(b,g),i=g.events;if(i){delete h.handle,h.events={};for(c in i)for(d=0,e=i[c].length;d<e;d++)f.event.add(b,c+(i[c][d].namespace?".":"")+i[c][d].namespace,i[c][d],i[c][d].data)}h.data&&(h.data=f.extend({},h.data))}}function bi(a,b){return f.nodeName(a,"table")?a.getElementsByTagName("tbody")[0]||a.appendChild(a.ownerDocument.createElement("tbody")):a}function U(a){var b=V.split("|"),c=a.createDocumentFragment();if(c.createElement)while(b.length)c.createElement(b.pop());return c}function T(a,b,c){b=b||0;if(f.isFunction(b))return f.grep(a,function(a,d){var e=!!b.call(a,d,a);return e===c});if(b.nodeType)return f.grep(a,function(a,d){return a===b===c});if(typeof b=="string"){var d=f.grep(a,function(a){return a.nodeType===1});if(O.test(b))return f.filter(b,d,!c);b=f.filter(b,d)}return f.grep(a,function(a,d){return f.inArray(a,b)>=0===c})}function S(a){return!a||!a.parentNode||a.parentNode.nodeType===11}function K(){return!0}function J(){return!1}function n(a,b,c){var d=b+"defer",e=b+"queue",g=b+"mark",h=f._data(a,d);h&&(c==="queue"||!f._data(a,e))&&(c==="mark"||!f._data(a,g))&&setTimeout(function(){!f._data(a,e)&&!f._data(a,g)&&(f.removeData(a,d,!0),h.fire())},0)}function m(a){for(var b in a){if(b==="data"&&f.isEmptyObject(a[b]))continue;if(b!=="toJSON")return!1}return!0}function l(a,c,d){if(d===b&&a.nodeType===1){var e="data-"+c.replace(k,"-$1").toLowerCase();d=a.getAttribute(e);if(typeof d=="string"){try{d=d==="true"?!0:d==="false"?!1:d==="null"?null:f.isNumeric(d)?parseFloat(d):j.test(d)?f.parseJSON(d):d}catch(g){}f.data(a,c,d)}else d=b}return d}function h(a){var b=g[a]={},c,d;a=a.split(/\s+/);for(c=0,d=a.length;c<d;c++)b[a[c]]=!0;return b}var c=a.document,d=a.navigator,e=a.location,f=function(){function J(){if(!e.isReady){try{c.documentElement.doScroll("left")}catch(a){setTimeout(J,1);return}e.ready()}}var e=function(a,b){return new e.fn.init(a,b,h)},f=a.jQuery,g=a.$,h,i=/^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,j=/\S/,k=/^\s+/,l=/\s+$/,m=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,n=/^[\],:{}\s]*$/,o=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,p=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,q=/(?:^|:|,)(?:\s*\[)+/g,r=/(webkit)[ \/]([\w.]+)/,s=/(opera)(?:.*version)?[ \/]([\w.]+)/,t=/(msie) ([\w.]+)/,u=/(mozilla)(?:.*? rv:([\w.]+))?/,v=/-([a-z]|[0-9])/ig,w=/^-ms-/,x=function(a,b){return(b+"").toUpperCase()},y=d.userAgent,z,A,B,C=Object.prototype.toString,D=Object.prototype.hasOwnProperty,E=Array.prototype.push,F=Array.prototype.slice,G=String.prototype.trim,H=Array.prototype.indexOf,I={};e.fn=e.prototype={constructor:e,init:function(a,d,f){var g,h,j,k;if(!a)return this;if(a.nodeType){this.context=this[0]=a,this.length=1;return this}if(a==="body"&&!d&&c.body){this.context=c,this[0]=c.body,this.selector=a,this.length=1;return this}if(typeof a=="string"){a.charAt(0)!=="<"||a.charAt(a.length-1)!==">"||a.length<3?g=i.exec(a):g=[null,a,null];if(g&&(g[1]||!d)){if(g[1]){d=d instanceof e?d[0]:d,k=d?d.ownerDocument||d:c,j=m.exec(a),j?e.isPlainObject(d)?(a=[c.createElement(j[1])],e.fn.attr.call(a,d,!0)):a=[k.createElement(j[1])]:(j=e.buildFragment([g[1]],[k]),a=(j.cacheable?e.clone(j.fragment):j.fragment).childNodes);return e.merge(this,a)}h=c.getElementById(g[2]);if(h&&h.parentNode){if(h.id!==g[2])return f.find(a);this.length=1,this[0]=h}this.context=c,this.selector=a;return this}return!d||d.jquery?(d||f).find(a):this.constructor(d).find(a)}if(e.isFunction(a))return f.ready(a);a.selector!==b&&(this.selector=a.selector,this.context=a.context);return e.makeArray(a,this)},selector:"",jquery:"1.7.1",length:0,size:function(){return this.length},toArray:function(){return F.call(this,0)},get:function(a){return a==null?this.toArray():a<0?this[this.length+a]:this[a]},pushStack:function(a,b,c){var d=this.constructor();e.isArray(a)?E.apply(d,a):e.merge(d,a),d.prevObject=this,d.context=this.context,b==="find"?d.selector=this.selector+(this.selector?" ":"")+c:b&&(d.selector=this.selector+"."+b+"("+c+")");return d},each:function(a,b){return e.each(this,a,b)},ready:function(a){e.bindReady(),A.add(a);return this},eq:function(a){a=+a;return a===-1?this.slice(a):this.slice(a,a+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(F.apply(this,arguments),"slice",F.call(arguments).join(","))},map:function(a){return this.pushStack(e.map(this,function(b,c){return a.call(b,c,b)}))},end:function(){return this.prevObject||this.constructor(null)},push:E,sort:[].sort,splice:[].splice},e.fn.init.prototype=e.fn,e.extend=e.fn.extend=function(){var a,c,d,f,g,h,i=arguments[0]||{},j=1,k=arguments.length,l=!1;typeof i=="boolean"&&(l=i,i=arguments[1]||{},j=2),typeof i!="object"&&!e.isFunction(i)&&(i={}),k===j&&(i=this,--j);for(;j<k;j++)if((a=arguments[j])!=null)for(c in a){d=i[c],f=a[c];if(i===f)continue;l&&f&&(e.isPlainObject(f)||(g=e.isArray(f)))?(g?(g=!1,h=d&&e.isArray(d)?d:[]):h=d&&e.isPlainObject(d)?d:{},i[c]=e.extend(l,h,f)):f!==b&&(i[c]=f)}return i},e.extend({noConflict:function(b){a.$===e&&(a.$=g),b&&a.jQuery===e&&(a.jQuery=f);return e},isReady:!1,readyWait:1,holdReady:function(a){a?e.readyWait++:e.ready(!0)},ready:function(a){if(a===!0&&!--e.readyWait||a!==!0&&!e.isReady){if(!c.body)return setTimeout(e.ready,1);e.isReady=!0;if(a!==!0&&--e.readyWait>0)return;A.fireWith(c,[e]),e.fn.trigger&&e(c).trigger("ready").off("ready")}},bindReady:function(){if(!A){A=e.Callbacks("once memory");if(c.readyState==="complete")return setTimeout(e.ready,1);if(c.addEventListener)c.addEventListener("DOMContentLoaded",B,!1),a.addEventListener("load",e.ready,!1);else if(c.attachEvent){c.attachEvent("onreadystatechange",B),a.attachEvent("onload",e.ready);var b=!1;try{b=a.frameElement==null}catch(d){}c.documentElement.doScroll&&b&&J()}}},isFunction:function(a){return e.type(a)==="function"},isArray:Array.isArray||function(a){return e.type(a)==="array"},isWindow:function(a){return a&&typeof a=="object"&&"setInterval"in a},isNumeric:function(a){return!isNaN(parseFloat(a))&&isFinite(a)},type:function(a){return a==null?String(a):I[C.call(a)]||"object"},isPlainObject:function(a){if(!a||e.type(a)!=="object"||a.nodeType||e.isWindow(a))return!1;try{if(a.constructor&&!D.call(a,"constructor")&&!D.call(a.constructor.prototype,"isPrototypeOf"))return!1}catch(c){return!1}var d;for(d in a);return d===b||D.call(a,d)},isEmptyObject:function(a){for(var b in a)return!1;return!0},error:function(a){throw new Error(a)},parseJSON:function(b){if(typeof b!="string"||!b)return null;b=e.trim(b);if(a.JSON&&a.JSON.parse)return a.JSON.parse(b);if(n.test(b.replace(o,"@").replace(p,"]").replace(q,"")))return(new Function("return "+b))();e.error("Invalid JSON: "+b)},parseXML:function(c){var d,f;try{a.DOMParser?(f=new DOMParser,d=f.parseFromString(c,"text/xml")):(d=new ActiveXObject("Microsoft.XMLDOM"),d.async="false",d.loadXML(c))}catch(g){d=b}(!d||!d.documentElement||d.getElementsByTagName("parsererror").length)&&e.error("Invalid XML: "+c);return d},noop:function(){},globalEval:function(b){b&&j.test(b)&&(a.execScript||function(b){a.eval.call(a,b)})(b)},camelCase:function(a){return a.replace(w,"ms-").replace(v,x)},nodeName:function(a,b){return a.nodeName&&a.nodeName.toUpperCase()===b.toUpperCase()},each:function(a,c,d){var f,g=0,h=a.length,i=h===b||e.isFunction(a);if(d){if(i){for(f in a)if(c.apply(a[f],d)===!1)break}else for(;g<h;)if(c.apply(a[g++],d)===!1)break}else if(i){for(f in a)if(c.call(a[f],f,a[f])===!1)break}else for(;g<h;)if(c.call(a[g],g,a[g++])===!1)break;return a},trim:G?function(a){return a==null?"":G.call(a)}:function(a){return a==null?"":(a+"").replace(k,"").replace(l,"")},makeArray:function(a,b){var c=b||[];if(a!=null){var d=e.type(a);a.length==null||d==="string"||d==="function"||d==="regexp"||e.isWindow(a)?E.call(c,a):e.merge(c,a)}return c},inArray:function(a,b,c){var d;if(b){if(H)return H.call(b,a,c);d=b.length,c=c?c<0?Math.max(0,d+c):c:0;for(;c<d;c++)if(c in b&&b[c]===a)return c}return-1},merge:function(a,c){var d=a.length,e=0;if(typeof c.length=="number")for(var f=c.length;e<f;e++)a[d++]=c[e];else while(c[e]!==b)a[d++]=c[e++];a.length=d;return a},grep:function(a,b,c){var d=[],e;c=!!c;for(var f=0,g=a.length;f<g;f++)e=!!b(a[f],f),c!==e&&d.push(a[f]);return d},map:function(a,c,d){var f,g,h=[],i=0,j=a.length,k=a instanceof e||j!==b&&typeof j=="number"&&(j>0&&a[0]&&a[j-1]||j===0||e.isArray(a));if(k)for(;i<j;i++)f=c(a[i],i,d),f!=null&&(h[h.length]=f);else for(g in a)f=c(a[g],g,d),f!=null&&(h[h.length]=f);return h.concat.apply([],h)},guid:1,proxy:function(a,c){if(typeof c=="string"){var d=a[c];c=a,a=d}if(!e.isFunction(a))return b;var f=F.call(arguments,2),g=function(){return a.apply(c,f.concat(F.call(arguments)))};g.guid=a.guid=a.guid||g.guid||e.guid++;return g},access:function(a,c,d,f,g,h){var i=a.length;if(typeof c=="object"){for(var j in c)e.access(a,j,c[j],f,g,d);return a}if(d!==b){f=!h&&f&&e.isFunction(d);for(var k=0;k<i;k++)g(a[k],c,f?d.call(a[k],k,g(a[k],c)):d,h);return a}return i?g(a[0],c):b},now:function(){return(new Date).getTime()},uaMatch:function(a){a=a.toLowerCase();var b=r.exec(a)||s.exec(a)||t.exec(a)||a.indexOf("compatible")<0&&u.exec(a)||[];return{browser:b[1]||"",version:b[2]||"0"}},sub:function(){function a(b,c){return new a.fn.init(b,c)}e.extend(!0,a,this),a.superclass=this,a.fn=a.prototype=this(),a.fn.constructor=a,a.sub=this.sub,a.fn.init=function(d,f){f&&f instanceof e&&!(f instanceof a)&&(f=a(f));return e.fn.init.call(this,d,f,b)},a.fn.init.prototype=a.fn;var b=a(c);return a},browser:{}}),e.each("Boolean Number String Function Array Date RegExp Object".split(" "),function(a,b){I["[object "+b+"]"]=b.toLowerCase()}),z=e.uaMatch(y),z.browser&&(e.browser[z.browser]=!0,e.browser.version=z.version),e.browser.webkit&&(e.browser.safari=!0),j.test(" ")&&(k=/^[\s\xA0]+/,l=/[\s\xA0]+$/),h=e(c),c.addEventListener?B=function(){c.removeEventListener("DOMContentLoaded",B,!1),e.ready()}:c.attachEvent&&(B=function(){c.readyState==="complete"&&(c.detachEvent("onreadystatechange",B),e.ready())});return e}(),g={};f.Callbacks=function(a){a=a?g[a]||h(a):{};var c=[],d=[],e,i,j,k,l,m=function(b){var d,e,g,h,i;for(d=0,e=b.length;d<e;d++)g=b[d],h=f.type(g),h==="array"?m(g):h==="function"&&(!a.unique||!o.has(g))&&c.push(g)},n=function(b,f){f=f||[],e=!a.memory||[b,f],i=!0,l=j||0,j=0,k=c.length;for(;c&&l<k;l++)if(c[l].apply(b,f)===!1&&a.stopOnFalse){e=!0;break}i=!1,c&&(a.once?e===!0?o.disable():c=[]:d&&d.length&&(e=d.shift(),o.fireWith(e[0],e[1])))},o={add:function(){if(c){var a=c.length;m(arguments),i?k=c.length:e&&e!==!0&&(j=a,n(e[0],e[1]))}return this},remove:function(){if(c){var b=arguments,d=0,e=b.length;for(;d<e;d++)for(var f=0;f<c.length;f++)if(b[d]===c[f]){i&&f<=k&&(k--,f<=l&&l--),c.splice(f--,1);if(a.unique)break}}return this},has:function(a){if(c){var b=0,d=c.length;for(;b<d;b++)if(a===c[b])return!0}return!1},empty:function(){c=[];return this},disable:function(){c=d=e=b;return this},disabled:function(){return!c},lock:function(){d=b,(!e||e===!0)&&o.disable();return this},locked:function(){return!d},fireWith:function(b,c){d&&(i?a.once||d.push([b,c]):(!a.once||!e)&&n(b,c));return this},fire:function(){o.fireWith(this,arguments);return this},fired:function(){return!!e}};return o};var i=[].slice;f.extend({Deferred:function(a){var b=f.Callbacks("once memory"),c=f.Callbacks("once memory"),d=f.Callbacks("memory"),e="pending",g={resolve:b,reject:c,notify:d},h={done:b.add,fail:c.add,progress:d.add,state:function(){return e},isResolved:b.fired,isRejected:c.fired,then:function(a,b,c){i.done(a).fail(b).progress(c);return this},always:function(){i.done.apply(i,arguments).fail.apply(i,arguments);return this},pipe:function(a,b,c){return f.Deferred(function(d){f.each({done:[a,"resolve"],fail:[b,"reject"],progress:[c,"notify"]},function(a,b){var c=b[0],e=b[1],g;f.isFunction(c)?i[a](function(){g=c.apply(this,arguments),g&&f.isFunction(g.promise)?g.promise().then(d.resolve,d.reject,d.notify):d[e+"With"](this===i?d:this,[g])}):i[a](d[e])})}).promise()},promise:function(a){if(a==null)a=h;else for(var b in h)a[b]=h[b];return a}},i=h.promise({}),j;for(j in g)i[j]=g[j].fire,i[j+"With"]=g[j].fireWith;i.done(function(){e="resolved"},c.disable,d.lock).fail(function(){e="rejected"},b.disable,d.lock),a&&a.call(i,i);return i},when:function(a){function m(a){return function(b){e[a]=arguments.length>1?i.call(arguments,0):b,j.notifyWith(k,e)}}function l(a){return function(c){b[a]=arguments.length>1?i.call(arguments,0):c,--g||j.resolveWith(j,b)}}var b=i.call(arguments,0),c=0,d=b.length,e=Array(d),g=d,h=d,j=d<=1&&a&&f.isFunction(a.promise)?a:f.Deferred(),k=j.promise();if(d>1){for(;c<d;c++)b[c]&&b[c].promise&&f.isFunction(b[c].promise)?b[c].promise().then(l(c),j.reject,m(c)):--g;g||j.resolveWith(j,b)}else j!==a&&j.resolveWith(j,d?[a]:[]);return k}}),f.support=function(){var b,d,e,g,h,i,j,k,l,m,n,o,p,q=c.createElement("div"),r=c.documentElement;q.setAttribute("className","t"),q.innerHTML=" <link/><table></table><a href='/a' style='top:1px;float:left;opacity:.55;'>a</a><input type='checkbox'/>",d=q.getElementsByTagName("*"),e=q.getElementsByTagName("a")[0];if(!d||!d.length||!e)return{};g=c.createElement("select"),h=g.appendChild(c.createElement("option")),i=q.getElementsByTagName("input")[0],b={leadingWhitespace:q.firstChild.nodeType===3,tbody:!q.getElementsByTagName("tbody").length,htmlSerialize:!!q.getElementsByTagName("link").length,style:/top/.test(e.getAttribute("style")),hrefNormalized:e.getAttribute("href")==="/a",opacity:/^0.55/.test(e.style.opacity),cssFloat:!!e.style.cssFloat,checkOn:i.value==="on",optSelected:h.selected,getSetAttribute:q.className!=="t",enctype:!!c.createElement("form").enctype,html5Clone:c.createElement("nav").cloneNode(!0).outerHTML!=="<:nav></:nav>",submitBubbles:!0,changeBubbles:!0,focusinBubbles:!1,deleteExpando:!0,noCloneEvent:!0,inlineBlockNeedsLayout:!1,shrinkWrapBlocks:!1,reliableMarginRight:!0},i.checked=!0,b.noCloneChecked=i.cloneNode(!0).checked,g.disabled=!0,b.optDisabled=!h.disabled;try{delete q.test}catch(s){b.deleteExpando=!1}!q.addEventListener&&q.attachEvent&&q.fireEvent&&(q.attachEvent("onclick",function(){b.noCloneEvent=!1}),q.cloneNode(!0).fireEvent("onclick")),i=c.createElement("input"),i.value="t",i.setAttribute("type","radio"),b.radioValue=i.value==="t",i.setAttribute("checked","checked"),q.appendChild(i),k=c.createDocumentFragment(),k.appendChild(q.lastChild),b.checkClone=k.cloneNode(!0).cloneNode(!0).lastChild.checked,b.appendChecked=i.checked,k.removeChild(i),k.appendChild(q),q.innerHTML="",a.getComputedStyle&&(j=c.createElement("div"),j.style.width="0",j.style.marginRight="0",q.style.width="2px",q.appendChild(j),b.reliableMarginRight=(parseInt((a.getComputedStyle(j,null)||{marginRight:0}).marginRight,10)||0)===0);if(q.attachEvent)for(o in{submit:1,change:1,focusin:1})n="on"+o,p=n in q,p||(q.setAttribute(n,"return;"),p=typeof q[n]=="function"),b[o+"Bubbles"]=p;k.removeChild(q),k=g=h=j=q=i=null,f(function(){var a,d,e,g,h,i,j,k,m,n,o,r=c.getElementsByTagName("body")[0];!r||(j=1,k="position:absolute;top:0;left:0;width:1px;height:1px;margin:0;",m="visibility:hidden;border:0;",n="style='"+k+"border:5px solid #000;padding:0;'",o="<div "+n+"><div></div></div>"+"<table "+n+" cellpadding='0' cellspacing='0'>"+"<tr><td></td></tr></table>",a=c.createElement("div"),a.style.cssText=m+"width:0;height:0;position:static;top:0;margin-top:"+j+"px",r.insertBefore(a,r.firstChild),q=c.createElement("div"),a.appendChild(q),q.innerHTML="<table><tr><td style='padding:0;border:0;display:none'></td><td>t</td></tr></table>",l=q.getElementsByTagName("td"),p=l[0].offsetHeight===0,l[0].style.display="",l[1].style.display="none",b.reliableHiddenOffsets=p&&l[0].offsetHeight===0,q.innerHTML="",q.style.width=q.style.paddingLeft="1px",f.boxModel=b.boxModel=q.offsetWidth===2,typeof q.style.zoom!="undefined"&&(q.style.display="inline",q.style.zoom=1,b.inlineBlockNeedsLayout=q.offsetWidth===2,q.style.display="",q.innerHTML="<div style='width:4px;'></div>",b.shrinkWrapBlocks=q.offsetWidth!==2),q.style.cssText=k+m,q.innerHTML=o,d=q.firstChild,e=d.firstChild,h=d.nextSibling.firstChild.firstChild,i={doesNotAddBorder:e.offsetTop!==5,doesAddBorderForTableAndCells:h.offsetTop===5},e.style.position="fixed",e.style.top="20px",i.fixedPosition=e.offsetTop===20||e.offsetTop===15,e.style.position=e.style.top="",d.style.overflow="hidden",d.style.position="relative",i.subtractsBorderForOverflowNotVisible=e.offsetTop===-5,i.doesNotIncludeMarginInBodyOffset=r.offsetTop!==j,r.removeChild(a),q=a=null,f.extend(b,i))});return b}();var j=/^(?:\{.*\}|\[.*\])$/,k=/([A-Z])/g;f.extend({cache:{},uuid:0,expando:"jQuery"+(f.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:!0},hasData:function(a){a=a.nodeType?f.cache[a[f.expando]]:a[f.expando];return!!a&&!m(a)},data:function(a,c,d,e){if(!!f.acceptData(a)){var g,h,i,j=f.expando,k=typeof c=="string",l=a.nodeType,m=l?f.cache:a,n=l?a[j]:a[j]&&j,o=c==="events";if((!n||!m[n]||!o&&!e&&!m[n].data)&&k&&d===b)return;n||(l?a[j]=n=++f.uuid:n=j),m[n]||(m[n]={},l||(m[n].toJSON=f.noop));if(typeof c=="object"||typeof c=="function")e?m[n]=f.extend(m[n],c):m[n].data=f.extend(m[n].data,c);g=h=m[n],e||(h.data||(h.data={}),h=h.data),d!==b&&(h[f.camelCase(c)]=d);if(o&&!h[c])return g.events;k?(i=h[c],i==null&&(i=h[f.camelCase(c)])):i=h;return i}},removeData:function(a,b,c){if(!!f.acceptData(a)){var d,e,g,h=f.expando,i=a.nodeType,j=i?f.cache:a,k=i?a[h]:h;if(!j[k])return;if(b){d=c?j[k]:j[k].data;if(d){f.isArray(b)||(b in d?b=[b]:(b=f.camelCase(b),b in d?b=[b]:b=b.split(" ")));for(e=0,g=b.length;e<g;e++)delete d[b[e]];if(!(c?m:f.isEmptyObject)(d))return}}if(!c){delete j[k].data;if(!m(j[k]))return}f.support.deleteExpando||!j.setInterval?delete j[k]:j[k]=null,i&&(f.support.deleteExpando?delete a[h]:a.removeAttribute?a.removeAttribute(h):a[h]=null)}},_data:function(a,b,c){return f.data(a,b,c,!0)},acceptData:function(a){if(a.nodeName){var b=f.noData[a.nodeName.toLowerCase()];if(b)return b!==!0&&a.getAttribute("classid")===b}return!0}}),f.fn.extend({data:function(a,c){var d,e,g,h=null;if(typeof a=="undefined"){if(this.length){h=f.data(this[0]);if(this[0].nodeType===1&&!f._data(this[0],"parsedAttrs")){e=this[0].attributes;for(var i=0,j=e.length;i<j;i++)g=e[i].name,g.indexOf("data-")===0&&(g=f.camelCase(g.substring(5)),l(this[0],g,h[g]));f._data(this[0],"parsedAttrs",!0)}}return h}if(typeof a=="object")return this.each(function(){f.data(this,a)});d=a.split("."),d[1]=d[1]?"."+d[1]:"";if(c===b){h=this.triggerHandler("getData"+d[1]+"!",[d[0]]),h===b&&this.length&&(h=f.data(this[0],a),h=l(this[0],a,h));return h===b&&d[1]?this.data(d[0]):h}return this.each(function(){var b=f(this),e=[d[0],c];b.triggerHandler("setData"+d[1]+"!",e),f.data(this,a,c),b.triggerHandler("changeData"+d[1]+"!",e)})},removeData:function(a){return this.each(function(){f.removeData(this,a)})}}),f.extend({_mark:function(a,b){a&&(b=(b||"fx")+"mark",f._data(a,b,(f._data(a,b)||0)+1))},_unmark:function(a,b,c){a!==!0&&(c=b,b=a,a=!1);if(b){c=c||"fx";var d=c+"mark",e=a?0:(f._data(b,d)||1)-1;e?f._data(b,d,e):(f.removeData(b,d,!0),n(b,c,"mark"))}},queue:function(a,b,c){var d;if(a){b=(b||"fx")+"queue",d=f._data(a,b),c&&(!d||f.isArray(c)?d=f._data(a,b,f.makeArray(c)):d.push(c));return d||[]}},dequeue:function(a,b){b=b||"fx";var c=f.queue(a,b),d=c.shift(),e={};d==="inprogress"&&(d=c.shift()),d&&(b==="fx"&&c.unshift("inprogress"),f._data(a,b+".run",e),d.call(a,function(){f.dequeue(a,b)},e)),c.length||(f.removeData(a,b+"queue "+b+".run",!0),n(a,b,"queue"))}}),f.fn.extend({queue:function(a,c){typeof a!="string"&&(c=a,a="fx");if(c===b)return f.queue(this[0],a);return this.each(function(){var b=f.queue(this,a,c);a==="fx"&&b[0]!=="inprogress"&&f.dequeue(this,a)})},dequeue:function(a){return this.each(function(){f.dequeue(this,a)})},delay:function(a,b){a=f.fx?f.fx.speeds[a]||a:a,b=b||"fx";return this.queue(b,function(b,c){var d=setTimeout(b,a);c.stop=function(){clearTimeout(d)}})},clearQueue:function(a){return this.queue(a||"fx",[])},promise:function(a,c){function m(){--h||d.resolveWith(e,[e])}typeof a!="string"&&(c=a,a=b),a=a||"fx";var d=f.Deferred(),e=this,g=e.length,h=1,i=a+"defer",j=a+"queue",k=a+"mark",l;while(g--)if(l=f.data(e[g],i,b,!0)||(f.data(e[g],j,b,!0)||f.data(e[g],k,b,!0))&&f.data(e[g],i,f.Callbacks("once memory"),!0))h++,l.add(m);m();return d.promise()}});var o=/[\n\t\r]/g,p=/\s+/,q=/\r/g,r=/^(?:button|input)$/i,s=/^(?:button|input|object|select|textarea)$/i,t=/^a(?:rea)?$/i,u=/^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i,v=f.support.getSetAttribute,w,x,y;f.fn.extend({attr:function(a,b){return f.access(this,a,b,!0,f.attr)},removeAttr:function(a){return this.each(function(){f.removeAttr(this,a)})},prop:function(a,b){return f.access(this,a,b,!0,f.prop)},removeProp:function(a){a=f.propFix[a]||a;return this.each(function(){try{this[a]=b,delete this[a]}catch(c){}})},addClass:function(a){var b,c,d,e,g,h,i;if(f.isFunction(a))return this.each(function(b){f(this).addClass(a.call(this,b,this.className))});if(a&&typeof a=="string"){b=a.split(p);for(c=0,d=this.length;c<d;c++){e=this[c];if(e.nodeType===1)if(!e.className&&b.length===1)e.className=a;else{g=" "+e.className+" ";for(h=0,i=b.length;h<i;h++)~g.indexOf(" "+b[h]+" ")||(g+=b[h]+" ");e.className=f.trim(g)}}}return this},removeClass:function(a){var c,d,e,g,h,i,j;if(f.isFunction(a))return this.each(function(b){f(this).removeClass(a.call(this,b,this.className))});if(a&&typeof a=="string"||a===b){c=(a||"").split(p);for(d=0,e=this.length;d<e;d++){g=this[d];if(g.nodeType===1&&g.className)if(a){h=(" "+g.className+" ").replace(o," ");for(i=0,j=c.length;i<j;i++)h=h.replace(" "+c[i]+" "," ");g.className=f.trim(h)}else g.className=""}}return this},toggleClass:function(a,b){var c=typeof a,d=typeof b=="boolean";if(f.isFunction(a))return this.each(function(c){f(this).toggleClass(a.call(this,c,this.className,b),b)});return this.each(function(){if(c==="string"){var e,g=0,h=f(this),i=b,j=a.split(p);while(e=j[g++])i=d?i:!h.hasClass(e),h[i?"addClass":"removeClass"](e)}else if(c==="undefined"||c==="boolean")this.className&&f._data(this,"__className__",this.className),this.className=this.className||a===!1?"":f._data(this,"__className__")||""})},hasClass:function(a){var b=" "+a+" ",c=0,d=this.length;for(;c<d;c++)if(this[c].nodeType===1&&(" "+this[c].className+" ").replace(o," ").indexOf(b)>-1)return!0;return!1},val:function(a){var c,d,e,g=this[0];{if(!!arguments.length){e=f.isFunction(a);return this.each(function(d){var g=f(this),h;if(this.nodeType===1){e?h=a.call(this,d,g.val()):h=a,h==null?h="":typeof h=="number"?h+="":f.isArray(h)&&(h=f.map(h,function(a){return a==null?"":a+""})),c=f.valHooks[this.nodeName.toLowerCase()]||f.valHooks[this.type];if(!c||!("set"in c)||c.set(this,h,"value")===b)this.value=h}})}if(g){c=f.valHooks[g.nodeName.toLowerCase()]||f.valHooks[g.type];if(c&&"get"in c&&(d=c.get(g,"value"))!==b)return d;d=g.value;return typeof d=="string"?d.replace(q,""):d==null?"":d}}}}),f.extend({valHooks:{option:{get:function(a){var b=a.attributes.value;return!b||b.specified?a.value:a.text}},select:{get:function(a){var b,c,d,e,g=a.selectedIndex,h=[],i=a.options,j=a.type==="select-one";if(g<0)return null;c=j?g:0,d=j?g+1:i.length;for(;c<d;c++){e=i[c];if(e.selected&&(f.support.optDisabled?!e.disabled:e.getAttribute("disabled")===null)&&(!e.parentNode.disabled||!f.nodeName(e.parentNode,"optgroup"))){b=f(e).val();if(j)return b;h.push(b)}}if(j&&!h.length&&i.length)return f(i[g]).val();return h},set:function(a,b){var c=f.makeArray(b);f(a).find("option").each(function(){this.selected=f.inArray(f(this).val(),c)>=0}),c.length||(a.selectedIndex=-1);return c}}},attrFn:{val:!0,css:!0,html:!0,text:!0,data:!0,width:!0,height:!0,offset:!0},attr:function(a,c,d,e){var g,h,i,j=a.nodeType;if(!!a&&j!==3&&j!==8&&j!==2){if(e&&c in f.attrFn)return f(a)[c](d);if(typeof a.getAttribute=="undefined")return f.prop(a,c,d);i=j!==1||!f.isXMLDoc(a),i&&(c=c.toLowerCase(),h=f.attrHooks[c]||(u.test(c)?x:w));if(d!==b){if(d===null){f.removeAttr(a,c);return}if(h&&"set"in h&&i&&(g=h.set(a,d,c))!==b)return g;a.setAttribute(c,""+d);return d}if(h&&"get"in h&&i&&(g=h.get(a,c))!==null)return g;g=a.getAttribute(c);return g===null?b:g}},removeAttr:function(a,b){var c,d,e,g,h=0;if(b&&a.nodeType===1){d=b.toLowerCase().split(p),g=d.length;for(;h<g;h++)e=d[h],e&&(c=f.propFix[e]||e,f.attr(a,e,""),a.removeAttribute(v?e:c),u.test(e)&&c in a&&(a[c]=!1))}},attrHooks:{type:{set:function(a,b){if(r.test(a.nodeName)&&a.parentNode)f.error("type property can't be changed");else if(!f.support.radioValue&&b==="radio"&&f.nodeName(a,"input")){var c=a.value;a.setAttribute("type",b),c&&(a.value=c);return b}}},value:{get:function(a,b){if(w&&f.nodeName(a,"button"))return w.get(a,b);return b in a?a.value:null},set:function(a,b,c){if(w&&f.nodeName(a,"button"))return w.set(a,b,c);a.value=b}}},propFix:{tabindex:"tabIndex",readonly:"readOnly","for":"htmlFor","class":"className",maxlength:"maxLength",cellspacing:"cellSpacing",cellpadding:"cellPadding",rowspan:"rowSpan",colspan:"colSpan",usemap:"useMap",frameborder:"frameBorder",contenteditable:"contentEditable"},prop:function(a,c,d){var e,g,h,i=a.nodeType;if(!!a&&i!==3&&i!==8&&i!==2){h=i!==1||!f.isXMLDoc(a),h&&(c=f.propFix[c]||c,g=f.propHooks[c]);return d!==b?g&&"set"in g&&(e=g.set(a,d,c))!==b?e:a[c]=d:g&&"get"in g&&(e=g.get(a,c))!==null?e:a[c]}},propHooks:{tabIndex:{get:function(a){var c=a.getAttributeNode("tabindex");return c&&c.specified?parseInt(c.value,10):s.test(a.nodeName)||t.test(a.nodeName)&&a.href?0:b}}}}),f.attrHooks.tabindex=f.propHooks.tabIndex,x={get:function(a,c){var d,e=f.prop(a,c);return e===!0||typeof e!="boolean"&&(d=a.getAttributeNode(c))&&d.nodeValue!==!1?c.toLowerCase():b},set:function(a,b,c){var d;b===!1?f.removeAttr(a,c):(d=f.propFix[c]||c,d in a&&(a[d]=!0),a.setAttribute(c,c.toLowerCase()));return c}},v||(y={name:!0,id:!0},w=f.valHooks.button={get:function(a,c){var d;d=a.getAttributeNode(c);return d&&(y[c]?d.nodeValue!=="":d.specified)?d.nodeValue:b},set:function(a,b,d){var e=a.getAttributeNode(d);e||(e=c.createAttribute(d),a.setAttributeNode(e));return e.nodeValue=b+""}},f.attrHooks.tabindex.set=w.set,f.each(["width","height"],function(a,b){f.attrHooks[b]=f.extend(f.attrHooks[b],{set:function(a,c){if(c===""){a.setAttribute(b,"auto");return c}}})}),f.attrHooks.contenteditable={get:w.get,set:function(a,b,c){b===""&&(b="false"),w.set(a,b,c)}}),f.support.hrefNormalized||f.each(["href","src","width","height"],function(a,c){f.attrHooks[c]=f.extend(f.attrHooks[c],{get:function(a){var d=a.getAttribute(c,2);return d===null?b:d}})}),f.support.style||(f.attrHooks.style={get:function(a){return a.style.cssText.toLowerCase()||b},set:function(a,b){return a.style.cssText=""+b}}),f.support.optSelected||(f.propHooks.selected=f.extend(f.propHooks.selected,{get:function(a){var b=a.parentNode;b&&(b.selectedIndex,b.parentNode&&b.parentNode.selectedIndex);return null}})),f.support.enctype||(f.propFix.enctype="encoding"),f.support.checkOn||f.each(["radio","checkbox"],function(){f.valHooks[this]={get:function(a){return a.getAttribute("value")===null?"on":a.value}}}),f.each(["radio","checkbox"],function(){f.valHooks[this]=f.extend(f.valHooks[this],{set:function(a,b){if(f.isArray(b))return a.checked=f.inArray(f(a).val(),b)>=0}})});var z=/^(?:textarea|input|select)$/i,A=/^([^\.]*)?(?:\.(.+))?$/,B=/\bhover(\.\S+)?\b/,C=/^key/,D=/^(?:mouse|contextmenu)|click/,E=/^(?:focusinfocus|focusoutblur)$/,F=/^(\w*)(?:#([\w\-]+))?(?:\.([\w\-]+))?$/,G=function(a){var b=F.exec(a);b&&(b[1]=(b[1]||"").toLowerCase(),b[3]=b[3]&&new RegExp("(?:^|\\s)"+b[3]+"(?:\\s|$)"));return b},H=function(a,b){var c=a.attributes||{};return(!b[1]||a.nodeName.toLowerCase()===b[1])&&(!b[2]||(c.id||{}).value===b[2])&&(!b[3]||b[3].test((c["class"]||{}).value))},I=function(a){return f.event.special.hover?a:a.replace(B,"mouseenter$1 mouseleave$1")};
-f.event={add:function(a,c,d,e,g){var h,i,j,k,l,m,n,o,p,q,r,s;if(!(a.nodeType===3||a.nodeType===8||!c||!d||!(h=f._data(a)))){d.handler&&(p=d,d=p.handler),d.guid||(d.guid=f.guid++),j=h.events,j||(h.events=j={}),i=h.handle,i||(h.handle=i=function(a){return typeof f!="undefined"&&(!a||f.event.triggered!==a.type)?f.event.dispatch.apply(i.elem,arguments):b},i.elem=a),c=f.trim(I(c)).split(" ");for(k=0;k<c.length;k++){l=A.exec(c[k])||[],m=l[1],n=(l[2]||"").split(".").sort(),s=f.event.special[m]||{},m=(g?s.delegateType:s.bindType)||m,s=f.event.special[m]||{},o=f.extend({type:m,origType:l[1],data:e,handler:d,guid:d.guid,selector:g,quick:G(g),namespace:n.join(".")},p),r=j[m];if(!r){r=j[m]=[],r.delegateCount=0;if(!s.setup||s.setup.call(a,e,n,i)===!1)a.addEventListener?a.addEventListener(m,i,!1):a.attachEvent&&a.attachEvent("on"+m,i)}s.add&&(s.add.call(a,o),o.handler.guid||(o.handler.guid=d.guid)),g?r.splice(r.delegateCount++,0,o):r.push(o),f.event.global[m]=!0}a=null}},global:{},remove:function(a,b,c,d,e){var g=f.hasData(a)&&f._data(a),h,i,j,k,l,m,n,o,p,q,r,s;if(!!g&&!!(o=g.events)){b=f.trim(I(b||"")).split(" ");for(h=0;h<b.length;h++){i=A.exec(b[h])||[],j=k=i[1],l=i[2];if(!j){for(j in o)f.event.remove(a,j+b[h],c,d,!0);continue}p=f.event.special[j]||{},j=(d?p.delegateType:p.bindType)||j,r=o[j]||[],m=r.length,l=l?new RegExp("(^|\\.)"+l.split(".").sort().join("\\.(?:.*\\.)?")+"(\\.|$)"):null;for(n=0;n<r.length;n++)s=r[n],(e||k===s.origType)&&(!c||c.guid===s.guid)&&(!l||l.test(s.namespace))&&(!d||d===s.selector||d==="**"&&s.selector)&&(r.splice(n--,1),s.selector&&r.delegateCount--,p.remove&&p.remove.call(a,s));r.length===0&&m!==r.length&&((!p.teardown||p.teardown.call(a,l)===!1)&&f.removeEvent(a,j,g.handle),delete o[j])}f.isEmptyObject(o)&&(q=g.handle,q&&(q.elem=null),f.removeData(a,["events","handle"],!0))}},customEvent:{getData:!0,setData:!0,changeData:!0},trigger:function(c,d,e,g){if(!e||e.nodeType!==3&&e.nodeType!==8){var h=c.type||c,i=[],j,k,l,m,n,o,p,q,r,s;if(E.test(h+f.event.triggered))return;h.indexOf("!")>=0&&(h=h.slice(0,-1),k=!0),h.indexOf(".")>=0&&(i=h.split("."),h=i.shift(),i.sort());if((!e||f.event.customEvent[h])&&!f.event.global[h])return;c=typeof c=="object"?c[f.expando]?c:new f.Event(h,c):new f.Event(h),c.type=h,c.isTrigger=!0,c.exclusive=k,c.namespace=i.join("."),c.namespace_re=c.namespace?new RegExp("(^|\\.)"+i.join("\\.(?:.*\\.)?")+"(\\.|$)"):null,o=h.indexOf(":")<0?"on"+h:"";if(!e){j=f.cache;for(l in j)j[l].events&&j[l].events[h]&&f.event.trigger(c,d,j[l].handle.elem,!0);return}c.result=b,c.target||(c.target=e),d=d!=null?f.makeArray(d):[],d.unshift(c),p=f.event.special[h]||{};if(p.trigger&&p.trigger.apply(e,d)===!1)return;r=[[e,p.bindType||h]];if(!g&&!p.noBubble&&!f.isWindow(e)){s=p.delegateType||h,m=E.test(s+h)?e:e.parentNode,n=null;for(;m;m=m.parentNode)r.push([m,s]),n=m;n&&n===e.ownerDocument&&r.push([n.defaultView||n.parentWindow||a,s])}for(l=0;l<r.length&&!c.isPropagationStopped();l++)m=r[l][0],c.type=r[l][1],q=(f._data(m,"events")||{})[c.type]&&f._data(m,"handle"),q&&q.apply(m,d),q=o&&m[o],q&&f.acceptData(m)&&q.apply(m,d)===!1&&c.preventDefault();c.type=h,!g&&!c.isDefaultPrevented()&&(!p._default||p._default.apply(e.ownerDocument,d)===!1)&&(h!=="click"||!f.nodeName(e,"a"))&&f.acceptData(e)&&o&&e[h]&&(h!=="focus"&&h!=="blur"||c.target.offsetWidth!==0)&&!f.isWindow(e)&&(n=e[o],n&&(e[o]=null),f.event.triggered=h,e[h](),f.event.triggered=b,n&&(e[o]=n));return c.result}},dispatch:function(c){c=f.event.fix(c||a.event);var d=(f._data(this,"events")||{})[c.type]||[],e=d.delegateCount,g=[].slice.call(arguments,0),h=!c.exclusive&&!c.namespace,i=[],j,k,l,m,n,o,p,q,r,s,t;g[0]=c,c.delegateTarget=this;if(e&&!c.target.disabled&&(!c.button||c.type!=="click")){m=f(this),m.context=this.ownerDocument||this;for(l=c.target;l!=this;l=l.parentNode||this){o={},q=[],m[0]=l;for(j=0;j<e;j++)r=d[j],s=r.selector,o[s]===b&&(o[s]=r.quick?H(l,r.quick):m.is(s)),o[s]&&q.push(r);q.length&&i.push({elem:l,matches:q})}}d.length>e&&i.push({elem:this,matches:d.slice(e)});for(j=0;j<i.length&&!c.isPropagationStopped();j++){p=i[j],c.currentTarget=p.elem;for(k=0;k<p.matches.length&&!c.isImmediatePropagationStopped();k++){r=p.matches[k];if(h||!c.namespace&&!r.namespace||c.namespace_re&&c.namespace_re.test(r.namespace))c.data=r.data,c.handleObj=r,n=((f.event.special[r.origType]||{}).handle||r.handler).apply(p.elem,g),n!==b&&(c.result=n,n===!1&&(c.preventDefault(),c.stopPropagation()))}}return c.result},props:"attrChange attrName relatedNode srcElement altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(a,b){a.which==null&&(a.which=b.charCode!=null?b.charCode:b.keyCode);return a}},mouseHooks:{props:"button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(a,d){var e,f,g,h=d.button,i=d.fromElement;a.pageX==null&&d.clientX!=null&&(e=a.target.ownerDocument||c,f=e.documentElement,g=e.body,a.pageX=d.clientX+(f&&f.scrollLeft||g&&g.scrollLeft||0)-(f&&f.clientLeft||g&&g.clientLeft||0),a.pageY=d.clientY+(f&&f.scrollTop||g&&g.scrollTop||0)-(f&&f.clientTop||g&&g.clientTop||0)),!a.relatedTarget&&i&&(a.relatedTarget=i===a.target?d.toElement:i),!a.which&&h!==b&&(a.which=h&1?1:h&2?3:h&4?2:0);return a}},fix:function(a){if(a[f.expando])return a;var d,e,g=a,h=f.event.fixHooks[a.type]||{},i=h.props?this.props.concat(h.props):this.props;a=f.Event(g);for(d=i.length;d;)e=i[--d],a[e]=g[e];a.target||(a.target=g.srcElement||c),a.target.nodeType===3&&(a.target=a.target.parentNode),a.metaKey===b&&(a.metaKey=a.ctrlKey);return h.filter?h.filter(a,g):a},special:{ready:{setup:f.bindReady},load:{noBubble:!0},focus:{delegateType:"focusin"},blur:{delegateType:"focusout"},beforeunload:{setup:function(a,b,c){f.isWindow(this)&&(this.onbeforeunload=c)},teardown:function(a,b){this.onbeforeunload===b&&(this.onbeforeunload=null)}}},simulate:function(a,b,c,d){var e=f.extend(new f.Event,c,{type:a,isSimulated:!0,originalEvent:{}});d?f.event.trigger(e,null,b):f.event.dispatch.call(b,e),e.isDefaultPrevented()&&c.preventDefault()}},f.event.handle=f.event.dispatch,f.removeEvent=c.removeEventListener?function(a,b,c){a.removeEventListener&&a.removeEventListener(b,c,!1)}:function(a,b,c){a.detachEvent&&a.detachEvent("on"+b,c)},f.Event=function(a,b){if(!(this instanceof f.Event))return new f.Event(a,b);a&&a.type?(this.originalEvent=a,this.type=a.type,this.isDefaultPrevented=a.defaultPrevented||a.returnValue===!1||a.getPreventDefault&&a.getPreventDefault()?K:J):this.type=a,b&&f.extend(this,b),this.timeStamp=a&&a.timeStamp||f.now(),this[f.expando]=!0},f.Event.prototype={preventDefault:function(){this.isDefaultPrevented=K;var a=this.originalEvent;!a||(a.preventDefault?a.preventDefault():a.returnValue=!1)},stopPropagation:function(){this.isPropagationStopped=K;var a=this.originalEvent;!a||(a.stopPropagation&&a.stopPropagation(),a.cancelBubble=!0)},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=K,this.stopPropagation()},isDefaultPrevented:J,isPropagationStopped:J,isImmediatePropagationStopped:J},f.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(a,b){f.event.special[a]={delegateType:b,bindType:b,handle:function(a){var c=this,d=a.relatedTarget,e=a.handleObj,g=e.selector,h;if(!d||d!==c&&!f.contains(c,d))a.type=e.origType,h=e.handler.apply(this,arguments),a.type=b;return h}}}),f.support.submitBubbles||(f.event.special.submit={setup:function(){if(f.nodeName(this,"form"))return!1;f.event.add(this,"click._submit keypress._submit",function(a){var c=a.target,d=f.nodeName(c,"input")||f.nodeName(c,"button")?c.form:b;d&&!d._submit_attached&&(f.event.add(d,"submit._submit",function(a){this.parentNode&&!a.isTrigger&&f.event.simulate("submit",this.parentNode,a,!0)}),d._submit_attached=!0)})},teardown:function(){if(f.nodeName(this,"form"))return!1;f.event.remove(this,"._submit")}}),f.support.changeBubbles||(f.event.special.change={setup:function(){if(z.test(this.nodeName)){if(this.type==="checkbox"||this.type==="radio")f.event.add(this,"propertychange._change",function(a){a.originalEvent.propertyName==="checked"&&(this._just_changed=!0)}),f.event.add(this,"click._change",function(a){this._just_changed&&!a.isTrigger&&(this._just_changed=!1,f.event.simulate("change",this,a,!0))});return!1}f.event.add(this,"beforeactivate._change",function(a){var b=a.target;z.test(b.nodeName)&&!b._change_attached&&(f.event.add(b,"change._change",function(a){this.parentNode&&!a.isSimulated&&!a.isTrigger&&f.event.simulate("change",this.parentNode,a,!0)}),b._change_attached=!0)})},handle:function(a){var b=a.target;if(this!==b||a.isSimulated||a.isTrigger||b.type!=="radio"&&b.type!=="checkbox")return a.handleObj.handler.apply(this,arguments)},teardown:function(){f.event.remove(this,"._change");return z.test(this.nodeName)}}),f.support.focusinBubbles||f.each({focus:"focusin",blur:"focusout"},function(a,b){var d=0,e=function(a){f.event.simulate(b,a.target,f.event.fix(a),!0)};f.event.special[b]={setup:function(){d++===0&&c.addEventListener(a,e,!0)},teardown:function(){--d===0&&c.removeEventListener(a,e,!0)}}}),f.fn.extend({on:function(a,c,d,e,g){var h,i;if(typeof a=="object"){typeof c!="string"&&(d=c,c=b);for(i in a)this.on(i,c,d,a[i],g);return this}d==null&&e==null?(e=c,d=c=b):e==null&&(typeof c=="string"?(e=d,d=b):(e=d,d=c,c=b));if(e===!1)e=J;else if(!e)return this;g===1&&(h=e,e=function(a){f().off(a);return h.apply(this,arguments)},e.guid=h.guid||(h.guid=f.guid++));return this.each(function(){f.event.add(this,a,e,d,c)})},one:function(a,b,c,d){return this.on.call(this,a,b,c,d,1)},off:function(a,c,d){if(a&&a.preventDefault&&a.handleObj){var e=a.handleObj;f(a.delegateTarget).off(e.namespace?e.type+"."+e.namespace:e.type,e.selector,e.handler);return this}if(typeof a=="object"){for(var g in a)this.off(g,c,a[g]);return this}if(c===!1||typeof c=="function")d=c,c=b;d===!1&&(d=J);return this.each(function(){f.event.remove(this,a,d,c)})},bind:function(a,b,c){return this.on(a,null,b,c)},unbind:function(a,b){return this.off(a,null,b)},live:function(a,b,c){f(this.context).on(a,this.selector,b,c);return this},die:function(a,b){f(this.context).off(a,this.selector||"**",b);return this},delegate:function(a,b,c,d){return this.on(b,a,c,d)},undelegate:function(a,b,c){return arguments.length==1?this.off(a,"**"):this.off(b,a,c)},trigger:function(a,b){return this.each(function(){f.event.trigger(a,b,this)})},triggerHandler:function(a,b){if(this[0])return f.event.trigger(a,b,this[0],!0)},toggle:function(a){var b=arguments,c=a.guid||f.guid++,d=0,e=function(c){var e=(f._data(this,"lastToggle"+a.guid)||0)%d;f._data(this,"lastToggle"+a.guid,e+1),c.preventDefault();return b[e].apply(this,arguments)||!1};e.guid=c;while(d<b.length)b[d++].guid=c;return this.click(e)},hover:function(a,b){return this.mouseenter(a).mouseleave(b||a)}}),f.each("blur focus focusin focusout load resize scroll unload click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup error contextmenu".split(" "),function(a,b){f.fn[b]=function(a,c){c==null&&(c=a,a=null);return arguments.length>0?this.on(b,null,a,c):this.trigger(b)},f.attrFn&&(f.attrFn[b]=!0),C.test(b)&&(f.event.fixHooks[b]=f.event.keyHooks),D.test(b)&&(f.event.fixHooks[b]=f.event.mouseHooks)}),function(){function x(a,b,c,e,f,g){for(var h=0,i=e.length;h<i;h++){var j=e[h];if(j){var k=!1;j=j[a];while(j){if(j[d]===c){k=e[j.sizset];break}if(j.nodeType===1){g||(j[d]=c,j.sizset=h);if(typeof b!="string"){if(j===b){k=!0;break}}else if(m.filter(b,[j]).length>0){k=j;break}}j=j[a]}e[h]=k}}}function w(a,b,c,e,f,g){for(var h=0,i=e.length;h<i;h++){var j=e[h];if(j){var k=!1;j=j[a];while(j){if(j[d]===c){k=e[j.sizset];break}j.nodeType===1&&!g&&(j[d]=c,j.sizset=h);if(j.nodeName.toLowerCase()===b){k=j;break}j=j[a]}e[h]=k}}}var a=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,d="sizcache"+(Math.random()+"").replace(".",""),e=0,g=Object.prototype.toString,h=!1,i=!0,j=/\\/g,k=/\r\n/g,l=/\W/;[0,0].sort(function(){i=!1;return 0});var m=function(b,d,e,f){e=e||[],d=d||c;var h=d;if(d.nodeType!==1&&d.nodeType!==9)return[];if(!b||typeof b!="string")return e;var i,j,k,l,n,q,r,t,u=!0,v=m.isXML(d),w=[],x=b;do{a.exec(""),i=a.exec(x);if(i){x=i[3],w.push(i[1]);if(i[2]){l=i[3];break}}}while(i);if(w.length>1&&p.exec(b))if(w.length===2&&o.relative[w[0]])j=y(w[0]+w[1],d,f);else{j=o.relative[w[0]]?[d]:m(w.shift(),d);while(w.length)b=w.shift(),o.relative[b]&&(b+=w.shift()),j=y(b,j,f)}else{!f&&w.length>1&&d.nodeType===9&&!v&&o.match.ID.test(w[0])&&!o.match.ID.test(w[w.length-1])&&(n=m.find(w.shift(),d,v),d=n.expr?m.filter(n.expr,n.set)[0]:n.set[0]);if(d){n=f?{expr:w.pop(),set:s(f)}:m.find(w.pop(),w.length===1&&(w[0]==="~"||w[0]==="+")&&d.parentNode?d.parentNode:d,v),j=n.expr?m.filter(n.expr,n.set):n.set,w.length>0?k=s(j):u=!1;while(w.length)q=w.pop(),r=q,o.relative[q]?r=w.pop():q="",r==null&&(r=d),o.relative[q](k,r,v)}else k=w=[]}k||(k=j),k||m.error(q||b);if(g.call(k)==="[object Array]")if(!u)e.push.apply(e,k);else if(d&&d.nodeType===1)for(t=0;k[t]!=null;t++)k[t]&&(k[t]===!0||k[t].nodeType===1&&m.contains(d,k[t]))&&e.push(j[t]);else for(t=0;k[t]!=null;t++)k[t]&&k[t].nodeType===1&&e.push(j[t]);else s(k,e);l&&(m(l,h,e,f),m.uniqueSort(e));return e};m.uniqueSort=function(a){if(u){h=i,a.sort(u);if(h)for(var b=1;b<a.length;b++)a[b]===a[b-1]&&a.splice(b--,1)}return a},m.matches=function(a,b){return m(a,null,null,b)},m.matchesSelector=function(a,b){return m(b,null,null,[a]).length>0},m.find=function(a,b,c){var d,e,f,g,h,i;if(!a)return[];for(e=0,f=o.order.length;e<f;e++){h=o.order[e];if(g=o.leftMatch[h].exec(a)){i=g[1],g.splice(1,1);if(i.substr(i.length-1)!=="\\"){g[1]=(g[1]||"").replace(j,""),d=o.find[h](g,b,c);if(d!=null){a=a.replace(o.match[h],"");break}}}}d||(d=typeof b.getElementsByTagName!="undefined"?b.getElementsByTagName("*"):[]);return{set:d,expr:a}},m.filter=function(a,c,d,e){var f,g,h,i,j,k,l,n,p,q=a,r=[],s=c,t=c&&c[0]&&m.isXML(c[0]);while(a&&c.length){for(h in o.filter)if((f=o.leftMatch[h].exec(a))!=null&&f[2]){k=o.filter[h],l=f[1],g=!1,f.splice(1,1);if(l.substr(l.length-1)==="\\")continue;s===r&&(r=[]);if(o.preFilter[h]){f=o.preFilter[h](f,s,d,r,e,t);if(!f)g=i=!0;else if(f===!0)continue}if(f)for(n=0;(j=s[n])!=null;n++)j&&(i=k(j,f,n,s),p=e^i,d&&i!=null?p?g=!0:s[n]=!1:p&&(r.push(j),g=!0));if(i!==b){d||(s=r),a=a.replace(o.match[h],"");if(!g)return[];break}}if(a===q)if(g==null)m.error(a);else break;q=a}return s},m.error=function(a){throw new Error("Syntax error, unrecognized expression: "+a)};var n=m.getText=function(a){var b,c,d=a.nodeType,e="";if(d){if(d===1||d===9){if(typeof a.textContent=="string")return a.textContent;if(typeof a.innerText=="string")return a.innerText.replace(k,"");for(a=a.firstChild;a;a=a.nextSibling)e+=n(a)}else if(d===3||d===4)return a.nodeValue}else for(b=0;c=a[b];b++)c.nodeType!==8&&(e+=n(c));return e},o=m.selectors={order:["ID","NAME","TAG"],match:{ID:/#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,CLASS:/\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,NAME:/\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,ATTR:/\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/,TAG:/^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,CHILD:/:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/,POS:/:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,PSEUDO:/:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/},leftMatch:{},attrMap:{"class":"className","for":"htmlFor"},attrHandle:{href:function(a){return a.getAttribute("href")},type:function(a){return a.getAttribute("type")}},relative:{"+":function(a,b){var c=typeof b=="string",d=c&&!l.test(b),e=c&&!d;d&&(b=b.toLowerCase());for(var f=0,g=a.length,h;f<g;f++)if(h=a[f]){while((h=h.previousSibling)&&h.nodeType!==1);a[f]=e||h&&h.nodeName.toLowerCase()===b?h||!1:h===b}e&&m.filter(b,a,!0)},">":function(a,b){var c,d=typeof b=="string",e=0,f=a.length;if(d&&!l.test(b)){b=b.toLowerCase();for(;e<f;e++){c=a[e];if(c){var g=c.parentNode;a[e]=g.nodeName.toLowerCase()===b?g:!1}}}else{for(;e<f;e++)c=a[e],c&&(a[e]=d?c.parentNode:c.parentNode===b);d&&m.filter(b,a,!0)}},"":function(a,b,c){var d,f=e++,g=x;typeof b=="string"&&!l.test(b)&&(b=b.toLowerCase(),d=b,g=w),g("parentNode",b,f,a,d,c)},"~":function(a,b,c){var d,f=e++,g=x;typeof b=="string"&&!l.test(b)&&(b=b.toLowerCase(),d=b,g=w),g("previousSibling",b,f,a,d,c)}},find:{ID:function(a,b,c){if(typeof b.getElementById!="undefined"&&!c){var d=b.getElementById(a[1]);return d&&d.parentNode?[d]:[]}},NAME:function(a,b){if(typeof b.getElementsByName!="undefined"){var c=[],d=b.getElementsByName(a[1]);for(var e=0,f=d.length;e<f;e++)d[e].getAttribute("name")===a[1]&&c.push(d[e]);return c.length===0?null:c}},TAG:function(a,b){if(typeof b.getElementsByTagName!="undefined")return b.getElementsByTagName(a[1])}},preFilter:{CLASS:function(a,b,c,d,e,f){a=" "+a[1].replace(j,"")+" ";if(f)return a;for(var g=0,h;(h=b[g])!=null;g++)h&&(e^(h.className&&(" "+h.className+" ").replace(/[\t\n\r]/g," ").indexOf(a)>=0)?c||d.push(h):c&&(b[g]=!1));return!1},ID:function(a){return a[1].replace(j,"")},TAG:function(a,b){return a[1].replace(j,"").toLowerCase()},CHILD:function(a){if(a[1]==="nth"){a[2]||m.error(a[0]),a[2]=a[2].replace(/^\+|\s*/g,"");var b=/(-?)(\d*)(?:n([+\-]?\d*))?/.exec(a[2]==="even"&&"2n"||a[2]==="odd"&&"2n+1"||!/\D/.test(a[2])&&"0n+"+a[2]||a[2]);a[2]=b[1]+(b[2]||1)-0,a[3]=b[3]-0}else a[2]&&m.error(a[0]);a[0]=e++;return a},ATTR:function(a,b,c,d,e,f){var g=a[1]=a[1].replace(j,"");!f&&o.attrMap[g]&&(a[1]=o.attrMap[g]),a[4]=(a[4]||a[5]||"").replace(j,""),a[2]==="~="&&(a[4]=" "+a[4]+" ");return a},PSEUDO:function(b,c,d,e,f){if(b[1]==="not")if((a.exec(b[3])||"").length>1||/^\w/.test(b[3]))b[3]=m(b[3],null,null,c);else{var g=m.filter(b[3],c,d,!0^f);d||e.push.apply(e,g);return!1}else if(o.match.POS.test(b[0])||o.match.CHILD.test(b[0]))return!0;return b},POS:function(a){a.unshift(!0);return a}},filters:{enabled:function(a){return a.disabled===!1&&a.type!=="hidden"},disabled:function(a){return a.disabled===!0},checked:function(a){return a.checked===!0},selected:function(a){a.parentNode&&a.parentNode.selectedIndex;return a.selected===!0},parent:function(a){return!!a.firstChild},empty:function(a){return!a.firstChild},has:function(a,b,c){return!!m(c[3],a).length},header:function(a){return/h\d/i.test(a.nodeName)},text:function(a){var b=a.getAttribute("type"),c=a.type;return a.nodeName.toLowerCase()==="input"&&"text"===c&&(b===c||b===null)},radio:function(a){return a.nodeName.toLowerCase()==="input"&&"radio"===a.type},checkbox:function(a){return a.nodeName.toLowerCase()==="input"&&"checkbox"===a.type},file:function(a){return a.nodeName.toLowerCase()==="input"&&"file"===a.type},password:function(a){return a.nodeName.toLowerCase()==="input"&&"password"===a.type},submit:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"submit"===a.type},image:function(a){return a.nodeName.toLowerCase()==="input"&&"image"===a.type},reset:function(a){var b=a.nodeName.toLowerCase();return(b==="input"||b==="button")&&"reset"===a.type},button:function(a){var b=a.nodeName.toLowerCase();return b==="input"&&"button"===a.type||b==="button"},input:function(a){return/input|select|textarea|button/i.test(a.nodeName)},focus:function(a){return a===a.ownerDocument.activeElement}},setFilters:{first:function(a,b){return b===0},last:function(a,b,c,d){return b===d.length-1},even:function(a,b){return b%2===0},odd:function(a,b){return b%2===1},lt:function(a,b,c){return b<c[3]-0},gt:function(a,b,c){return b>c[3]-0},nth:function(a,b,c){return c[3]-0===b},eq:function(a,b,c){return c[3]-0===b}},filter:{PSEUDO:function(a,b,c,d){var e=b[1],f=o.filters[e];if(f)return f(a,c,b,d);if(e==="contains")return(a.textContent||a.innerText||n([a])||"").indexOf(b[3])>=0;if(e==="not"){var g=b[3];for(var h=0,i=g.length;h<i;h++)if(g[h]===a)return!1;return!0}m.error(e)},CHILD:function(a,b){var c,e,f,g,h,i,j,k=b[1],l=a;switch(k){case"only":case"first":while(l=l.previousSibling)if(l.nodeType===1)return!1;if(k==="first")return!0;l=a;case"last":while(l=l.nextSibling)if(l.nodeType===1)return!1;return!0;case"nth":c=b[2],e=b[3];if(c===1&&e===0)return!0;f=b[0],g=a.parentNode;if(g&&(g[d]!==f||!a.nodeIndex)){i=0;for(l=g.firstChild;l;l=l.nextSibling)l.nodeType===1&&(l.nodeIndex=++i);g[d]=f}j=a.nodeIndex-e;return c===0?j===0:j%c===0&&j/c>=0}},ID:function(a,b){return a.nodeType===1&&a.getAttribute("id")===b},TAG:function(a,b){return b==="*"&&a.nodeType===1||!!a.nodeName&&a.nodeName.toLowerCase()===b},CLASS:function(a,b){return(" "+(a.className||a.getAttribute("class"))+" ").indexOf(b)>-1},ATTR:function(a,b){var c=b[1],d=m.attr?m.attr(a,c):o.attrHandle[c]?o.attrHandle[c](a):a[c]!=null?a[c]:a.getAttribute(c),e=d+"",f=b[2],g=b[4];return d==null?f==="!=":!f&&m.attr?d!=null:f==="="?e===g:f==="*="?e.indexOf(g)>=0:f==="~="?(" "+e+" ").indexOf(g)>=0:g?f==="!="?e!==g:f==="^="?e.indexOf(g)===0:f==="$="?e.substr(e.length-g.length)===g:f==="|="?e===g||e.substr(0,g.length+1)===g+"-":!1:e&&d!==!1},POS:function(a,b,c,d){var e=b[2],f=o.setFilters[e];if(f)return f(a,c,b,d)}}},p=o.match.POS,q=function(a,b){return"\\"+(b-0+1)};for(var r in o.match)o.match[r]=new RegExp(o.match[r].source+/(?![^\[]*\])(?![^\(]*\))/.source),o.leftMatch[r]=new RegExp(/(^(?:.|\r|\n)*?)/.source+o.match[r].source.replace(/\\(\d+)/g,q));var s=function(a,b){a=Array.prototype.slice.call(a,0);if(b){b.push.apply(b,a);return b}return a};try{Array.prototype.slice.call(c.documentElement.childNodes,0)[0].nodeType}catch(t){s=function(a,b){var c=0,d=b||[];if(g.call(a)==="[object Array]")Array.prototype.push.apply(d,a);else if(typeof a.length=="number")for(var e=a.length;c<e;c++)d.push(a[c]);else for(;a[c];c++)d.push(a[c]);return d}}var u,v;c.documentElement.compareDocumentPosition?u=function(a,b){if(a===b){h=!0;return 0}if(!a.compareDocumentPosition||!b.compareDocumentPosition)return a.compareDocumentPosition?-1:1;return a.compareDocumentPosition(b)&4?-1:1}:(u=function(a,b){if(a===b){h=!0;return 0}if(a.sourceIndex&&b.sourceIndex)return a.sourceIndex-b.sourceIndex;var c,d,e=[],f=[],g=a.parentNode,i=b.parentNode,j=g;if(g===i)return v(a,b);if(!g)return-1;if(!i)return 1;while(j)e.unshift(j),j=j.parentNode;j=i;while(j)f.unshift(j),j=j.parentNode;c=e.length,d=f.length;for(var k=0;k<c&&k<d;k++)if(e[k]!==f[k])return v(e[k],f[k]);return k===c?v(a,f[k],-1):v(e[k],b,1)},v=function(a,b,c){if(a===b)return c;var d=a.nextSibling;while(d){if(d===b)return-1;d=d.nextSibling}return 1}),function(){var a=c.createElement("div"),d="script"+(new Date).getTime(),e=c.documentElement;a.innerHTML="<a name='"+d+"'/>",e.insertBefore(a,e.firstChild),c.getElementById(d)&&(o.find.ID=function(a,c,d){if(typeof c.getElementById!="undefined"&&!d){var e=c.getElementById(a[1]);return e?e.id===a[1]||typeof e.getAttributeNode!="undefined"&&e.getAttributeNode("id").nodeValue===a[1]?[e]:b:[]}},o.filter.ID=function(a,b){var c=typeof a.getAttributeNode!="undefined"&&a.getAttributeNode("id");return a.nodeType===1&&c&&c.nodeValue===b}),e.removeChild(a),e=a=null}(),function(){var a=c.createElement("div");a.appendChild(c.createComment("")),a.getElementsByTagName("*").length>0&&(o.find.TAG=function(a,b){var c=b.getElementsByTagName(a[1]);if(a[1]==="*"){var d=[];for(var e=0;c[e];e++)c[e].nodeType===1&&d.push(c[e]);c=d}return c}),a.innerHTML="<a href='#'></a>",a.firstChild&&typeof a.firstChild.getAttribute!="undefined"&&a.firstChild.getAttribute("href")!=="#"&&(o.attrHandle.href=function(a){return a.getAttribute("href",2)}),a=null}(),c.querySelectorAll&&function(){var a=m,b=c.createElement("div"),d="__sizzle__";b.innerHTML="<p class='TEST'></p>";if(!b.querySelectorAll||b.querySelectorAll(".TEST").length!==0){m=function(b,e,f,g){e=e||c;if(!g&&!m.isXML(e)){var h=/^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec(b);if(h&&(e.nodeType===1||e.nodeType===9)){if(h[1])return s(e.getElementsByTagName(b),f);if(h[2]&&o.find.CLASS&&e.getElementsByClassName)return s(e.getElementsByClassName(h[2]),f)}if(e.nodeType===9){if(b==="body"&&e.body)return s([e.body],f);if(h&&h[3]){var i=e.getElementById(h[3]);if(!i||!i.parentNode)return s([],f);if(i.id===h[3])return s([i],f)}try{return s(e.querySelectorAll(b),f)}catch(j){}}else if(e.nodeType===1&&e.nodeName.toLowerCase()!=="object"){var k=e,l=e.getAttribute("id"),n=l||d,p=e.parentNode,q=/^\s*[+~]/.test(b);l?n=n.replace(/'/g,"\\$&"):e.setAttribute("id",n),q&&p&&(e=e.parentNode);try{if(!q||p)return s(e.querySelectorAll("[id='"+n+"'] "+b),f)}catch(r){}finally{l||k.removeAttribute("id")}}}return a(b,e,f,g)};for(var e in a)m[e]=a[e];b=null}}(),function(){var a=c.documentElement,b=a.matchesSelector||a.mozMatchesSelector||a.webkitMatchesSelector||a.msMatchesSelector;if(b){var d=!b.call(c.createElement("div"),"div"),e=!1;try{b.call(c.documentElement,"[test!='']:sizzle")}catch(f){e=!0}m.matchesSelector=function(a,c){c=c.replace(/\=\s*([^'"\]]*)\s*\]/g,"='$1']");if(!m.isXML(a))try{if(e||!o.match.PSEUDO.test(c)&&!/!=/.test(c)){var f=b.call(a,c);if(f||!d||a.document&&a.document.nodeType!==11)return f}}catch(g){}return m(c,null,null,[a]).length>0}}}(),function(){var a=c.createElement("div");a.innerHTML="<div class='test e'></div><div class='test'></div>";if(!!a.getElementsByClassName&&a.getElementsByClassName("e").length!==0){a.lastChild.className="e";if(a.getElementsByClassName("e").length===1)return;o.order.splice(1,0,"CLASS"),o.find.CLASS=function(a,b,c){if(typeof b.getElementsByClassName!="undefined"&&!c)return b.getElementsByClassName(a[1])},a=null}}(),c.documentElement.contains?m.contains=function(a,b){return a!==b&&(a.contains?a.contains(b):!0)}:c.documentElement.compareDocumentPosition?m.contains=function(a,b){return!!(a.compareDocumentPosition(b)&16)}:m.contains=function(){return!1},m.isXML=function(a){var b=(a?a.ownerDocument||a:0).documentElement;return b?b.nodeName!=="HTML":!1};var y=function(a,b,c){var d,e=[],f="",g=b.nodeType?[b]:b;while(d=o.match.PSEUDO.exec(a))f+=d[0],a=a.replace(o.match.PSEUDO,"");a=o.relative[a]?a+"*":a;for(var h=0,i=g.length;h<i;h++)m(a,g[h],e,c);return m.filter(f,e)};m.attr=f.attr,m.selectors.attrMap={},f.find=m,f.expr=m.selectors,f.expr[":"]=f.expr.filters,f.unique=m.uniqueSort,f.text=m.getText,f.isXMLDoc=m.isXML,f.contains=m.contains}();var L=/Until$/,M=/^(?:parents|prevUntil|prevAll)/,N=/,/,O=/^.[^:#\[\.,]*$/,P=Array.prototype.slice,Q=f.expr.match.POS,R={children:!0,contents:!0,next:!0,prev:!0};f.fn.extend({find:function(a){var b=this,c,d;if(typeof a!="string")return f(a).filter(function(){for(c=0,d=b.length;c<d;c++)if(f.contains(b[c],this))return!0});var e=this.pushStack("","find",a),g,h,i;for(c=0,d=this.length;c<d;c++){g=e.length,f.find(a,this[c],e);if(c>0)for(h=g;h<e.length;h++)for(i=0;i<g;i++)if(e[i]===e[h]){e.splice(h--,1);break}}return e},has:function(a){var b=f(a);return this.filter(function(){for(var a=0,c=b.length;a<c;a++)if(f.contains(this,b[a]))return!0})},not:function(a){return this.pushStack(T(this,a,!1),"not",a)},filter:function(a){return this.pushStack(T(this,a,!0),"filter",a)},is:function(a){return!!a&&(typeof a=="string"?Q.test(a)?f(a,this.context).index(this[0])>=0:f.filter(a,this).length>0:this.filter(a).length>0)},closest:function(a,b){var c=[],d,e,g=this[0];if(f.isArray(a)){var h=1;while(g&&g.ownerDocument&&g!==b){for(d=0;d<a.length;d++)f(g).is(a[d])&&c.push({selector:a[d],elem:g,level:h});g=g.parentNode,h++}return c}var i=Q.test(a)||typeof a!="string"?f(a,b||this.context):0;for(d=0,e=this.length;d<e;d++){g=this[d];while(g){if(i?i.index(g)>-1:f.find.matchesSelector(g,a)){c.push(g);break}g=g.parentNode;if(!g||!g.ownerDocument||g===b||g.nodeType===11)break}}c=c.length>1?f.unique(c):c;return this.pushStack(c,"closest",a)},index:function(a){if(!a)return this[0]&&this[0].parentNode?this.prevAll().length:-1;if(typeof a=="string")return f.inArray(this[0],f(a));return f.inArray(a.jquery?a[0]:a,this)},add:function(a,b){var c=typeof a=="string"?f(a,b):f.makeArray(a&&a.nodeType?[a]:a),d=f.merge(this.get(),c);return this.pushStack(S(c[0])||S(d[0])?d:f.unique(d))},andSelf:function(){return this.add(this.prevObject)}}),f.each({parent:function(a){var b=a.parentNode;return b&&b.nodeType!==11?b:null},parents:function(a){return f.dir(a,"parentNode")},parentsUntil:function(a,b,c){return f.dir(a,"parentNode",c)},next:function(a){return f.nth(a,2,"nextSibling")},prev:function(a){return f.nth(a,2,"previousSibling")},nextAll:function(a){return f.dir(a,"nextSibling")},prevAll:function(a){return f.dir(a,"previousSibling")},nextUntil:function(a,b,c){return f.dir(a,"nextSibling",c)},prevUntil:function(a,b,c){return f.dir(a,"previousSibling",c)},siblings:function(a){return f.sibling(a.parentNode.firstChild,a)},children:function(a){return f.sibling(a.firstChild)},contents:function(a){return f.nodeName(a,"iframe")?a.contentDocument||a.contentWindow.document:f.makeArray(a.childNodes)}},function(a,b){f.fn[a]=function(c,d){var e=f.map(this,b,c);L.test(a)||(d=c),d&&typeof d=="string"&&(e=f.filter(d,e)),e=this.length>1&&!R[a]?f.unique(e):e,(this.length>1||N.test(d))&&M.test(a)&&(e=e.reverse());return this.pushStack(e,a,P.call(arguments).join(","))}}),f.extend({filter:function(a,b,c){c&&(a=":not("+a+")");return b.length===1?f.find.matchesSelector(b[0],a)?[b[0]]:[]:f.find.matches(a,b)},dir:function(a,c,d){var e=[],g=a[c];while(g&&g.nodeType!==9&&(d===b||g.nodeType!==1||!f(g).is(d)))g.nodeType===1&&e.push(g),g=g[c];return e},nth:function(a,b,c,d){b=b||1;var e=0;for(;a;a=a[c])if(a.nodeType===1&&++e===b)break;return a},sibling:function(a,b){var c=[];for(;a;a=a.nextSibling)a.nodeType===1&&a!==b&&c.push(a);return c}});var V="abbr|article|aside|audio|canvas|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",W=/ jQuery\d+="(?:\d+|null)"/g,X=/^\s+/,Y=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,Z=/<([\w:]+)/,$=/<tbody/i,_=/<|&#?\w+;/,ba=/<(?:script|style)/i,bb=/<(?:script|object|embed|option|style)/i,bc=new RegExp("<(?:"+V+")","i"),bd=/checked\s*(?:[^=]|=\s*.checked.)/i,be=/\/(java|ecma)script/i,bf=/^\s*<!(?:\[CDATA\[|\-\-)/,bg={option:[1,"<select multiple='multiple'>","</select>"],legend:[1,"<fieldset>","</fieldset>"],thead:[1,"<table>","</table>"],tr:[2,"<table><tbody>","</tbody></table>"],td:[3,"<table><tbody><tr>","</tr></tbody></table>"],col:[2,"<table><tbody></tbody><colgroup>","</colgroup></table>"],area:[1,"<map>","</map>"],_default:[0,"",""]},bh=U(c);bg.optgroup=bg.option,bg.tbody=bg.tfoot=bg.colgroup=bg.caption=bg.thead,bg.th=bg.td,f.support.htmlSerialize||(bg._default=[1,"div<div>","</div>"]),f.fn.extend({text:function(a){if(f.isFunction(a))return this.each(function(b){var c=f(this);c.text(a.call(this,b,c.text()))});if(typeof a!="object"&&a!==b)return this.empty().append((this[0]&&this[0].ownerDocument||c).createTextNode(a));return f.text(this)},wrapAll:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapAll(a.call(this,b))});if(this[0]){var b=f(a,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&b.insertBefore(this[0]),b.map(function(){var a=this;while(a.firstChild&&a.firstChild.nodeType===1)a=a.firstChild;return a}).append(this)}return this},wrapInner:function(a){if(f.isFunction(a))return this.each(function(b){f(this).wrapInner(a.call(this,b))});return this.each(function(){var b=f(this),c=b.contents();c.length?c.wrapAll(a):b.append(a)})},wrap:function(a){var b=f.isFunction(a);return this.each(function(c){f(this).wrapAll(b?a.call(this,c):a)})},unwrap:function(){return this.parent().each(function(){f.nodeName(this,"body")||f(this).replaceWith(this.childNodes)}).end()},append:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.appendChild(a)})},prepend:function(){return this.domManip(arguments,!0,function(a){this.nodeType===1&&this.insertBefore(a,this.firstChild)})},before:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this)});if(arguments.length){var a=f.clean(arguments);a.push.apply(a,this.toArray());return this.pushStack(a,"before",arguments)}},after:function(){if(this[0]&&this[0].parentNode)return this.domManip(arguments,!1,function(a){this.parentNode.insertBefore(a,this.nextSibling)});if(arguments.length){var a=this.pushStack(this,"after",arguments);a.push.apply(a,f.clean(arguments));return a}},remove:function(a,b){for(var c=0,d;(d=this[c])!=null;c++)if(!a||f.filter(a,[d]).length)!b&&d.nodeType===1&&(f.cleanData(d.getElementsByTagName("*")),f.cleanData([d])),d.parentNode&&d.parentNode.removeChild(d);return this},empty:function()
-{for(var a=0,b;(b=this[a])!=null;a++){b.nodeType===1&&f.cleanData(b.getElementsByTagName("*"));while(b.firstChild)b.removeChild(b.firstChild)}return this},clone:function(a,b){a=a==null?!1:a,b=b==null?a:b;return this.map(function(){return f.clone(this,a,b)})},html:function(a){if(a===b)return this[0]&&this[0].nodeType===1?this[0].innerHTML.replace(W,""):null;if(typeof a=="string"&&!ba.test(a)&&(f.support.leadingWhitespace||!X.test(a))&&!bg[(Z.exec(a)||["",""])[1].toLowerCase()]){a=a.replace(Y,"<$1></$2>");try{for(var c=0,d=this.length;c<d;c++)this[c].nodeType===1&&(f.cleanData(this[c].getElementsByTagName("*")),this[c].innerHTML=a)}catch(e){this.empty().append(a)}}else f.isFunction(a)?this.each(function(b){var c=f(this);c.html(a.call(this,b,c.html()))}):this.empty().append(a);return this},replaceWith:function(a){if(this[0]&&this[0].parentNode){if(f.isFunction(a))return this.each(function(b){var c=f(this),d=c.html();c.replaceWith(a.call(this,b,d))});typeof a!="string"&&(a=f(a).detach());return this.each(function(){var b=this.nextSibling,c=this.parentNode;f(this).remove(),b?f(b).before(a):f(c).append(a)})}return this.length?this.pushStack(f(f.isFunction(a)?a():a),"replaceWith",a):this},detach:function(a){return this.remove(a,!0)},domManip:function(a,c,d){var e,g,h,i,j=a[0],k=[];if(!f.support.checkClone&&arguments.length===3&&typeof j=="string"&&bd.test(j))return this.each(function(){f(this).domManip(a,c,d,!0)});if(f.isFunction(j))return this.each(function(e){var g=f(this);a[0]=j.call(this,e,c?g.html():b),g.domManip(a,c,d)});if(this[0]){i=j&&j.parentNode,f.support.parentNode&&i&&i.nodeType===11&&i.childNodes.length===this.length?e={fragment:i}:e=f.buildFragment(a,this,k),h=e.fragment,h.childNodes.length===1?g=h=h.firstChild:g=h.firstChild;if(g){c=c&&f.nodeName(g,"tr");for(var l=0,m=this.length,n=m-1;l<m;l++)d.call(c?bi(this[l],g):this[l],e.cacheable||m>1&&l<n?f.clone(h,!0,!0):h)}k.length&&f.each(k,bp)}return this}}),f.buildFragment=function(a,b,d){var e,g,h,i,j=a[0];b&&b[0]&&(i=b[0].ownerDocument||b[0]),i.createDocumentFragment||(i=c),a.length===1&&typeof j=="string"&&j.length<512&&i===c&&j.charAt(0)==="<"&&!bb.test(j)&&(f.support.checkClone||!bd.test(j))&&(f.support.html5Clone||!bc.test(j))&&(g=!0,h=f.fragments[j],h&&h!==1&&(e=h)),e||(e=i.createDocumentFragment(),f.clean(a,i,e,d)),g&&(f.fragments[j]=h?e:1);return{fragment:e,cacheable:g}},f.fragments={},f.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(a,b){f.fn[a]=function(c){var d=[],e=f(c),g=this.length===1&&this[0].parentNode;if(g&&g.nodeType===11&&g.childNodes.length===1&&e.length===1){e[b](this[0]);return this}for(var h=0,i=e.length;h<i;h++){var j=(h>0?this.clone(!0):this).get();f(e[h])[b](j),d=d.concat(j)}return this.pushStack(d,a,e.selector)}}),f.extend({clone:function(a,b,c){var d,e,g,h=f.support.html5Clone||!bc.test("<"+a.nodeName)?a.cloneNode(!0):bo(a);if((!f.support.noCloneEvent||!f.support.noCloneChecked)&&(a.nodeType===1||a.nodeType===11)&&!f.isXMLDoc(a)){bk(a,h),d=bl(a),e=bl(h);for(g=0;d[g];++g)e[g]&&bk(d[g],e[g])}if(b){bj(a,h);if(c){d=bl(a),e=bl(h);for(g=0;d[g];++g)bj(d[g],e[g])}}d=e=null;return h},clean:function(a,b,d,e){var g;b=b||c,typeof b.createElement=="undefined"&&(b=b.ownerDocument||b[0]&&b[0].ownerDocument||c);var h=[],i;for(var j=0,k;(k=a[j])!=null;j++){typeof k=="number"&&(k+="");if(!k)continue;if(typeof k=="string")if(!_.test(k))k=b.createTextNode(k);else{k=k.replace(Y,"<$1></$2>");var l=(Z.exec(k)||["",""])[1].toLowerCase(),m=bg[l]||bg._default,n=m[0],o=b.createElement("div");b===c?bh.appendChild(o):U(b).appendChild(o),o.innerHTML=m[1]+k+m[2];while(n--)o=o.lastChild;if(!f.support.tbody){var p=$.test(k),q=l==="table"&&!p?o.firstChild&&o.firstChild.childNodes:m[1]==="<table>"&&!p?o.childNodes:[];for(i=q.length-1;i>=0;--i)f.nodeName(q[i],"tbody")&&!q[i].childNodes.length&&q[i].parentNode.removeChild(q[i])}!f.support.leadingWhitespace&&X.test(k)&&o.insertBefore(b.createTextNode(X.exec(k)[0]),o.firstChild),k=o.childNodes}var r;if(!f.support.appendChecked)if(k[0]&&typeof (r=k.length)=="number")for(i=0;i<r;i++)bn(k[i]);else bn(k);k.nodeType?h.push(k):h=f.merge(h,k)}if(d){g=function(a){return!a.type||be.test(a.type)};for(j=0;h[j];j++)if(e&&f.nodeName(h[j],"script")&&(!h[j].type||h[j].type.toLowerCase()==="text/javascript"))e.push(h[j].parentNode?h[j].parentNode.removeChild(h[j]):h[j]);else{if(h[j].nodeType===1){var s=f.grep(h[j].getElementsByTagName("script"),g);h.splice.apply(h,[j+1,0].concat(s))}d.appendChild(h[j])}}return h},cleanData:function(a){var b,c,d=f.cache,e=f.event.special,g=f.support.deleteExpando;for(var h=0,i;(i=a[h])!=null;h++){if(i.nodeName&&f.noData[i.nodeName.toLowerCase()])continue;c=i[f.expando];if(c){b=d[c];if(b&&b.events){for(var j in b.events)e[j]?f.event.remove(i,j):f.removeEvent(i,j,b.handle);b.handle&&(b.handle.elem=null)}g?delete i[f.expando]:i.removeAttribute&&i.removeAttribute(f.expando),delete d[c]}}}});var bq=/alpha\([^)]*\)/i,br=/opacity=([^)]*)/,bs=/([A-Z]|^ms)/g,bt=/^-?\d+(?:px)?$/i,bu=/^-?\d/,bv=/^([\-+])=([\-+.\de]+)/,bw={position:"absolute",visibility:"hidden",display:"block"},bx=["Left","Right"],by=["Top","Bottom"],bz,bA,bB;f.fn.css=function(a,c){if(arguments.length===2&&c===b)return this;return f.access(this,a,c,!0,function(a,c,d){return d!==b?f.style(a,c,d):f.css(a,c)})},f.extend({cssHooks:{opacity:{get:function(a,b){if(b){var c=bz(a,"opacity","opacity");return c===""?"1":c}return a.style.opacity}}},cssNumber:{fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":f.support.cssFloat?"cssFloat":"styleFloat"},style:function(a,c,d,e){if(!!a&&a.nodeType!==3&&a.nodeType!==8&&!!a.style){var g,h,i=f.camelCase(c),j=a.style,k=f.cssHooks[i];c=f.cssProps[i]||i;if(d===b){if(k&&"get"in k&&(g=k.get(a,!1,e))!==b)return g;return j[c]}h=typeof d,h==="string"&&(g=bv.exec(d))&&(d=+(g[1]+1)*+g[2]+parseFloat(f.css(a,c)),h="number");if(d==null||h==="number"&&isNaN(d))return;h==="number"&&!f.cssNumber[i]&&(d+="px");if(!k||!("set"in k)||(d=k.set(a,d))!==b)try{j[c]=d}catch(l){}}},css:function(a,c,d){var e,g;c=f.camelCase(c),g=f.cssHooks[c],c=f.cssProps[c]||c,c==="cssFloat"&&(c="float");if(g&&"get"in g&&(e=g.get(a,!0,d))!==b)return e;if(bz)return bz(a,c)},swap:function(a,b,c){var d={};for(var e in b)d[e]=a.style[e],a.style[e]=b[e];c.call(a);for(e in b)a.style[e]=d[e]}}),f.curCSS=f.css,f.each(["height","width"],function(a,b){f.cssHooks[b]={get:function(a,c,d){var e;if(c){if(a.offsetWidth!==0)return bC(a,b,d);f.swap(a,bw,function(){e=bC(a,b,d)});return e}},set:function(a,b){if(!bt.test(b))return b;b=parseFloat(b);if(b>=0)return b+"px"}}}),f.support.opacity||(f.cssHooks.opacity={get:function(a,b){return br.test((b&&a.currentStyle?a.currentStyle.filter:a.style.filter)||"")?parseFloat(RegExp.$1)/100+"":b?"1":""},set:function(a,b){var c=a.style,d=a.currentStyle,e=f.isNumeric(b)?"alpha(opacity="+b*100+")":"",g=d&&d.filter||c.filter||"";c.zoom=1;if(b>=1&&f.trim(g.replace(bq,""))===""){c.removeAttribute("filter");if(d&&!d.filter)return}c.filter=bq.test(g)?g.replace(bq,e):g+" "+e}}),f(function(){f.support.reliableMarginRight||(f.cssHooks.marginRight={get:function(a,b){var c;f.swap(a,{display:"inline-block"},function(){b?c=bz(a,"margin-right","marginRight"):c=a.style.marginRight});return c}})}),c.defaultView&&c.defaultView.getComputedStyle&&(bA=function(a,b){var c,d,e;b=b.replace(bs,"-$1").toLowerCase(),(d=a.ownerDocument.defaultView)&&(e=d.getComputedStyle(a,null))&&(c=e.getPropertyValue(b),c===""&&!f.contains(a.ownerDocument.documentElement,a)&&(c=f.style(a,b)));return c}),c.documentElement.currentStyle&&(bB=function(a,b){var c,d,e,f=a.currentStyle&&a.currentStyle[b],g=a.style;f===null&&g&&(e=g[b])&&(f=e),!bt.test(f)&&bu.test(f)&&(c=g.left,d=a.runtimeStyle&&a.runtimeStyle.left,d&&(a.runtimeStyle.left=a.currentStyle.left),g.left=b==="fontSize"?"1em":f||0,f=g.pixelLeft+"px",g.left=c,d&&(a.runtimeStyle.left=d));return f===""?"auto":f}),bz=bA||bB,f.expr&&f.expr.filters&&(f.expr.filters.hidden=function(a){var b=a.offsetWidth,c=a.offsetHeight;return b===0&&c===0||!f.support.reliableHiddenOffsets&&(a.style&&a.style.display||f.css(a,"display"))==="none"},f.expr.filters.visible=function(a){return!f.expr.filters.hidden(a)});var bD=/%20/g,bE=/\[\]$/,bF=/\r?\n/g,bG=/#.*$/,bH=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,bI=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,bJ=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,bK=/^(?:GET|HEAD)$/,bL=/^\/\//,bM=/\?/,bN=/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,bO=/^(?:select|textarea)/i,bP=/\s+/,bQ=/([?&])_=[^&]*/,bR=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/,bS=f.fn.load,bT={},bU={},bV,bW,bX=["*/"]+["*"];try{bV=e.href}catch(bY){bV=c.createElement("a"),bV.href="",bV=bV.href}bW=bR.exec(bV.toLowerCase())||[],f.fn.extend({load:function(a,c,d){if(typeof a!="string"&&bS)return bS.apply(this,arguments);if(!this.length)return this;var e=a.indexOf(" ");if(e>=0){var g=a.slice(e,a.length);a=a.slice(0,e)}var h="GET";c&&(f.isFunction(c)?(d=c,c=b):typeof c=="object"&&(c=f.param(c,f.ajaxSettings.traditional),h="POST"));var i=this;f.ajax({url:a,type:h,dataType:"html",data:c,complete:function(a,b,c){c=a.responseText,a.isResolved()&&(a.done(function(a){c=a}),i.html(g?f("<div>").append(c.replace(bN,"")).find(g):c)),d&&i.each(d,[c,b,a])}});return this},serialize:function(){return f.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?f.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||bO.test(this.nodeName)||bI.test(this.type))}).map(function(a,b){var c=f(this).val();return c==null?null:f.isArray(c)?f.map(c,function(a,c){return{name:b.name,value:a.replace(bF,"\r\n")}}):{name:b.name,value:c.replace(bF,"\r\n")}}).get()}}),f.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(a,b){f.fn[b]=function(a){return this.on(b,a)}}),f.each(["get","post"],function(a,c){f[c]=function(a,d,e,g){f.isFunction(d)&&(g=g||e,e=d,d=b);return f.ajax({type:c,url:a,data:d,success:e,dataType:g})}}),f.extend({getScript:function(a,c){return f.get(a,b,c,"script")},getJSON:function(a,b,c){return f.get(a,b,c,"json")},ajaxSetup:function(a,b){b?b_(a,f.ajaxSettings):(b=a,a=f.ajaxSettings),b_(a,b);return a},ajaxSettings:{url:bV,isLocal:bJ.test(bW[1]),global:!0,type:"GET",contentType:"application/x-www-form-urlencoded",processData:!0,async:!0,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":bX},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":a.String,"text html":!0,"text json":f.parseJSON,"text xml":f.parseXML},flatOptions:{context:!0,url:!0}},ajaxPrefilter:bZ(bT),ajaxTransport:bZ(bU),ajax:function(a,c){function w(a,c,l,m){if(s!==2){s=2,q&&clearTimeout(q),p=b,n=m||"",v.readyState=a>0?4:0;var o,r,u,w=c,x=l?cb(d,v,l):b,y,z;if(a>=200&&a<300||a===304){if(d.ifModified){if(y=v.getResponseHeader("Last-Modified"))f.lastModified[k]=y;if(z=v.getResponseHeader("Etag"))f.etag[k]=z}if(a===304)w="notmodified",o=!0;else try{r=cc(d,x),w="success",o=!0}catch(A){w="parsererror",u=A}}else{u=w;if(!w||a)w="error",a<0&&(a=0)}v.status=a,v.statusText=""+(c||w),o?h.resolveWith(e,[r,w,v]):h.rejectWith(e,[v,w,u]),v.statusCode(j),j=b,t&&g.trigger("ajax"+(o?"Success":"Error"),[v,d,o?r:u]),i.fireWith(e,[v,w]),t&&(g.trigger("ajaxComplete",[v,d]),--f.active||f.event.trigger("ajaxStop"))}}typeof a=="object"&&(c=a,a=b),c=c||{};var d=f.ajaxSetup({},c),e=d.context||d,g=e!==d&&(e.nodeType||e instanceof f)?f(e):f.event,h=f.Deferred(),i=f.Callbacks("once memory"),j=d.statusCode||{},k,l={},m={},n,o,p,q,r,s=0,t,u,v={readyState:0,setRequestHeader:function(a,b){if(!s){var c=a.toLowerCase();a=m[c]=m[c]||a,l[a]=b}return this},getAllResponseHeaders:function(){return s===2?n:null},getResponseHeader:function(a){var c;if(s===2){if(!o){o={};while(c=bH.exec(n))o[c[1].toLowerCase()]=c[2]}c=o[a.toLowerCase()]}return c===b?null:c},overrideMimeType:function(a){s||(d.mimeType=a);return this},abort:function(a){a=a||"abort",p&&p.abort(a),w(0,a);return this}};h.promise(v),v.success=v.done,v.error=v.fail,v.complete=i.add,v.statusCode=function(a){if(a){var b;if(s<2)for(b in a)j[b]=[j[b],a[b]];else b=a[v.status],v.then(b,b)}return this},d.url=((a||d.url)+"").replace(bG,"").replace(bL,bW[1]+"//"),d.dataTypes=f.trim(d.dataType||"*").toLowerCase().split(bP),d.crossDomain==null&&(r=bR.exec(d.url.toLowerCase()),d.crossDomain=!(!r||r[1]==bW[1]&&r[2]==bW[2]&&(r[3]||(r[1]==="http:"?80:443))==(bW[3]||(bW[1]==="http:"?80:443)))),d.data&&d.processData&&typeof d.data!="string"&&(d.data=f.param(d.data,d.traditional)),b$(bT,d,c,v);if(s===2)return!1;t=d.global,d.type=d.type.toUpperCase(),d.hasContent=!bK.test(d.type),t&&f.active++===0&&f.event.trigger("ajaxStart");if(!d.hasContent){d.data&&(d.url+=(bM.test(d.url)?"&":"?")+d.data,delete d.data),k=d.url;if(d.cache===!1){var x=f.now(),y=d.url.replace(bQ,"$1_="+x);d.url=y+(y===d.url?(bM.test(d.url)?"&":"?")+"_="+x:"")}}(d.data&&d.hasContent&&d.contentType!==!1||c.contentType)&&v.setRequestHeader("Content-Type",d.contentType),d.ifModified&&(k=k||d.url,f.lastModified[k]&&v.setRequestHeader("If-Modified-Since",f.lastModified[k]),f.etag[k]&&v.setRequestHeader("If-None-Match",f.etag[k])),v.setRequestHeader("Accept",d.dataTypes[0]&&d.accepts[d.dataTypes[0]]?d.accepts[d.dataTypes[0]]+(d.dataTypes[0]!=="*"?", "+bX+"; q=0.01":""):d.accepts["*"]);for(u in d.headers)v.setRequestHeader(u,d.headers[u]);if(d.beforeSend&&(d.beforeSend.call(e,v,d)===!1||s===2)){v.abort();return!1}for(u in{success:1,error:1,complete:1})v[u](d[u]);p=b$(bU,d,c,v);if(!p)w(-1,"No Transport");else{v.readyState=1,t&&g.trigger("ajaxSend",[v,d]),d.async&&d.timeout>0&&(q=setTimeout(function(){v.abort("timeout")},d.timeout));try{s=1,p.send(l,w)}catch(z){if(s<2)w(-1,z);else throw z}}return v},param:function(a,c){var d=[],e=function(a,b){b=f.isFunction(b)?b():b,d[d.length]=encodeURIComponent(a)+"="+encodeURIComponent(b)};c===b&&(c=f.ajaxSettings.traditional);if(f.isArray(a)||a.jquery&&!f.isPlainObject(a))f.each(a,function(){e(this.name,this.value)});else for(var g in a)ca(g,a[g],c,e);return d.join("&").replace(bD,"+")}}),f.extend({active:0,lastModified:{},etag:{}});var cd=f.now(),ce=/(\=)\?(&|$)|\?\?/i;f.ajaxSetup({jsonp:"callback",jsonpCallback:function(){return f.expando+"_"+cd++}}),f.ajaxPrefilter("json jsonp",function(b,c,d){var e=b.contentType==="application/x-www-form-urlencoded"&&typeof b.data=="string";if(b.dataTypes[0]==="jsonp"||b.jsonp!==!1&&(ce.test(b.url)||e&&ce.test(b.data))){var g,h=b.jsonpCallback=f.isFunction(b.jsonpCallback)?b.jsonpCallback():b.jsonpCallback,i=a[h],j=b.url,k=b.data,l="$1"+h+"$2";b.jsonp!==!1&&(j=j.replace(ce,l),b.url===j&&(e&&(k=k.replace(ce,l)),b.data===k&&(j+=(/\?/.test(j)?"&":"?")+b.jsonp+"="+h))),b.url=j,b.data=k,a[h]=function(a){g=[a]},d.always(function(){a[h]=i,g&&f.isFunction(i)&&a[h](g[0])}),b.converters["script json"]=function(){g||f.error(h+" was not called");return g[0]},b.dataTypes[0]="json";return"script"}}),f.ajaxSetup({accepts:{script:"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"},contents:{script:/javascript|ecmascript/},converters:{"text script":function(a){f.globalEval(a);return a}}}),f.ajaxPrefilter("script",function(a){a.cache===b&&(a.cache=!1),a.crossDomain&&(a.type="GET",a.global=!1)}),f.ajaxTransport("script",function(a){if(a.crossDomain){var d,e=c.head||c.getElementsByTagName("head")[0]||c.documentElement;return{send:function(f,g){d=c.createElement("script"),d.async="async",a.scriptCharset&&(d.charset=a.scriptCharset),d.src=a.url,d.onload=d.onreadystatechange=function(a,c){if(c||!d.readyState||/loaded|complete/.test(d.readyState))d.onload=d.onreadystatechange=null,e&&d.parentNode&&e.removeChild(d),d=b,c||g(200,"success")},e.insertBefore(d,e.firstChild)},abort:function(){d&&d.onload(0,1)}}}});var cf=a.ActiveXObject?function(){for(var a in ch)ch[a](0,1)}:!1,cg=0,ch;f.ajaxSettings.xhr=a.ActiveXObject?function(){return!this.isLocal&&ci()||cj()}:ci,function(a){f.extend(f.support,{ajax:!!a,cors:!!a&&"withCredentials"in a})}(f.ajaxSettings.xhr()),f.support.ajax&&f.ajaxTransport(function(c){if(!c.crossDomain||f.support.cors){var d;return{send:function(e,g){var h=c.xhr(),i,j;c.username?h.open(c.type,c.url,c.async,c.username,c.password):h.open(c.type,c.url,c.async);if(c.xhrFields)for(j in c.xhrFields)h[j]=c.xhrFields[j];c.mimeType&&h.overrideMimeType&&h.overrideMimeType(c.mimeType),!c.crossDomain&&!e["X-Requested-With"]&&(e["X-Requested-With"]="XMLHttpRequest");try{for(j in e)h.setRequestHeader(j,e[j])}catch(k){}h.send(c.hasContent&&c.data||null),d=function(a,e){var j,k,l,m,n;try{if(d&&(e||h.readyState===4)){d=b,i&&(h.onreadystatechange=f.noop,cf&&delete ch[i]);if(e)h.readyState!==4&&h.abort();else{j=h.status,l=h.getAllResponseHeaders(),m={},n=h.responseXML,n&&n.documentElement&&(m.xml=n),m.text=h.responseText;try{k=h.statusText}catch(o){k=""}!j&&c.isLocal&&!c.crossDomain?j=m.text?200:404:j===1223&&(j=204)}}}catch(p){e||g(-1,p)}m&&g(j,k,m,l)},!c.async||h.readyState===4?d():(i=++cg,cf&&(ch||(ch={},f(a).unload(cf)),ch[i]=d),h.onreadystatechange=d)},abort:function(){d&&d(0,1)}}}});var ck={},cl,cm,cn=/^(?:toggle|show|hide)$/,co=/^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i,cp,cq=[["height","marginTop","marginBottom","paddingTop","paddingBottom"],["width","marginLeft","marginRight","paddingLeft","paddingRight"],["opacity"]],cr;f.fn.extend({show:function(a,b,c){var d,e;if(a||a===0)return this.animate(cu("show",3),a,b,c);for(var g=0,h=this.length;g<h;g++)d=this[g],d.style&&(e=d.style.display,!f._data(d,"olddisplay")&&e==="none"&&(e=d.style.display=""),e===""&&f.css(d,"display")==="none"&&f._data(d,"olddisplay",cv(d.nodeName)));for(g=0;g<h;g++){d=this[g];if(d.style){e=d.style.display;if(e===""||e==="none")d.style.display=f._data(d,"olddisplay")||""}}return this},hide:function(a,b,c){if(a||a===0)return this.animate(cu("hide",3),a,b,c);var d,e,g=0,h=this.length;for(;g<h;g++)d=this[g],d.style&&(e=f.css(d,"display"),e!=="none"&&!f._data(d,"olddisplay")&&f._data(d,"olddisplay",e));for(g=0;g<h;g++)this[g].style&&(this[g].style.display="none");return this},_toggle:f.fn.toggle,toggle:function(a,b,c){var d=typeof a=="boolean";f.isFunction(a)&&f.isFunction(b)?this._toggle.apply(this,arguments):a==null||d?this.each(function(){var b=d?a:f(this).is(":hidden");f(this)[b?"show":"hide"]()}):this.animate(cu("toggle",3),a,b,c);return this},fadeTo:function(a,b,c,d){return this.filter(":hidden").css("opacity",0).show().end().animate({opacity:b},a,c,d)},animate:function(a,b,c,d){function g(){e.queue===!1&&f._mark(this);var b=f.extend({},e),c=this.nodeType===1,d=c&&f(this).is(":hidden"),g,h,i,j,k,l,m,n,o;b.animatedProperties={};for(i in a){g=f.camelCase(i),i!==g&&(a[g]=a[i],delete a[i]),h=a[g],f.isArray(h)?(b.animatedProperties[g]=h[1],h=a[g]=h[0]):b.animatedProperties[g]=b.specialEasing&&b.specialEasing[g]||b.easing||"swing";if(h==="hide"&&d||h==="show"&&!d)return b.complete.call(this);c&&(g==="height"||g==="width")&&(b.overflow=[this.style.overflow,this.style.overflowX,this.style.overflowY],f.css(this,"display")==="inline"&&f.css(this,"float")==="none"&&(!f.support.inlineBlockNeedsLayout||cv(this.nodeName)==="inline"?this.style.display="inline-block":this.style.zoom=1))}b.overflow!=null&&(this.style.overflow="hidden");for(i in a)j=new f.fx(this,b,i),h=a[i],cn.test(h)?(o=f._data(this,"toggle"+i)||(h==="toggle"?d?"show":"hide":0),o?(f._data(this,"toggle"+i,o==="show"?"hide":"show"),j[o]()):j[h]()):(k=co.exec(h),l=j.cur(),k?(m=parseFloat(k[2]),n=k[3]||(f.cssNumber[i]?"":"px"),n!=="px"&&(f.style(this,i,(m||1)+n),l=(m||1)/j.cur()*l,f.style(this,i,l+n)),k[1]&&(m=(k[1]==="-="?-1:1)*m+l),j.custom(l,m,n)):j.custom(l,h,""));return!0}var e=f.speed(b,c,d);if(f.isEmptyObject(a))return this.each(e.complete,[!1]);a=f.extend({},a);return e.queue===!1?this.each(g):this.queue(e.queue,g)},stop:function(a,c,d){typeof a!="string"&&(d=c,c=a,a=b),c&&a!==!1&&this.queue(a||"fx",[]);return this.each(function(){function h(a,b,c){var e=b[c];f.removeData(a,c,!0),e.stop(d)}var b,c=!1,e=f.timers,g=f._data(this);d||f._unmark(!0,this);if(a==null)for(b in g)g[b]&&g[b].stop&&b.indexOf(".run")===b.length-4&&h(this,g,b);else g[b=a+".run"]&&g[b].stop&&h(this,g,b);for(b=e.length;b--;)e[b].elem===this&&(a==null||e[b].queue===a)&&(d?e[b](!0):e[b].saveState(),c=!0,e.splice(b,1));(!d||!c)&&f.dequeue(this,a)})}}),f.each({slideDown:cu("show",1),slideUp:cu("hide",1),slideToggle:cu("toggle",1),fadeIn:{opacity:"show"},fadeOut:{opacity:"hide"},fadeToggle:{opacity:"toggle"}},function(a,b){f.fn[a]=function(a,c,d){return this.animate(b,a,c,d)}}),f.extend({speed:function(a,b,c){var d=a&&typeof a=="object"?f.extend({},a):{complete:c||!c&&b||f.isFunction(a)&&a,duration:a,easing:c&&b||b&&!f.isFunction(b)&&b};d.duration=f.fx.off?0:typeof d.duration=="number"?d.duration:d.duration in f.fx.speeds?f.fx.speeds[d.duration]:f.fx.speeds._default;if(d.queue==null||d.queue===!0)d.queue="fx";d.old=d.complete,d.complete=function(a){f.isFunction(d.old)&&d.old.call(this),d.queue?f.dequeue(this,d.queue):a!==!1&&f._unmark(this)};return d},easing:{linear:function(a,b,c,d){return c+d*a},swing:function(a,b,c,d){return(-Math.cos(a*Math.PI)/2+.5)*d+c}},timers:[],fx:function(a,b,c){this.options=b,this.elem=a,this.prop=c,b.orig=b.orig||{}}}),f.fx.prototype={update:function(){this.options.step&&this.options.step.call(this.elem,this.now,this),(f.fx.step[this.prop]||f.fx.step._default)(this)},cur:function(){if(this.elem[this.prop]!=null&&(!this.elem.style||this.elem.style[this.prop]==null))return this.elem[this.prop];var a,b=f.css(this.elem,this.prop);return isNaN(a=parseFloat(b))?!b||b==="auto"?0:b:a},custom:function(a,c,d){function h(a){return e.step(a)}var e=this,g=f.fx;this.startTime=cr||cs(),this.end=c,this.now=this.start=a,this.pos=this.state=0,this.unit=d||this.unit||(f.cssNumber[this.prop]?"":"px"),h.queue=this.options.queue,h.elem=this.elem,h.saveState=function(){e.options.hide&&f._data(e.elem,"fxshow"+e.prop)===b&&f._data(e.elem,"fxshow"+e.prop,e.start)},h()&&f.timers.push(h)&&!cp&&(cp=setInterval(g.tick,g.interval))},show:function(){var a=f._data(this.elem,"fxshow"+this.prop);this.options.orig[this.prop]=a||f.style(this.elem,this.prop),this.options.show=!0,a!==b?this.custom(this.cur(),a):this.custom(this.prop==="width"||this.prop==="height"?1:0,this.cur()),f(this.elem).show()},hide:function(){this.options.orig[this.prop]=f._data(this.elem,"fxshow"+this.prop)||f.style(this.elem,this.prop),this.options.hide=!0,this.custom(this.cur(),0)},step:function(a){var b,c,d,e=cr||cs(),g=!0,h=this.elem,i=this.options;if(a||e>=i.duration+this.startTime){this.now=this.end,this.pos=this.state=1,this.update(),i.animatedProperties[this.prop]=!0;for(b in i.animatedProperties)i.animatedProperties[b]!==!0&&(g=!1);if(g){i.overflow!=null&&!f.support.shrinkWrapBlocks&&f.each(["","X","Y"],function(a,b){h.style["overflow"+b]=i.overflow[a]}),i.hide&&f(h).hide();if(i.hide||i.show)for(b in i.animatedProperties)f.style(h,b,i.orig[b]),f.removeData(h,"fxshow"+b,!0),f.removeData(h,"toggle"+b,!0);d=i.complete,d&&(i.complete=!1,d.call(h))}return!1}i.duration==Infinity?this.now=e:(c=e-this.startTime,this.state=c/i.duration,this.pos=f.easing[i.animatedProperties[this.prop]](this.state,c,0,1,i.duration),this.now=this.start+(this.end-this.start)*this.pos),this.update();return!0}},f.extend(f.fx,{tick:function(){var a,b=f.timers,c=0;for(;c<b.length;c++)a=b[c],!a()&&b[c]===a&&b.splice(c--,1);b.length||f.fx.stop()},interval:13,stop:function(){clearInterval(cp),cp=null},speeds:{slow:600,fast:200,_default:400},step:{opacity:function(a){f.style(a.elem,"opacity",a.now)},_default:function(a){a.elem.style&&a.elem.style[a.prop]!=null?a.elem.style[a.prop]=a.now+a.unit:a.elem[a.prop]=a.now}}}),f.each(["width","height"],function(a,b){f.fx.step[b]=function(a){f.style(a.elem,b,Math.max(0,a.now)+a.unit)}}),f.expr&&f.expr.filters&&(f.expr.filters.animated=function(a){return f.grep(f.timers,function(b){return a===b.elem}).length});var cw=/^t(?:able|d|h)$/i,cx=/^(?:body|html)$/i;"getBoundingClientRect"in c.documentElement?f.fn.offset=function(a){var b=this[0],c;if(a)return this.each(function(b){f.offset.setOffset(this,a,b)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return f.offset.bodyOffset(b);try{c=b.getBoundingClientRect()}catch(d){}var e=b.ownerDocument,g=e.documentElement;if(!c||!f.contains(g,b))return c?{top:c.top,left:c.left}:{top:0,left:0};var h=e.body,i=cy(e),j=g.clientTop||h.clientTop||0,k=g.clientLeft||h.clientLeft||0,l=i.pageYOffset||f.support.boxModel&&g.scrollTop||h.scrollTop,m=i.pageXOffset||f.support.boxModel&&g.scrollLeft||h.scrollLeft,n=c.top+l-j,o=c.left+m-k;return{top:n,left:o}}:f.fn.offset=function(a){var b=this[0];if(a)return this.each(function(b){f.offset.setOffset(this,a,b)});if(!b||!b.ownerDocument)return null;if(b===b.ownerDocument.body)return f.offset.bodyOffset(b);var c,d=b.offsetParent,e=b,g=b.ownerDocument,h=g.documentElement,i=g.body,j=g.defaultView,k=j?j.getComputedStyle(b,null):b.currentStyle,l=b.offsetTop,m=b.offsetLeft;while((b=b.parentNode)&&b!==i&&b!==h){if(f.support.fixedPosition&&k.position==="fixed")break;c=j?j.getComputedStyle(b,null):b.currentStyle,l-=b.scrollTop,m-=b.scrollLeft,b===d&&(l+=b.offsetTop,m+=b.offsetLeft,f.support.doesNotAddBorder&&(!f.support.doesAddBorderForTableAndCells||!cw.test(b.nodeName))&&(l+=parseFloat(c.borderTopWidth)||0,m+=parseFloat(c.borderLeftWidth)||0),e=d,d=b.offsetParent),f.support.subtractsBorderForOverflowNotVisible&&c.overflow!=="visible"&&(l+=parseFloat(c.borderTopWidth)||0,m+=parseFloat(c.borderLeftWidth)||0),k=c}if(k.position==="relative"||k.position==="static")l+=i.offsetTop,m+=i.offsetLeft;f.support.fixedPosition&&k.position==="fixed"&&(l+=Math.max(h.scrollTop,i.scrollTop),m+=Math.max(h.scrollLeft,i.scrollLeft));return{top:l,left:m}},f.offset={bodyOffset:function(a){var b=a.offsetTop,c=a.offsetLeft;f.support.doesNotIncludeMarginInBodyOffset&&(b+=parseFloat(f.css(a,"marginTop"))||0,c+=parseFloat(f.css(a,"marginLeft"))||0);return{top:b,left:c}},setOffset:function(a,b,c){var d=f.css(a,"position");d==="static"&&(a.style.position="relative");var e=f(a),g=e.offset(),h=f.css(a,"top"),i=f.css(a,"left"),j=(d==="absolute"||d==="fixed")&&f.inArray("auto",[h,i])>-1,k={},l={},m,n;j?(l=e.position(),m=l.top,n=l.left):(m=parseFloat(h)||0,n=parseFloat(i)||0),f.isFunction(b)&&(b=b.call(a,c,g)),b.top!=null&&(k.top=b.top-g.top+m),b.left!=null&&(k.left=b.left-g.left+n),"using"in b?b.using.call(a,k):e.css(k)}},f.fn.extend({position:function(){if(!this[0])return null;var a=this[0],b=this.offsetParent(),c=this.offset(),d=cx.test(b[0].nodeName)?{top:0,left:0}:b.offset();c.top-=parseFloat(f.css(a,"marginTop"))||0,c.left-=parseFloat(f.css(a,"marginLeft"))||0,d.top+=parseFloat(f.css(b[0],"borderTopWidth"))||0,d.left+=parseFloat(f.css(b[0],"borderLeftWidth"))||0;return{top:c.top-d.top,left:c.left-d.left}},offsetParent:function(){return this.map(function(){var a=this.offsetParent||c.body;while(a&&!cx.test(a.nodeName)&&f.css(a,"position")==="static")a=a.offsetParent;return a})}}),f.each(["Left","Top"],function(a,c){var d="scroll"+c;f.fn[d]=function(c){var e,g;if(c===b){e=this[0];if(!e)return null;g=cy(e);return g?"pageXOffset"in g?g[a?"pageYOffset":"pageXOffset"]:f.support.boxModel&&g.document.documentElement[d]||g.document.body[d]:e[d]}return this.each(function(){g=cy(this),g?g.scrollTo(a?f(g).scrollLeft():c,a?c:f(g).scrollTop()):this[d]=c})}}),f.each(["Height","Width"],function(a,c){var d=c.toLowerCase();f.fn["inner"+c]=function(){var a=this[0];return a?a.style?parseFloat(f.css(a,d,"padding")):this[d]():null},f.fn["outer"+c]=function(a){var b=this[0];return b?b.style?parseFloat(f.css(b,d,a?"margin":"border")):this[d]():null},f.fn[d]=function(a){var e=this[0];if(!e)return a==null?null:this;if(f.isFunction(a))return this.each(function(b){var c=f(this);c[d](a.call(this,b,c[d]()))});if(f.isWindow(e)){var g=e.document.documentElement["client"+c],h=e.document.body;return e.document.compatMode==="CSS1Compat"&&g||h&&h["client"+c]||g}if(e.nodeType===9)return Math.max(e.documentElement["client"+c],e.body["scroll"+c],e.documentElement["scroll"+c],e.body["offset"+c],e.documentElement["offset"+c]);if(a===b){var i=f.css(e,d),j=parseFloat(i);return f.isNumeric(j)?j:i}return this.css(d,typeof a=="string"?a:a+"px")}}),a.jQuery=a.$=f,typeof define=="function"&&define.amd&&define.amd.jQuery&&define("jquery",[],function(){return f})})(window);
\ No newline at end of file
diff --git a/src/site/resources/js/site.js b/src/site/resources/js/site.js
deleted file mode 100644
index 7e37161..0000000
--- a/src/site/resources/js/site.js
+++ /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.
-*/
-
-/* Executed on page load. */
-$(document).ready(function() {
-
- // Hack to add default visuals to tables
- $('table').each(function() {
- if ($(this).hasClass('bodyTable') || $(this).hasClass('tableblock')) {
-
- // Remove border="1" which is added by maven
- this.border = 0;
-
- // Add bootstrap table styling
- $(this).addClass('table table-striped table-bordered');
- }
- });
-
- // Render tabs
- $('.auto-tabs').each(function(groupid) {
-
- // Find tab bar
- $(this).find('ul').each(function() {
-
- // Add styling
- $(this).addClass('nav nav-tabs');
-
- // Go tab bar items
- $(this).find('li').each(function(itemid) {
-
- // Set first tab as active
- if (itemid == 0) {
- $(this).addClass('active');
- }
-
- // Replace text with a link to tab contents
- var name = $(this).html();
- var link = $('<a>')
- .attr('href', '#' + 'tab-' + groupid + '-' + itemid)
- .attr('data-toggle', 'tab')
- .html(name);
- $(this).html(link);
- });
- });
-
- // Find tab contents
- $(this).find('.tab-content .tab-pane').each(function(itemid) {
-
- // Set first tab as active
- if (itemid == 0) {
- $(this).addClass('active');
- }
-
- // Set the tab id
- $(this).attr('id', 'tab-' + groupid + '-' + itemid);
- });
- });
-
- // Make external links open in new tab
- $('a.external').attr('target', '_blank');
-});
diff --git a/src/site/site.vm b/src/site/site.vm
deleted file mode 100644
index 981d0a3..0000000
--- a/src/site/site.vm
+++ /dev/null
@@ -1,521 +0,0 @@
-<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
-<!--
- Licensed to the Apache Software Foundation (ASF) under one or more
- contributor license agreements. See the NOTICE file distributed with
- this work for additional information regarding copyright ownership.
- The ASF licenses this file to You under the Apache License, Version 2.0
- (the "License"); you may not use this file except in compliance with
- the License. You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<!-- Generated by Apache Maven Doxia at $dateFormat.format( $currentDate ) -->
-#macro ( link $href $name $target $img $position $alt $border $width $height )
- #set ( $linkTitle = ' title="' + $name + '"' )
- #if( $target )
- #set ( $linkTarget = ' target="' + $target + '"' )
- #else
- #set ( $linkTarget = "" )
- #end
- #if ( $href.toLowerCase().startsWith("http:/") || $href.toLowerCase().startsWith("https:/") ||
- $href.toLowerCase().startsWith("ftp:/") || $href.toLowerCase().startsWith("mailto:/") ||
- $href.toLowerCase().startsWith("file:/") || ($href.toLowerCase().indexOf("://") != -1) )
- #set ( $linkClass = ' class="external" target="_blank"' )
-
- #if ( $linkTarget )
- #else
- #set ( $linkTarget = "_blank" )
- #end
-
- #else
- #set ( $linkClass = "" )
- #end
- #if ( $img )
- #if ( $position == "left" )
- <a href="$href"$linkClass$linkTarget$linkTitle>#image($img $alt $border $width $height)$name</a>
- #else
- <a href="$href"$linkClass$linkTarget$linkTitle>$name #image($img $alt $border $width $height)</a>
- #end
- #else
- <a href="$href"$linkClass$linkTarget$linkTitle>$name</a>
- #end
-#end
-##
-#macro ( image $img $alt $border $width $height )
- #if( $img )
- #if ( ! ( $img.toLowerCase().startsWith("http:/") || $img.toLowerCase().startsWith("https:/") ||
- $img.toLowerCase().startsWith("ftp:/") || $img.toLowerCase().startsWith("mailto:/") ||
- $img.toLowerCase().startsWith("file:/") || ($img.toLowerCase().indexOf("://") != -1) ) )
- #set ( $imgSrc = $PathTool.calculateLink( $img, $relativePath ) )
- #set ( $imgSrc = $imgSrc.replaceAll( '\\', '/' ) )
- #set ( $imgSrc = ' src="' + $imgSrc + '"' )
- #else
- #set ( $imgSrc = ' src="' + $img + '"' )
- #end
- #if( $alt )
- #set ( $imgAlt = ' alt="' + $alt + '"' )
- #else
- #set ( $imgAlt = ' alt=""' )
- #end
- #if( $border )
- #set ( $imgBorder = ' border="' + $border + '"' )
- #else
- #set ( $imgBorder = "" )
- #end
- #if( $width )
- #set ( $imgWidth = ' width="' + $width + '"' )
- #else
- #set ( $imgWidth = "" )
- #end
- #if( $height )
- #set ( $imgHeight = ' height="' + $height + '"' )
- #else
- #set ( $imgHeight = "" )
- #end
- <img class="imageLink"$imgSrc$imgAlt$imgBorder$imgWidth$imgHeight/>
- #end
-#end
-#macro ( banner $banner $id )
- #if ( $banner )
- #if( $banner.href )
- #set ( $hrf = $banner.href )
- #if ( ! ( $hrf.toLowerCase().startsWith("http:/") || $hrf.toLowerCase().startsWith("https:/") ||
- $hrf.toLowerCase().startsWith("ftp:/") || $hrf.toLowerCase().startsWith("mailto:/") ||
- $hrf.toLowerCase().startsWith("file:/") || ($hrf.toLowerCase().indexOf("://") != -1) ) )
- #set ( $hrf = $PathTool.calculateLink( $hrf, $relativePath ) )
- #set ( $hrf = $hrf.replaceAll( '\\', '/' ) )
- #if ( ( $hrf == '' ) )
- #set ( $hrf = './' )
- #end
- #end
- <a href="$hrf" id="$id"#if( $banner.alt ) title="$banner.alt"#end>
- #else
- <div id="$id">
- #end
-##
- #if( $banner.src )
- #set ( $src = $banner.src )
- #if ( ! ( $src.toLowerCase().startsWith("http:/") || $src.toLowerCase().startsWith("https:/") ||
- $src.toLowerCase().startsWith("ftp:/") || $src.toLowerCase().startsWith("mailto:/") ||
- $src.toLowerCase().startsWith("file:/") || ($src.toLowerCase().indexOf("://") != -1) ) )
- #set ( $src = $PathTool.calculateLink( $src, $relativePath ) )
- #set ( $src = $src.replaceAll( '\\', '/' ) )
- #end
- #if ( $banner.alt )
- #set ( $alt = $banner.alt )
- #else
- #set ( $alt = $banner.name )
- #end
- <img src="$src" alt="$alt" />
- #else
- $banner.name
- #end
-##
- #if( $banner.href )
- </a>
- #else
- </div>
- #end
- #end
-#end
-##
-#macro ( links $links )
- <ul class="nav">
- #set ( $counter = 0 )
- #foreach( $item in $links )
- #set ( $counter = $counter + 1 )
- #set ( $currentItemHref = $PathTool.calculateLink( $item.href, $relativePath ) )
- #set ( $currentItemHref = $currentItemHref.replaceAll( '\\', '/' ) )
- #set ( $activeClass = "" )
- #if ( $alignedFileName == $currentItemHref)
- #set ( $activeClass = ' class="active"' )
- #end
- <li$activeClass>
- #link( $currentItemHref $item.name $item.target $item.img $item.position $item.alt $item.border $item.width $item.height )
- </li>
- #end
- </ul>
-#end
-##
-#macro ( breadcrumbs $breadcrumbs )
- #foreach( $item in $breadcrumbs )
- #set ( $currentItemHref = $PathTool.calculateLink( $item.href, $relativePath ) )
- #set ( $currentItemHref = $currentItemHref.replaceAll( '\\', '/' ) )
- #if ( ( $currentItemHref == '' ) )
- #set ( $currentItemHref = './' )
- #end
-##
- #link( $currentItemHref $item.name $item.target $item.img $item.position $item.alt $item.border $item.width $item.height )
- <span class="divider">></span>
- #end
- $title
-#end
-##
-#macro ( displayTree $display $item )
- #if ( $item && $item.items && $item.items.size() > 0 )
- #foreach( $subitem in $item.items )
- #set ( $subitemHref = $PathTool.calculateLink( $subitem.href, $relativePath ) )
- #set ( $subitemHref = $subitemHref.replaceAll( '\\', '/' ) )
-##
- #if ( $alignedFileName == $subitemHref )
- #set ( $display = true )
- #end
-##
- #displayTree( $display $subitem )
- #end
- #end
-#end
-##
-#macro ( menuItem $item $isComponentDocumentation )
- #set ( $collapse = "none" )
- #set ( $currentItemHref = $PathTool.calculateLink( $item.href, $relativePath ) )
- #set ( $currentItemHref = $currentItemHref.replaceAll( '\\', '/' ) )
-##
- #if ( $item && $item.items && $item.items.size() > 0 )
- #if ( $item.collapse == false )
- #set ( $collapse = "expanded" )
- #else
- ## By default collapsed
- #set ( $collapse = "collapsed" )
- #end
-##
- #set ( $display = false )
- #displayTree( $display $item )
-##
- #if ( $alignedFileName == $currentItemHref || $display )
- #set ( $collapse = "expanded" )
- #end
- #end
- #set ( $active = "" )
- #if ( $alignedFileName == $currentItemHref )
- #set ($active = " active")
- #end
- #set ( $thisProjectDir = "../${project.artifactId}" )
- #if ($thisProjectDir == $PathTool.getDirectoryComponent( $item.href ))
- #set ($active = " active")
- #end
- #if (${project.artifactId} != "log4j" && $isComponentDocumentation &&
- ($item.href == "team-list.html" || $item.href == "mail-lists.html"
- || $item.href == "issue-tracking.html" || $item.href == "license.html"
- || $item.href == "source-repository.html"))
- <!-- Removing overall project item $item.name from component-specific menu -->
- #else
- #set ($thisItemName = $item.name)
- #if (${project.artifactId} != "log4j" && $isComponentDocumentation )
- #set ($thisItemName = $item.name.replace("Project Information", "Component Project"))
- #set ($thisItemName = $item.name.replace("Project", "Component"))
- #end
- <li class="$collapse$active">
- #link($currentItemHref $thisItemName $item.target $item.img $item.position $item.alt $item.border $item.width $item.height )
- #if ( $item && $item.items && $item.items.size() > 0 )
- #if ( $collapse == "expanded" )
- <ul>
- #foreach( $subitem in $item.items )
- #menuItem( $subitem $isComponentDocumentation )
- #end
- </ul>
- #end
- #end
- </li>
- #end
-#end
-##
-#macro ( mainMenu $menus )
- #foreach( $menu in $menus )
- <ul class="nav nav-list">
- #set ($isComponentDocumentation = false)
- #if ( $menu.name )
- #set ( $menuName = $menu.name )
- #if ( $menuName == "Project Documentation" )
- #set ( $menuName = "Component Documentation" )
- #set ($isComponentDocumentation = true)
- #end
- #if ( $menu.img )
- <li class="nav-header"><i class="$menu.img"></i>$menuName</li>
- #else
- <li class="nav-header">$menuName</li>
- #end
- #end
- #if ( $menu.items && $menu.items.size() > 0 )
- #foreach( $item in $menu.items )
- #menuItem( $item $isComponentDocumentation )
- #end
- #end
- </ul>
- #end
-#end
-##
-#macro ( copyright )
- #if ( $project )
- #if ( ${project.organization} && ${project.organization.name} )
- #set ( $period = "" )
- #else
- #set ( $period = "." )
- #end
-##
- #set ( $currentYear = ${currentDate.year} + 1900 )
-##
- #if ( ${project.inceptionYear} && ( ${project.inceptionYear} != ${currentYear.toString()} ) )
- ${project.inceptionYear}-${currentYear}${period}
- #else
- ${currentYear}${period}
- #end
-##
- #if ( ${project.organization} )
- #if ( ${project.organization.name} && ${project.organization.url} )
- <a href="$project.organization.url">${project.organization.name}</a>.
- #elseif ( ${project.organization.name} )
- ${project.organization.name}.
- #end
- #end
- #end
-#end
-##
-#macro ( publishDate $position $publishDate $version )
- #if ( $publishDate && $publishDate.format )
- #set ( $format = $publishDate.format )
- #else
- #set ( $format = "yyyy-MM-dd" )
- #end
-##
- $dateFormat.applyPattern( $format )
-##
- #set ( $dateToday = $dateFormat.format( $currentDate ) )
-##
- #if ( $publishDate && $publishDate.position )
- #set ( $datePosition = $publishDate.position )
- #else
- #set ( $datePosition = "left" )
- #end
-##
- #if ( $version )
- #if ( $version.position )
- #set ( $versionPosition = $version.position )
- #else
- #set ( $versionPosition = "left" )
- #end
- #else
- #set ( $version = "" )
- #set ( $versionPosition = "left" )
- #end
-##
- #set ( $breadcrumbs = $decoration.body.breadcrumbs )
- #set ( $links = $decoration.body.links )
-
- #if ( $datePosition.equalsIgnoreCase( $position ) )
- #if ( ( $datePosition.equalsIgnoreCase( "right" ) ) || ( $datePosition.equalsIgnoreCase( "bottom" ) ) )
- <span id="publishDate">$i18n.getString( "site-renderer", $locale, "template.lastpublished" ): $dateToday</span>
- #if ( $versionPosition.equalsIgnoreCase( $position ) )
- <span class="divider">|</span> <span id="projectVersion">$i18n.getString( "site-renderer", $locale, "template.version" ): ${project.version}</span>
- #end
- #elseif ( ( $datePosition.equalsIgnoreCase( "navigation-bottom" ) ) || ( $datePosition.equalsIgnoreCase( "navigation-top" ) ) )
- <div id="lastPublished">
- <span id="publishDate">$i18n.getString( "site-renderer", $locale, "template.lastpublished" ): $dateToday</span>
- #if ( $versionPosition.equalsIgnoreCase( $position ) )
- <span class="divider">|</span> <span id="projectVersion">$i18n.getString( "site-renderer", $locale, "template.version" ): ${project.version}</span>
- #end
- </div>
- #elseif ( $datePosition.equalsIgnoreCase("left") )
- <div class="pull-left">
- <span id="publishDate">$i18n.getString( "site-renderer", $locale, "template.lastpublished" ): $dateToday</span>
- #if ( $versionPosition.equalsIgnoreCase( $position ) )
- <span class="divider">|</span> <span id="projectVersion">$i18n.getString( "site-renderer", $locale, "template.version" ): ${project.version}</span>
- #end
- #if ( $breadcrumbs && $breadcrumbs.size() > 0 )
- <span class="divider">|</span> #breadcrumbs( $breadcrumbs )
- #end
- </div>
- #end
- #elseif ( $versionPosition.equalsIgnoreCase( $position ) )
- #if ( ( $versionPosition.equalsIgnoreCase( "right" ) ) || ( $versionPosition.equalsIgnoreCase( "bottom" ) ) )
- $prefix <span id="projectVersion">$i18n.getString( "site-renderer", $locale, "template.version" ): ${project.version}</span>
- #elseif ( ( $versionPosition.equalsIgnoreCase( "navigation-bottom" ) ) || ( $versionPosition.equalsIgnoreCase( "navigation-top" ) ) )
- <div id="lastPublished">
- <span id="projectVersion">$i18n.getString( "site-renderer", $locale, "template.version" ): ${project.version}</span>
- </div>
- #elseif ( $versionPosition.equalsIgnoreCase("left") )
- <div class="pull-left">
- <span id="projectVersion">$i18n.getString( "site-renderer", $locale, "template.version" ): ${project.version}</span>
- #if ( $breadcrumbs && $breadcrumbs.size() > 0 )
- <span class="divider">|</span> #breadcrumbs( $breadcrumbs )
- #end
- </div>
- #end
- #elseif ( $position.equalsIgnoreCase( "left" ) )
- #if ( $breadcrumbs && $breadcrumbs.size() > 0 )
- <div class="pull-left">
- #breadcrumbs( $breadcrumbs )
- </div>
- #end
- #end
-#end
-##
-#macro ( poweredByLogo $poweredBy )
- #if( $poweredBy )
- #foreach ($item in $poweredBy)
- #if( $item.href )
- #set ( $href = $PathTool.calculateLink( $item.href, $relativePath ) )
- #set ( $href = $href.replaceAll( '\\', '/' ) )
- #else
- #set ( $href="http://maven.apache.org/" )
- #end
-##
- #if( $item.name )
- #set ( $name = $item.name )
- #else
- #set ( $name = $i18n.getString( "site-renderer", $locale, "template.builtby" ) )
- #set ( $name = "${name} Maven" )
- #end
-##
- #if( $item.img )
- #set ( $img = $item.img )
- #else
- #set ( $img = "images/maven-feather.png" )
- #end
-##
- #if ( ! ( $img.toLowerCase().startsWith("http:/") || $img.toLowerCase().startsWith("https:/") ||
- $img.toLowerCase().startsWith("ftp:/") || $img.toLowerCase().startsWith("mailto:/") ||
- $img.toLowerCase().startsWith("file:/") || ($img.toLowerCase().indexOf("://") != -1) ) )
- #set ( $img = $PathTool.calculateLink( $img, $relativePath ) )
- #set ( $img = $img.replaceAll( '\\', '/' ) )
- #end
-##
- #if( $item.alt )
- #set ( $alt = ' alt="' + $item.alt + '"' )
- #else
- #set ( $alt = ' alt="' + $name + '"' )
- #end
-##
- #if( $item.border )
- #set ( $border = ' border="' + $item.border + '"' )
- #else
- #set ( $border = "" )
- #end
-##
- #if( $item.width )
- #set ( $width = ' width="' + $item.width + '"' )
- #else
- #set ( $width = "" )
- #end
- #if( $item.height )
- #set ( $height = ' height="' + $item.height + '"' )
- #else
- #set ( $height = "" )
- #end
-##
- <a href="$href" title="$name" class="poweredBy">
- <img class="poweredBy" $alt src="$img" $border $width $height />
- </a>
- #end
- #if( $poweredBy.isEmpty() )
- <a href="http://maven.apache.org/" title="$i18n.getString( "site-renderer", $locale, "template.builtby" ) Maven" class="poweredBy">
- <img class="poweredBy" alt="$i18n.getString( "site-renderer", $locale, "template.builtby" ) Maven" src="$relativePath/images/maven-feather.png" />
- </a>
- #end
- #else
- <a href="http://maven.apache.org/" title="$i18n.getString( "site-renderer", $locale, "template.builtby" ) Maven" class="poweredBy">
- <img class="poweredBy" alt="$i18n.getString( "site-renderer", $locale, "template.builtby" ) Maven" src="$relativePath/images/maven-feather.png" />
- </a>
- #end
-#end
-##
-#macro ( googleAnalytics $accountId )
- #if( $accountId && $accountId != "" )
- <!-- Google Analytics -->
- <script type="text/javascript">
-
- var _gaq = _gaq || [];
- _gaq.push(['_setAccount', '$accountId']);
- _gaq.push (['_gat._anonymizeIp']);
- _gaq.push(['_trackPageview']);
-
- (function() {
- var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
- ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
- var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
- })();
-
- </script>
- #end
-#end
-##
-<html xmlns="http://www.w3.org/1999/xhtml"#if ( $locale ) xml:lang="$locale.language" lang="$locale.language"#end>
- <head>
- <meta http-equiv="Content-Type" content="text/html; charset=${outputEncoding}" />
- <title>$title - $project.name</title>
- <link rel="stylesheet" href="$relativePath/css/bootstrap.min.css" type="text/css" />
- <link rel="stylesheet" href="$relativePath/css/github.css" type="text/css" />
- <link rel="stylesheet" href="$relativePath/css/site.css" type="text/css" />
- <script type="text/javascript" src="$relativePath/js/jquery.min.js"></script>
- <script type="text/javascript" src="$relativePath/js/bootstrap.min.js"></script>
- <script type="text/javascript" src="$relativePath/js/highlight.pack.js"></script>
- <script type="text/javascript">hljs.initHighlightingOnLoad();</script>
- <script type="text/javascript" src="$relativePath/js/site.js"></script>
-#foreach( $author in $authors )
- <meta name="author" content="$author" />
-#end
-#if ( $dateCreation )
- <meta name="Date-Creation-yyyymmdd" content="$dateCreation" />
-#end
-#if ( $dateRevision )
- <meta name="Date-Revision-yyyymmdd" content="$dateRevision" />
-#end
-#if ( $locale )
- <meta http-equiv="Content-Language" content="$locale.language" />
-#end
- $headContent
- #googleAnalytics( $decoration.googleAnalyticsAccountId )
- </head>
- <body class="composite">
- <a href="http://www.apache.org/events/current-event.html">
- <img class=logo-left src="http://www.apache.org/events/current-event-234x60.png"/>
- </a>
- <img class="logo-right" src="$relativePath/images/logo.png" alt="Apache log4j logo" />
- <a href="https://logging.apache.org/">
- <img class="logo-center" src="$relativePath/images/ls-logo.jpg" alt="Apache logging services logo" />
- </a>
-
- <div class="clear"></div>
-
- <div class="navbar">
- <div class="navbar-inner">
- <div class="container-fluid">
- <a class="brand" href="$project.url">$project.name ™</a>
- #links( $decoration.body.links )
- </div>
- </div>
- </div>
-
- <div class="container-fluid">
- <table class="layout-table">
- <tr>
- <td class="sidebar">
- <div class="well sidebar-nav">
- #mainMenu( $decoration.body.menus )
- </div>
- <div id="poweredBy">
- #poweredByLogo( $decoration.poweredBy )
- </div>
- </td>
- <td class="content">
- $bodyContent
- </td>
- </tr>
- </table>
- </div>
-
- <div class="footer">
- #set ( $currentYear = ${currentDate.year} + 1900 )
- <p>Copyright © ${project.inceptionYear}-${currentYear} <a class="external" href="$project.organization.url">${project.organization.name}</a>. All Rights Reserved.</p>
- <p>Apache Logging, Apache Log4j, Log4j, Apache, the Apache feather logo, and the Apache Logging project logo are trademarks of The Apache Software Foundation.</p>
- <p>Site powered by <a class="external" href="http://getbootstrap.com/">Twitter Bootstrap</a>. Icons from <a class="external" href="http://glyphicons.com/">Glyphicons Free</a>.</p>
- </div>
- </div>
- </body>
-</html>
diff --git a/src/site/site.xml b/src/site/site.xml
index 01bfb83..48a6d3e 100644
--- a/src/site/site.xml
+++ b/src/site/site.xml
@@ -19,6 +19,19 @@
xmlns="http://maven.apache.org/DECORATION/1.4.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/DECORATION/1.4.0 http://maven.apache.org/xsd/decoration-1.4.0.xsd">
+ <bannerLeft>
+ <src>images/ls-logo.jpg</src>
+ <href>http://logging.apache.org</href>
+ </bannerLeft>
+ <bannerRight>
+ <src>images/logo.png</src>
+ <href>http://logging.apache.org/log4j/2.x</href>
+ </bannerRight>
+ <skin>
+ <groupId>org.apache.maven.skins</groupId>
+ <artifactId>maven-fluido-skin</artifactId>
+ <version>1.8</version>
+ </skin>
<body>
<links>
<item name="Logging Wiki" href="https://cwiki.apache.org/confluence/display/LOGGING/Log4j"/>
@@ -28,7 +41,7 @@
<item name="GitHub" href="https://github.com/apache/logging-log4j2"/>
</links>
- <menu name="Apache Log4j™ 2" inherit="top" img="icon-home">
+ <menu name="Apache Log4j™ 2" inherit="top" img="img/glyphicons/home.png">
<item name="About" href="/index.html"/>
<item name="Download" href="/download.html"/>
<item name="Javadoc" href="/javadoc.html" collapse="true">
@@ -61,21 +74,25 @@
<item name="Trade-offs" href="/performance.html#tradeoffs" />
</item>
<item name="Articles and Tutorials" href="/articles.html"/>
+ <item name="Security" href="/security.html"/>
+ <item name="Support" href="/support.html"/>
<item name="Thanks" href="/thanks.html"/>
</menu>
- <menu name="For Contributors" inherit="top" img="icon-pencil">
+ <menu name="For Contributors" inherit="top" img="img/glyphicons/pencil.png">
<item name="Building Log4j from Source" href="/build.html"/>
<item name="Guidelines" href="/guidelines.html"/>
<item name="Style Guide" href="/javastyle.html"/>
</menu>
- <menu name="Manual" inherit="top" img="icon-book">
+ <menu name="Manual" inherit="top" img="img/glyphicons/book.png">
<item name="Introduction" href="/manual/index.html"/>
<item name="Architecture" href="/manual/architecture.html"/>
+ <item name="Log4j 1.x Compatibility" href="manual/compatibility.html"/>
<item name="Log4j 1.x Migration" href="manual/migration.html"/>
<item name="Java API" href="/manual/api.html" collapse="true">
+ <item name="Log Builder" href="manual/logbuilder.html"/>
<item name="Flow Tracing" href="manual/flowtracing.html"/>
<item name="Markers" href="manual/markers.html"/>
<item name="Event Logging" href="/manual/eventlogging.html"/>
@@ -111,6 +128,7 @@
<item name="Usage" href="/manual/usage.html" collapse="true">
<item name="Static vs non-Static Loggers"/>
<item name="Logger Name vs Class Name"/>
+ <item name="Logging in the Cloud" href="/manual/cloud.html"/>
</item>
<item name="Web Applications and JSPs" href="/manual/webapp.html" collapse="true">
@@ -125,15 +143,21 @@
<item name="Lookups" href="/manual/lookups.html" collapse="true">
<item name="Context Map" href="/manual/lookups.html#ContextMapLookup"/>
<item name="Date" href="/manual/lookups.html#DateLookup"/>
+ <item name="Docker" href="manual/lookups.html#DockerLookup"/>
<item name="Environment" href="/manual/lookups.html#EnvironmentLookup"/>
+ <item name="Event" href="/manual/lookups.html#EventLookup"/>
<item name="Java" href="/manual/lookups.html#JavaLookup"/>
<item name="JNDI" href="/manual/lookups.html#JndiLookup"/>
<item name="JVM Arguments" href="/manual/lookups.html#JmxRuntimeInputArgumentsLookup"/>
+ <item name="Kubernetes" href="/manual/lookups.html#KubernetesLookup"/>
<item name="Log4j Config" href="/manual/lookups.html#Log4jConfigLookup"/>
+ <item name="Lower" href="/manual/lookups.html#LowerLookup"/>
<item name="Main Arguments" href="/manual/lookups.html#AppMainArgsLookup"/>
<item name="Map" href="/manual/lookups.html#MapLookup"/>
+ <item name="Spring" href="/manual/lookups.html#SpringLookup"/>
<item name="Structured Data" href="/manual/lookups.html#StructuredDataLookup"/>
<item name="System Properties" href="/manual/lookups.html#SystemPropertiesLookup"/>
+ <item name="Upper" href="/manual/lookups.html#UpperLookup"/>
<item name="Web" href="/manual/lookups.html#WebLookup"/>
</item>
@@ -152,8 +176,8 @@
<item name="Memory Mapped File" href="/manual/appenders.html#MemoryMappedFileAppender"/>
<item name="NoSQL" href="/manual/appenders.html#NoSQLAppender"/>
<item name="NoSQL for MongoDB" href="/manual/appenders.html#NoSQLAppenderMongoDB"/>
- <item name="NoSQL for MongoDB 2" href="/manual/appenders.html#NoSQLAppenderMongoDB2"/>
<item name="NoSQL for MongoDB 3" href="/manual/appenders.html#NoSQLAppenderMongoDB3"/>
+ <item name="NoSQL for MongoDB 4" href="/manual/appenders.html#NoSQLAppenderMongoDB4"/>
<item name="NoSQL for CouchDB" href="/manual/appenders.html#NoSQLAppenderCouchDB"/>
<item name="Output Stream" href="/manual/appenders.html#OutputStreamAppender"/>
<item name="Random Access File" href="/manual/appenders.html#RandomAccessFileAppender"/>
@@ -175,6 +199,7 @@
<item name="GELF" href="/manual/layouts.html#GELFLayout"/>
<item name="HTML" href="/manual/layouts.html#HTMLLayout"/>
<item name="JSON" href="/manual/layouts.html#JSONLayout"/>
+ <item name="JSON Template" href="/manual/json-template-layout.html"/>
<item name="Pattern" href="/manual/layouts.html#PatternLayout"/>
<item name="RFC-5424" href="/manual/layouts.html#RFC5424Layout"/>
<item name="Serialized" href="/manual/layouts.html#SerializedLayout"/>
@@ -268,22 +293,24 @@
</menu>
- <menu name="Related Projects" inherit="top" img="icon-tags">
+ <menu name="Related Projects" inherit="top" img="img/glyphicons/tag.png">
<item name="Log4j-Scala" href="http://logging.apache.org/log4j/scala/index.html"/>
</menu>
- <menu name="Legacy" inherit="top" img="icon-tags">
- <item name="Log4j 1.2" href="http://logging.apache.org/log4j/1.2/"/>
- <item name="Log4j 2.3" href="http://logging.apache.org/log4j/log4j-2.3/"/>
+ <menu name="Legacy" inherit="top" img="img/glyphicons/link.png">
+ <item name="Log4j 1.2 - End of Life" href="http://logging.apache.org/log4j/1.2/"/>
+ <item name="Log4j 2.3 - Java 6" href="http://logging.apache.org/log4j/log4j-2.3/"/>
+ <item name="Log4j 2.12.1 - Java 7" href="http://logging.apache.org/log4j/log4j-2.12.1/"/>
</menu>
- <menu name="Components" inherit="top" img="icon-cog">
+ <menu name="Components" inherit="top" img="img/glyphicons/cog.png">
<item name="API" href="log4j-api/index.html"/>
<item name="Implementation" href="log4j-core/index.html"/>
<item name="Commons Logging Bridge" href="log4j-jcl/index.html"/>
<item name="Log4j 1.2 API" href="log4j-1.2-api/index.html"/>
<item name="SLF4J Binding" href="log4j-slf4j-impl/index.html"/>
<item name="JUL Adapter" href="log4j-jul/index.html"/>
+ <item name="JDK Platform Logger" href="log4j-jpl/index.html"/>
<item name="Log4j 2 to SLF4J Adapter" href="log4j-to-slf4j/index.html"/>
<item name="Apache Flume Appender" href="log4j-flume-ng/index.html"/>
<item name="Log4j Tag Library" href="log4j-taglib/index.html"/>
@@ -291,14 +318,17 @@
<item name="Log4j Web Application Support" href="log4j-web/index.html"/>
<item name="Log4j Application Server Integration" href="log4j-appserver/index.html"/>
<item name="Log4j CouchDB appender" href="log4j-couchdb/index.html"/>
- <item name="Log4j MongoDB2 appender" href="log4j-mongodb2/index.html"/>
<item name="Log4j MongoDB3 appender" href="log4j-mongodb3/index.html"/>
+ <item name="Log4j MongoDB4 appender" href="log4j-mongodb4/index.html"/>
<item name="Log4j Cassandra appender" href="log4j-cassandra/index.html"/>
<item name="Log4j IO Streams" href="log4j-iostreams/index.html"/>
<item name="Log4j Liquibase Binding" href="log4j-liquibase/index.html"/>
+ <item name="Log4j Docker Support" href="log4j-docker/index.html"/>
+ <item name="Lob4j Kubernetes Support" href="log4j-kubernetes/index.html"/>
+ <item name="Log4j Spring Cloud Config Client" href="log4j-spring-cloud-config/log4j-spring-cloud-config-client/index.html"/>
</menu>
- <menu name="Project Information" img="icon-info-sign">
+ <menu name="Project Information" img="img/glyphicons/info.png">
<item name="Dependency Convergence" href="/dependency-convergence.html" />
<item name="Dependency Management" href="/dependency-management.html" />
<item name="Project Team" href="/team-list.html" />
@@ -309,10 +339,13 @@
<item name="Project Summary" href="/project-summary.html" />
</menu>
- <menu name="Project Reports" img="icon-cog">
+ <menu name="Project Reports" img="img/glyphicons/layers.png">
<item name="Changes Report" href="/changes-report.html" />
<item name="JIRA Report" href="/jira-report.html" />
<item name="RAT Report" href="/rat-report.html" />
</menu>
+ <footer><![CDATA[<p align="center">Copyright © ${project.inceptionYear}-${currentYear} <a class="external" href="http://www.apache.org">The Apache Software Foundation</a>. All Rights Reserved.<br>
+ Apache Logging, Apache Log4j, Log4j, Apache, the Apache feather logo, and the Apache Logging project logo are trademarks of The Apache Software Foundation.</p>]]>
+ </footer>
</body>
</project>
diff --git a/toolchains-docker.xml b/toolchains-docker.xml
index 844e3ec..3f924a2 100644
--- a/toolchains-docker.xml
+++ b/toolchains-docker.xml
@@ -16,27 +16,24 @@
~ limitations under the license.
-->
<toolchains>
- <!-- JDK toolchains -->
<toolchain>
<type>jdk</type>
<provides>
- <version>1.7</version>
- <vendor>sun</vendor>
+ <version>1.8</version>
+ <vendor>openjdk</vendor>
</provides>
<configuration>
- <jdkHome>/docker-java-home</jdkHome>
+ <jdkHome>/usr/local/openjdk-8</jdkHome>
</configuration>
</toolchain>
<toolchain>
<type>jdk</type>
<provides>
- <version>9</version>
- <vendor>sun</vendor>
+ <version>11</version>
+ <vendor>openjdk</vendor>
</provides>
<configuration>
- <jdkHome>/docker-java-9-home</jdkHome>
+ <jdkHome>/usr/local/openjdk-11</jdkHome>
</configuration>
</toolchain>
-
- <!-- other toolchains -->
-</toolchains>
\ No newline at end of file
+</toolchains>
diff --git a/toolchains-jenkins-ibm.xml b/toolchains-jenkins-ibm.xml
deleted file mode 100644
index 4fdd5c6..0000000
--- a/toolchains-jenkins-ibm.xml
+++ /dev/null
@@ -1,63 +0,0 @@
-<?xml version="1.0" encoding="UTF8"?>
-<!--
- ~ Licensed to the Apache Software Foundation (ASF) under one or more
- ~ contributor license agreements. See the NOTICE file distributed with
- ~ this work for additional information regarding copyright ownership.
- ~ The ASF licenses this file to You under the Apache license, Version 2.0
- ~ (the "License"); you may not use this file except in compliance with
- ~ the License. You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the license for the specific language governing permissions and
- ~ limitations under the license.
- -->
-<toolchains>
- <!-- JDK toolchains -->
- <toolchain>
- <type>jdk</type>
- <provides>
- <version>1.7</version>
- <vendor>ibm</vendor>
- </provides>
- <configuration>
- <jdkHome>/home/jenkins/tools/java/ibm-1.7-64</jdkHome>
- </configuration>
- </toolchain>
- <toolchain>
- <type>jdk</type>
- <provides>
- <version>1.8</version>
- <vendor>ibm</vendor>
- </provides>
- <configuration>
- <jdkHome>/home/jenkins/tools/java/ibm-java-x86_64-80</jdkHome>
- </configuration>
- </toolchain>
- <!-- TODO: configure OpenJ9/J9 whenever it's installed -->
- <toolchain>
- <type>jdk</type>
- <provides>
- <version>9</version>
- <vendor>sun</vendor>
- </provides>
- <configuration>
- <jdkHome>/home/jenkins/tools/java/latest1.9</jdkHome>
- </configuration>
- </toolchain>
- <toolchain>
- <type>jdk</type>
- <provides>
- <version>10</version>
- <vendor>sun</vendor>
- </provides>
- <configuration>
- <jdkHome>/home/jenkins/tools/java/latest10</jdkHome>
- </configuration>
- </toolchain>
-
- <!-- other toolchains -->
-</toolchains>
diff --git a/toolchains-jenkins-ubuntu.xml b/toolchains-jenkins-ubuntu.xml
deleted file mode 100644
index 5821ce6..0000000
--- a/toolchains-jenkins-ubuntu.xml
+++ /dev/null
@@ -1,62 +0,0 @@
-<?xml version="1.0" encoding="UTF8"?>
-<!--
- ~ Licensed to the Apache Software Foundation (ASF) under one or more
- ~ contributor license agreements. See the NOTICE file distributed with
- ~ this work for additional information regarding copyright ownership.
- ~ The ASF licenses this file to You under the Apache license, Version 2.0
- ~ (the "License"); you may not use this file except in compliance with
- ~ the License. You may obtain a copy of the License at
- ~
- ~ http://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the license for the specific language governing permissions and
- ~ limitations under the license.
- -->
-<toolchains>
- <!-- JDK toolchains -->
- <toolchain>
- <type>jdk</type>
- <provides>
- <version>1.7</version>
- <vendor>sun</vendor>
- </provides>
- <configuration>
- <jdkHome>/home/jenkins/tools/java/latest1.7</jdkHome>
- </configuration>
- </toolchain>
- <toolchain>
- <type>jdk</type>
- <provides>
- <version>1.8</version>
- <vendor>sun</vendor>
- </provides>
- <configuration>
- <jdkHome>/home/jenkins/tools/java/latest1.8</jdkHome>
- </configuration>
- </toolchain>
- <toolchain>
- <type>jdk</type>
- <provides>
- <version>9</version>
- <vendor>sun</vendor>
- </provides>
- <configuration>
- <jdkHome>/home/jenkins/tools/java/latest1.9</jdkHome>
- </configuration>
- </toolchain>
- <toolchain>
- <type>jdk</type>
- <provides>
- <version>10</version>
- <vendor>sun</vendor>
- </provides>
- <configuration>
- <jdkHome>/home/jenkins/tools/java/latest10</jdkHome>
- </configuration>
- </toolchain>
-
- <!-- other toolchains -->
-</toolchains>
diff --git a/toolchains-jenkins-win.xml b/toolchains-jenkins-win.xml
deleted file mode 100644
index 51438bb..0000000
--- a/toolchains-jenkins-win.xml
+++ /dev/null
@@ -1,52 +0,0 @@
-<?xml version="1.0" encoding="UTF8"?>
-<!--
- ~ Licensed to the Apache Software Foundation (ASF) under one or more
- ~ contributor license agreements. See the NOTICE file distributed with
- ~ this work for additional information regarding copyright ownership.
- ~ The ASF licenses this file to You under the Apache License, Version 2.0
- ~ (the "License"); you may not use this file except in compliance with
- ~ the License. You may obtain a copy of the License at
- ~
- ~ https://www.apache.org/licenses/LICENSE-2.0
- ~
- ~ Unless required by applicable law or agreed to in writing, software
- ~ distributed under the License is distributed on an "AS IS" BASIS,
- ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- ~ See the License for the specific language governing permissions and
- ~ limitations under the License.
- -->
-<toolchains>
- <!-- JDK toolchains -->
- <toolchain>
- <type>jdk</type>
- <provides>
- <version>1.7</version>
- <vendor>sun</vendor>
- </provides>
- <configuration>
- <jdkHome>F:\jenkins\tools\java\latest1.7</jdkHome>
- </configuration>
- </toolchain>
- <toolchain>
- <type>jdk</type>
- <provides>
- <version>1.8</version>
- <vendor>sun</vendor>
- </provides>
- <configuration>
- <jdkHome>F:\jenkins\tools\java\latest1.8</jdkHome>
- </configuration>
- </toolchain>
- <toolchain>
- <type>jdk</type>
- <provides>
- <version>9</version>
- <vendor>sun</vendor>
- </provides>
- <configuration>
- <jdkHome>F:\jenkins\tools\java\latest9</jdkHome>
- </configuration>
- </toolchain>
-
- <!-- other toolchains -->
-</toolchains>