[LOG4J2-1809]: Add global configuration environment SPI This adds a PropertySource service provider interface for providing global configuration properties. This also refactors support in LOG4J2-1431 to use these PropertySource providers.
Project: http://git-wip-us.apache.org/repos/asf/logging-log4j2/repo Commit: http://git-wip-us.apache.org/repos/asf/logging-log4j2/commit/d777b6f5 Tree: http://git-wip-us.apache.org/repos/asf/logging-log4j2/tree/d777b6f5 Diff: http://git-wip-us.apache.org/repos/asf/logging-log4j2/diff/d777b6f5 Branch: refs/heads/LOG4J2-1431 Commit: d777b6f559de7c9052a9bef77909df0a0f25fa29 Parents: 6744c8c Author: Matt Sicker <[email protected]> Authored: Sun Feb 5 12:45:19 2017 -0600 Committer: Matt Sicker <[email protected]> Committed: Sat Aug 26 15:50:56 2017 -0500 ---------------------------------------------------------------------- .../log4j/util/EnvironmentPropertySource.java | 55 +++++++++ .../log4j/util/PropertiesPropertySource.java | 54 ++++++++ .../logging/log4j/util/PropertiesUtil.java | 123 ++++--------------- .../log4j/util/PropertyFilePropertySource.java | 52 ++++++++ .../logging/log4j/util/PropertySource.java | 115 +++++++++++++++++ .../util/SystemPropertiesPropertySource.java | 48 ++++++++ ...org.apache.logging.log4j.util.PropertySource | 16 +++ 7 files changed, 367 insertions(+), 96 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/d777b6f5/log4j-api/src/main/java/org/apache/logging/log4j/util/EnvironmentPropertySource.java ---------------------------------------------------------------------- diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/EnvironmentPropertySource.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/EnvironmentPropertySource.java new file mode 100644 index 0000000..d413d61 --- /dev/null +++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/EnvironmentPropertySource.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.util; + +import java.util.Map; + +/** + * PropertySource implementation that uses environment variables as a source. All environment variables must begin + * with {@code LOG4J_} so as not to conflict with other variables. Normalized environment variables follow a scheme + * like this: {@code log4j2.fooBarProperty} would normalize to {@code LOG4J_FOO_BAR_PROPERTY}. + * + * @since 2.9 + */ +public class EnvironmentPropertySource implements PropertySource { + @Override + public int getPriority() { + return -100; + } + + @Override + public void forEach(final BiConsumer<String, String> action) { + for (final Map.Entry<String, String> entry : System.getenv().entrySet()) { + final String key = entry.getKey(); + if (key.startsWith("LOG4J_")) { + action.accept(key.substring(6), entry.getValue()); + } + } + } + + @Override + public CharSequence getNormalForm(final Iterable<? extends CharSequence> tokens) { + final StringBuilder sb = new StringBuilder("LOG4J"); + for (final CharSequence token : tokens) { + sb.append('_'); + for (int i = 0; i < token.length(); i++) { + sb.append(Character.toUpperCase(token.charAt(i))); + } + } + return sb.toString(); + } +} http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/d777b6f5/log4j-api/src/main/java/org/apache/logging/log4j/util/PropertiesPropertySource.java ---------------------------------------------------------------------- diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/PropertiesPropertySource.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/PropertiesPropertySource.java new file mode 100644 index 0000000..7622509 --- /dev/null +++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/PropertiesPropertySource.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; + +import java.util.Map; +import java.util.Properties; + +/** + * PropertySource backed by a {@link Properties} instance. Normalized property names follow a scheme like this: + * {@code Log4jContextSelector} would normalize to {@code log4j2.contextSelector}. + * + * @since 2.9 + */ +public class PropertiesPropertySource implements PropertySource { + + private static final String PREFIX = "log4j2."; + + private final Properties properties; + + public PropertiesPropertySource(final Properties properties) { + this.properties = properties; + } + + @Override + public int getPriority() { + return 0; + } + + @Override + public void forEach(final BiConsumer<String, String> action) { + for (final Map.Entry<Object, Object> entry : properties.entrySet()) { + action.accept(((String) entry.getKey()), ((String) entry.getValue())); + } + } + + @Override + public CharSequence getNormalForm(final Iterable<? extends CharSequence> tokens) { + return PREFIX + Util.joinAsCamelCase(tokens); + } +} http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/d777b6f5/log4j-api/src/main/java/org/apache/logging/log4j/util/PropertiesUtil.java ---------------------------------------------------------------------- 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 b760076..46f3344 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 @@ -18,16 +18,17 @@ package org.apache.logging.log4j.util; import java.io.IOException; import java.io.InputStream; -import java.net.URL; import java.nio.charset.Charset; import java.nio.charset.UnsupportedCharsetException; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Properties; +import java.util.ServiceLoader; +import java.util.Set; +import java.util.TreeSet; import java.util.concurrent.ConcurrentHashMap; -import java.util.regex.Matcher; -import java.util.regex.Pattern; /** * <em>Consider this class private.</em> @@ -48,7 +49,7 @@ public final class PropertiesUtil { * @param props the Properties to use by default */ public PropertiesUtil(final Properties props) { - this.environment = new Environment(props); + this.environment = new Environment(new PropertiesPropertySource(props)); } /** @@ -58,15 +59,7 @@ public final class PropertiesUtil { * @param propertiesFileName the location of properties file to load */ public PropertiesUtil(final String propertiesFileName) { - final Properties properties = new Properties(); - for (final URL url : LoaderUtil.findResources(propertiesFileName)) { - try (final InputStream in = url.openStream()) { - properties.load(in); - } catch (final IOException ioe) { - LowLevelLogUtil.logException("Unable to read " + url.toString(), ioe); - } - } - this.environment = new Environment(properties); + this.environment = new Environment(new PropertyFilePropertySource(propertiesFileName)); } /** @@ -286,93 +279,31 @@ public final class PropertiesUtil { */ private static class Environment { - private static final String NORMALIZED_PREFIX = "log4j2."; - private static final String PREFIXES = "(?:[Ll]og4j2?|org\\.apache\\.logging\\.log4j\\.)?"; - private static final Pattern PROPERTY_TOKENIZER = Pattern.compile(PREFIXES + "([A-Z]*[a-z0-9]+)[-._/]?"); - - private final Map<String, String> literal = new ConcurrentHashMap<>(); - private final Map<String, String> normalized = new ConcurrentHashMap<>(); + private final Map<CharSequence, String> literal = new ConcurrentHashMap<>(); + private final Map<CharSequence, String> normalized = new ConcurrentHashMap<>(); private final Map<List<CharSequence>, String> tokenized = new ConcurrentHashMap<>(); - private Environment(final Properties props) { - for (final Map.Entry<String, String> entry : System.getenv().entrySet()) { - final String envKey = entry.getKey(); - final String value = entry.getValue(); - if (envKey.startsWith("LOG4J_")) { - final String key = envKey.substring(6); - literal.put(key, value); - final List<CharSequence> tokens = tokenize(envKey); - if (!tokens.isEmpty()) { - tokenized.put(tokens, value); - normalized.put("LOG4J" + joinAsAllCapsUnderscored(tokens), value); - } - } - } - for (final Map.Entry<Object, Object> entry : props.entrySet()) { - final String key = (String) entry.getKey(); - final String value = (String) entry.getValue(); - literal.put(key, value); - final List<CharSequence> tokens = tokenize(key); - if (tokens.isEmpty()) { - normalized.put(NORMALIZED_PREFIX + key, value); - } else { - tokenized.put(tokens, value); - normalized.put(NORMALIZED_PREFIX + joinAsCamelCase(tokens), value); - } - - } - final Properties systemProperties = getSystemProperties(); - for (Map.Entry<Object, Object> entry : systemProperties.entrySet()) { - final String key = (String) entry.getKey(); - final String value = (String) entry.getValue(); - literal.put(key, value); - // TODO: should this include tokenized lookups? could get screwy - final List<CharSequence> tokens = tokenize(key); - if (tokens.isEmpty()) { - normalized.put(NORMALIZED_PREFIX + key, value); - } else { - tokenized.put(tokens, value); - normalized.put(NORMALIZED_PREFIX + joinAsCamelCase(tokens), value); - } - } - } - - private static CharSequence joinAsAllCapsUnderscored(final List<CharSequence> tokens) { - final StringBuilder sb = new StringBuilder(); - for (final CharSequence token : tokens) { - sb.append('_'); - for (int i = 0; i < token.length(); i++) { - sb.append(Character.toUpperCase(token.charAt(i))); - } + private Environment(final PropertySource propertySource) { + final Set<PropertySource> sources = new TreeSet<>(new PropertySource.Comparator()); + sources.add(propertySource); + for (final PropertySource source : ServiceLoader.load(PropertySource.class)) { + sources.add(source); } - return sb.toString(); - } - - private static CharSequence joinAsCamelCase(final List<CharSequence> tokens) { - final StringBuilder sb = new StringBuilder(); - boolean first = true; - for (final CharSequence token : tokens) { - if (first) { - sb.append(token); - } else { - sb.append(Character.toUpperCase(token.charAt(0))); - if (token.length() > 1) { - sb.append(token.subSequence(1, token.length())); + for (final PropertySource source : sources) { + source.forEach(new BiConsumer<String, String>() { + @Override + public void accept(final String key, final String value) { + literal.put(key, value); + final List<CharSequence> tokens = PropertySource.Util.tokenize(key); + if (tokens.isEmpty()) { + normalized.put(source.getNormalForm(Collections.singleton(key)), value); + } else { + normalized.put(source.getNormalForm(tokens), value); + tokenized.put(tokens, value); + } } - } - first = false; - } - return sb.toString(); - } - - private static List<CharSequence> tokenize(final CharSequence value) { - // TODO: cache? - List<CharSequence> tokens = new ArrayList<>(); - final Matcher matcher = PROPERTY_TOKENIZER.matcher(value); - while (matcher.find()) { - tokens.add(matcher.group(1).toLowerCase()); + }); } - return tokens; } private String get(final String key) { @@ -391,7 +322,7 @@ public final class PropertiesUtil { if (prop != null) { return prop; } - return tokenized.get(tokenize(key)); + return tokenized.get(PropertySource.Util.tokenize(key)); } } http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/d777b6f5/log4j-api/src/main/java/org/apache/logging/log4j/util/PropertyFilePropertySource.java ---------------------------------------------------------------------- diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/PropertyFilePropertySource.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/PropertyFilePropertySource.java new file mode 100644 index 0000000..2920460 --- /dev/null +++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/PropertyFilePropertySource.java @@ -0,0 +1,52 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.util; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.Properties; + +/** + * PropertySource backed by a properties file. Follows the same conventions as {@link PropertiesPropertySource}. + * + * @since 2.9 + */ +public class PropertyFilePropertySource extends PropertiesPropertySource { + + public PropertyFilePropertySource(final String fileName) { + super(loadPropertiesFile(fileName)); + } + + private static Properties loadPropertiesFile(final String fileName) { + final Properties props = new Properties(); + for (final URL url : LoaderUtil.findResources(fileName)) { + try (final InputStream in = url.openStream()) { + props.load(in); + } catch (IOException e) { + LowLevelLogUtil.logException("Unable to read " + url, e); + } + } + return props; + } + + @Override + public int getPriority() { + return 0; + } + +} http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/d777b6f5/log4j-api/src/main/java/org/apache/logging/log4j/util/PropertySource.java ---------------------------------------------------------------------- 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 new file mode 100644 index 0000000..4dd9cf3 --- /dev/null +++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/PropertySource.java @@ -0,0 +1,115 @@ +package org.apache.logging.log4j.util; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * A source for global configuration properties. + * + * @since 2.9 + */ +public interface PropertySource { + + /** + * Returns the order in which this PropertySource has priority. A higher value means that the source will be + * applied later so as to take precedence over other property sources. + * + * @return priority value + */ + int getPriority(); + + /** + * Iterates over all properties and performs an action for each key/value pair. + * + * @param action action to perform on each key/value pair + */ + 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 + * "foo", "bar", "baz", might be normalized into the property name "log4j2.fooBarBaz". + * + * @param tokens list of property name tokens + * @return a normalized property name using the given tokens + */ + CharSequence getNormalForm(Iterable<? extends CharSequence> tokens); + + /** + * Comparator for ordering PropertySource instances by priority. + * + * @since 2.9 + */ + class Comparator implements java.util.Comparator<PropertySource>, Serializable { + private static final long serialVersionUID = 1L; + + @Override + public int compare(final PropertySource o1, final PropertySource o2) { + return Integer.compare(Objects.requireNonNull(o1).getPriority(), Objects.requireNonNull(o2).getPriority()); + } + } + + /** + * Utility methods useful for PropertySource implementations. + * + * @since 2.9 + */ + final class Util { + private static final String PREFIXES = "(?:[Ll]og4j2?|org\\.apache\\.logging\\.log4j\\.)?"; + private static final Pattern PROPERTY_TOKENIZER = Pattern.compile(PREFIXES + "([A-Z]*[a-z0-9]+)[-._/]?"); + private static final Map<CharSequence, List<CharSequence>> CACHE = new ConcurrentHashMap<>(); + + /** + * Converts a property name string into a list of tokens. This will strip a prefix of {@code log4j}, + * {@code log4j2}, {@code Log4j}, or {@code org.apache.logging.log4j}, along with separators of + * dash {@code -}, dot {@code .}, underscore {@code _}, and slash {@code /}. Tokens can also be separated + * by camel case conventions without needing a separator character in between. + * + * @param value property name + * @return the property broken into lower case tokens + */ + public static List<CharSequence> tokenize(final CharSequence value) { + if (CACHE.containsKey(value)) { + return CACHE.get(value); + } + List<CharSequence> tokens = new ArrayList<>(); + final Matcher matcher = PROPERTY_TOKENIZER.matcher(value); + while (matcher.find()) { + tokens.add(matcher.group(1).toLowerCase()); + } + CACHE.put(value, tokens); + return tokens; + } + + /** + * Joins a list of strings using camelCaseConventions. + * + * @param tokens tokens to convert + * @return tokensAsCamelCase + */ + public static CharSequence joinAsCamelCase(final Iterable<? extends CharSequence> tokens) { + final StringBuilder sb = new StringBuilder(); + boolean first = true; + for (final CharSequence token : tokens) { + if (first) { + sb.append(token); + } else { + sb.append(Character.toUpperCase(token.charAt(0))); + if (token.length() > 1) { + sb.append(token.subSequence(1, token.length())); + } + } + first = false; + } + return sb.toString(); + } + + private Util() { + } + } +} http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/d777b6f5/log4j-api/src/main/java/org/apache/logging/log4j/util/SystemPropertiesPropertySource.java ---------------------------------------------------------------------- diff --git a/log4j-api/src/main/java/org/apache/logging/log4j/util/SystemPropertiesPropertySource.java b/log4j-api/src/main/java/org/apache/logging/log4j/util/SystemPropertiesPropertySource.java new file mode 100644 index 0000000..8733f23 --- /dev/null +++ b/log4j-api/src/main/java/org/apache/logging/log4j/util/SystemPropertiesPropertySource.java @@ -0,0 +1,48 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache license, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the license for the specific language governing permissions and + * limitations under the license. + */ +package org.apache.logging.log4j.util; + +import java.util.Map; + +/** + * PropertySource backed by the current system properties. Other than having a higher priority over normal properties, + * this follows the same rules as {@link PropertiesPropertySource}. + * + * @since 2.9 + */ +public class SystemPropertiesPropertySource implements PropertySource { + + private static final String PREFIX = "log4j2."; + + @Override + public int getPriority() { + return 100; + } + + @Override + public void forEach(final BiConsumer<String, String> action) { + for (final Map.Entry<Object, Object> entry : System.getProperties().entrySet()) { + action.accept(((String) entry.getKey()), ((String) entry.getValue())); + } + } + + @Override + public CharSequence getNormalForm(final Iterable<? extends CharSequence> tokens) { + return PREFIX + Util.joinAsCamelCase(tokens); + } + +} http://git-wip-us.apache.org/repos/asf/logging-log4j2/blob/d777b6f5/log4j-api/src/main/resources/META-INF/services/org.apache.logging.log4j.util.PropertySource ---------------------------------------------------------------------- diff --git a/log4j-api/src/main/resources/META-INF/services/org.apache.logging.log4j.util.PropertySource b/log4j-api/src/main/resources/META-INF/services/org.apache.logging.log4j.util.PropertySource new file mode 100644 index 0000000..39c959c --- /dev/null +++ b/log4j-api/src/main/resources/META-INF/services/org.apache.logging.log4j.util.PropertySource @@ -0,0 +1,16 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache license, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the license for the specific language governing permissions and +# limitations under the license. +org.apache.logging.log4j.util.EnvironmentPropertySource +org.apache.logging.log4j.util.SystemPropertiesPropertySource \ No newline at end of file
