Skip to content

Commit e0ccc53

Browse files
authored
Add support for Log4j 1 configuration formats (#145)
This adds support for Log4j 1 configuration formats to the Configuration Converter API Since Log4j Core 2 is a complete rewrite for Log4j 1, the converter: - Needs to know exactly what configuration parameters a Log4j 1 component supports to create an equivalent Log4j Core 2 configuration. - Introduces a pluggable `spi/v1/Log4j1ComponentParser` interface. For each supported Log4j 1 component, an implementation of this interface must be provided and registered with `ServiceLoader`. The following Log4j 1 components are currently supported: - Appenders: `ConsoleAppender`, `DailyRollingFileAppender`, `FileAppender` and `RollingFileAppender`. - Filters: `DenyAllFilter`, `LevelMatchFilter`, `LevelRangeFilter` and `StringMatchFilter`. - Layouts: `HTMLLayout`, `PatternLayout`, `SimpleLayout`, `TTCCLayout`. Part of apache/logging-log4j2#3220
1 parent f4392b1 commit e0ccc53

File tree

47 files changed

+3910
-64
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+3910
-64
lines changed

log4j-converter-config/src/main/java/org/apache/logging/converter/config/internal/ComponentUtils.java

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -33,17 +33,24 @@ public static ConfigurationNodeBuilder newNodeBuilder() {
3333
return new ConfigurationNodeBuilder();
3434
}
3535

36-
public static ConfigurationNode createThresholdFilter(String level) {
36+
public static ConfigurationNode newAppenderRef(String ref) {
37+
return newNodeBuilder()
38+
.setPluginName("AppenderRef")
39+
.addAttribute("ref", ref)
40+
.get();
41+
}
42+
43+
public static ConfigurationNode newThresholdFilter(String level) {
3744
return newNodeBuilder()
3845
.setPluginName("ThresholdFilter")
3946
.addAttribute("level", level)
40-
.build();
47+
.get();
4148
}
4249

43-
public static ConfigurationNode createCompositeFilter(Iterable<? extends ConfigurationNode> filters) {
50+
public static ConfigurationNode newCompositeFilter(Iterable<? extends ConfigurationNode> filters) {
4451
ConfigurationNodeBuilder builder = newNodeBuilder().setPluginName("Filters");
4552
filters.forEach(builder::addChild);
46-
return builder.build();
53+
return builder.get();
4754
}
4855

4956
private ComponentUtils() {}
@@ -73,22 +80,23 @@ public ConfigurationNodeBuilder addAttribute(String key, boolean value) {
7380
return this;
7481
}
7582

83+
public ConfigurationNodeBuilder addAttribute(String key, int value) {
84+
attributes.put(key, String.valueOf(value));
85+
return this;
86+
}
87+
7688
public ConfigurationNodeBuilder addChild(ConfigurationNode child) {
7789
children.add(child);
7890
return this;
7991
}
8092

81-
public ConfigurationNode build() {
93+
@Override
94+
public ConfigurationNode get() {
8295
if (pluginName == null) {
8396
throw new ConfigurationConverterException("No plugin name specified");
8497
}
8598
return new ConfigurationNodeImpl(pluginName, attributes, children);
8699
}
87-
88-
@Override
89-
public ConfigurationNode get() {
90-
return build();
91-
}
92100
}
93101

94102
private static final class ConfigurationNodeImpl implements ConfigurationNode {
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to you under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.logging.converter.config.internal;
18+
19+
import java.util.Map;
20+
import java.util.Properties;
21+
import java.util.stream.Stream;
22+
import org.apache.logging.converter.config.ConfigurationConverterException;
23+
import org.apache.logging.converter.config.spi.v1.PropertiesSubset;
24+
import org.jspecify.annotations.Nullable;
25+
26+
public final class PropertiesUtils {
27+
28+
public static @Nullable String getAndRemove(Properties properties, String key) {
29+
return (String) properties.remove(key);
30+
}
31+
32+
public static String getLastComponent(String name) {
33+
int idx = name.lastIndexOf('.');
34+
return idx == -1 ? name : name.substring(idx + 1);
35+
}
36+
37+
public static Properties extractSubset(Properties properties, String prefix) {
38+
Properties subset = org.apache.logging.log4j.util.PropertiesUtil.extractSubset(properties, prefix);
39+
String value = getAndRemove(properties, prefix);
40+
if (value != null) {
41+
subset.setProperty("", value);
42+
}
43+
return subset;
44+
}
45+
46+
public static @Nullable String extractProperty(PropertiesSubset subset, String key) {
47+
return (String) subset.getProperties().remove(key);
48+
}
49+
50+
public static PropertiesSubset extractSubset(PropertiesSubset parentSubset, String childPrefix) {
51+
Properties parentProperties = parentSubset.getProperties();
52+
Properties properties =
53+
org.apache.logging.log4j.util.PropertiesUtil.extractSubset(parentProperties, childPrefix);
54+
String value = getAndRemove(parentProperties, childPrefix);
55+
if (value != null) {
56+
properties.setProperty("", value);
57+
}
58+
return PropertiesSubset.of(addPrefixes(parentSubset.getPrefix(), childPrefix), properties);
59+
}
60+
61+
public static Map<String, Properties> partitionOnCommonPrefixes(Properties properties) {
62+
return org.apache.logging.log4j.util.PropertiesUtil.partitionOnCommonPrefixes(properties, true);
63+
}
64+
65+
public static Stream<PropertiesSubset> partitionOnCommonPrefixes(PropertiesSubset parentSubset) {
66+
String parentPrefix = parentSubset.getPrefix();
67+
String effectivePrefix = parentPrefix.isEmpty() ? parentPrefix : parentPrefix + ".";
68+
return org.apache.logging.log4j.util.PropertiesUtil.partitionOnCommonPrefixes(
69+
parentSubset.getProperties(), true)
70+
.entrySet()
71+
.stream()
72+
.map(entry -> PropertiesSubset.of(effectivePrefix + entry.getKey(), entry.getValue()));
73+
}
74+
75+
private static String addPrefixes(String left, String right) {
76+
return left.isEmpty() ? right : right.isEmpty() ? left : left + "." + right;
77+
}
78+
79+
public static void throwIfNotEmpty(PropertiesSubset subset) {
80+
Properties properties = subset.getProperties();
81+
if (!properties.isEmpty()) {
82+
String prefix = subset.getPrefix();
83+
if (properties.size() == 1) {
84+
throw new ConfigurationConverterException("Unknown configuration property '"
85+
+ addPrefixes(
86+
prefix,
87+
properties.stringPropertyNames().iterator().next()) + "'.");
88+
}
89+
StringBuilder messageBuilder = new StringBuilder("Unknown configuration properties:");
90+
properties.stringPropertyNames().stream()
91+
.map(k -> addPrefixes(prefix, k))
92+
.forEach(k -> messageBuilder.append("\n\t").append(k));
93+
throw new ConfigurationConverterException(messageBuilder.toString());
94+
}
95+
}
96+
97+
private PropertiesUtils() {}
98+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to you under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.logging.converter.config.internal;
18+
19+
import java.util.regex.Pattern;
20+
import org.apache.logging.converter.config.ConfigurationConverterException;
21+
import org.apache.logging.log4j.util.Strings;
22+
23+
public final class StringUtils {
24+
25+
private static final Pattern SUBSTITUTION_PATTERN = Pattern.compile("\\$\\{([^}]+)\\}");
26+
27+
public static final String FALSE = "false";
28+
public static final String TRUE = "true";
29+
30+
public static String capitalize(String value) {
31+
if (Strings.isEmpty(value) || Character.isUpperCase(value.charAt(0))) {
32+
return value;
33+
}
34+
final char[] chars = value.toCharArray();
35+
chars[0] = Character.toUpperCase(chars[0]);
36+
return new String(chars);
37+
}
38+
39+
public static String decapitalize(String value) {
40+
if (Strings.isEmpty(value) || Character.isLowerCase(value.charAt(0))) {
41+
return value;
42+
}
43+
final char[] chars = value.toCharArray();
44+
chars[0] = Character.toLowerCase(chars[0]);
45+
return new String(chars);
46+
}
47+
48+
public static boolean parseBoolean(String value) {
49+
return Boolean.parseBoolean(value.trim());
50+
}
51+
52+
public static int parseInteger(String value) {
53+
try {
54+
return Integer.parseInt(value.trim());
55+
} catch (NumberFormatException e) {
56+
throw new ConfigurationConverterException("Invalid integer value: " + value, e);
57+
}
58+
}
59+
60+
public static long parseLong(String value) {
61+
try {
62+
return Long.parseLong(value.trim());
63+
} catch (NumberFormatException e) {
64+
throw new ConfigurationConverterException("Invalid long value: " + value, e);
65+
}
66+
}
67+
68+
public static String convertPropertySubstitution(String value) {
69+
return SUBSTITUTION_PATTERN.matcher(value).replaceAll("${sys:$1}");
70+
}
71+
72+
private StringUtils() {}
73+
}

log4j-converter-config/src/main/java/org/apache/logging/converter/config/internal/XmlUtils.java

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import javax.xml.parsers.DocumentBuilder;
2525
import javax.xml.parsers.DocumentBuilderFactory;
2626
import javax.xml.parsers.ParserConfigurationException;
27+
import org.apache.logging.converter.config.ConfigurationConverterException;
2728
import org.w3c.dom.Element;
2829
import org.w3c.dom.Node;
2930
import org.w3c.dom.NodeList;
@@ -60,6 +61,41 @@ public static DocumentBuilder createDocumentBuilderV2() throws IOException {
6061
return newDocumentBuilder(factory);
6162
}
6263

64+
/**
65+
* Finds an XPath expression that helps to identify a node in an XML document
66+
*
67+
* @param node An XML node.
68+
* @return An XPath expression.
69+
*/
70+
public static String getXPathExpression(Element node) {
71+
String tagName = node.getTagName();
72+
// Position of the node among siblings
73+
int position = 1;
74+
Node sibling = node.getPreviousSibling();
75+
while (sibling != null) {
76+
if (sibling instanceof Element && tagName.equals(((Element) sibling).getTagName())) {
77+
position++;
78+
}
79+
sibling = sibling.getPreviousSibling();
80+
}
81+
Node parent = node.getParentNode();
82+
String parentExpression = parent instanceof Element ? getXPathExpression((Element) parent) : "";
83+
return parentExpression + "/" + tagName + "[" + position + "]";
84+
}
85+
86+
public static void throwUnknownElement(Element node) {
87+
throw new ConfigurationConverterException("Unknown configuration element '" + getXPathExpression(node) + "'.");
88+
}
89+
90+
public static String requireNonEmpty(Element node, String attributeName) {
91+
String value = node.getAttribute(attributeName);
92+
if (value.isEmpty()) {
93+
throw new ConfigurationConverterException("Missing required attribute '" + attributeName
94+
+ "' on configuration element '" + getXPathExpression(node) + "'.");
95+
}
96+
return value;
97+
}
98+
6399
private static void disableXIncludeAware(DocumentBuilderFactory factory) throws IOException {
64100
try {
65101
factory.setXIncludeAware(false);

0 commit comments

Comments
 (0)