Skip to content

Commit

Permalink
Adds a Configuration Converter API (#142)
Browse files Browse the repository at this point in the history
Adds an API to perform automatic conversions between different logging configuration file formats and a basic implementation.
The implementation supports the following configuration file formats:

- Log4j Core 2 XML,
- Log4j Core 2 JSON,
- Log4j Core 2 YAML,
- Log4j Core 2 Properties (read-only),
- Log4j Core 3 Properties.

The API is extensible through `ServiceLoader` and allows integrators to provide support for additional configuration file formats.

Read-only support for the Log4j 1 Properties and Log4j 1 XML configuration formats will be provided in a separate PR.

**Note**: Currently the API is only accessible from Java code.
Its main purpose is to provide a "Migrate Log4j 1.x to Log4j Core 2.x", a "Migrate Logback to Log4j Core 2.x" and a "Migrate JUL to Log4j Core 2.x" [OpenRewrite recipe](https://docs.openrewrite.org/recipes/java/logging/log4j).
A `picocli`-based `log4j-transform-cli` tool to access all the goodies in this repository, will be provided later.

Closes apache/logging-log4j2#2080
  • Loading branch information
ppkarwasz authored Nov 27, 2024
1 parent 27ef8a8 commit f4392b1
Show file tree
Hide file tree
Showing 45 changed files with 3,234 additions and 30 deletions.
18 changes: 0 additions & 18 deletions log4j-codegen/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -43,24 +43,6 @@

<dependencies>

<dependency>
<groupId>org.osgi</groupId>
<artifactId>org.osgi.annotation.bundle</artifactId>
<scope>provided</scope>
</dependency>

<dependency>
<groupId>org.osgi</groupId>
<artifactId>org.osgi.annotation.versioning</artifactId>
<scope>provided</scope>
</dependency>

<dependency>
<groupId>com.github.spotbugs</groupId>
<artifactId>spotbugs-annotations</artifactId>
<scope>provided</scope>
</dependency>

<!-- Compile dependencies: the artifact is shaded, so limit these. -->
<dependency>
<groupId>info.picocli</groupId>
Expand Down
113 changes: 113 additions & 0 deletions log4j-converter-config/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
<?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-transform-parent</artifactId>
<version>${revision}</version>
<relativePath>../log4j-transform-parent</relativePath>
</parent>

<artifactId>log4j-converter-config</artifactId>
<name>Apache Log4j Configuration Converter</name>
<description>Converts various logging configuration formats to the Log4j Core 2.x format.</description>

<properties>
<!-- This artifact is an API: we need to generate its Javadoc -->
<maven.javadoc.skip>false</maven.javadoc.skip>

<!-- Remove after first release -->
<bnd.baseline.fail.on.missing>false</bnd.baseline.fail.on.missing>

<!-- Dependencies -->
<jackson.version>2.18.1</jackson.version>
<txw2.version>4.0.5</txw2.version>
</properties>

<dependencyManagement>
<dependencies>

<dependency>
<groupId>com.fasterxml.jackson</groupId>
<artifactId>jackson-bom</artifactId>
<version>${jackson.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>

</dependencies>
</dependencyManagement>

<dependencies>

<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
</dependency>

<dependency>
<groupId>org.jspecify</groupId>
<artifactId>jspecify</artifactId>
</dependency>

<!-- Used in the JSON configuration format -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>

<!-- Used in the v3 Properties configuration format -->
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-properties</artifactId>
</dependency>

<!-- Used in the YAML configuration format -->
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-yaml</artifactId>
</dependency>

<!-- Used in the XML configuration format -->
<dependency>
<groupId>org.glassfish.jaxb</groupId>
<artifactId>txw2</artifactId>
<version>${txw2.version}</version>
</dependency>

<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<scope>test</scope>
</dependency>

</dependencies>
</project>
Original file line number Diff line number Diff line change
@@ -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.converter.config;

import java.io.InputStream;
import java.io.OutputStream;
import java.util.Set;
import org.apache.logging.converter.config.internal.DefaultConfigurationConverter;

/**
* Service class to convert between different logging configuration formats.
*/
public interface ConfigurationConverter {

/**
* A default implementation of {@link ConfigurationConverter} that uses {@link java.util.ServiceLoader} to load additional formats.
* @see org.apache.logging.converter.config.spi.ConfigurationMapper
*/
static ConfigurationConverter getInstance() {
return DefaultConfigurationConverter.INSTANCE;
}

/**
* Converts a logging configuration file from one format to another.
*
* @param inputStream The input configuration file, never {@code null}.
* @param inputFormat The input format. Must be one of the formats returned by {@link #getSupportedInputFormats()}.
* @param outputStream The output configuration file, never {@code null}.
* @param outputFormat The output format. Must be one of the formats returned by {@link #getSupportedOutputFormats()}.
* @throws ConfigurationConverterException If any kind of error occurs during the conversion process.
*/
void convert(InputStream inputStream, String inputFormat, OutputStream outputStream, String outputFormat)
throws ConfigurationConverterException;

/**
* Returns the list of supported input formats.
*/
Set<String> getSupportedInputFormats();

/**
* Returns the list of supported output formats.
*/
Set<String> getSupportedOutputFormats();
}
Original file line number Diff line number Diff line change
@@ -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.converter.config;

/**
* Exception thrown by {@link ConfigurationConverter}, when a problem occurs during the conversion.
*/
public class ConfigurationConverterException extends RuntimeException {

private static final long serialVersionUID = 1L;

public ConfigurationConverterException(final String message) {
super(message);
}

public ConfigurationConverterException(final String message, final Throwable cause) {
super(message, cause);
}
}
Original file line number Diff line number Diff line change
@@ -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.converter.config.internal;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.function.Supplier;
import org.apache.logging.converter.config.ConfigurationConverterException;
import org.apache.logging.converter.config.spi.ConfigurationNode;
import org.jspecify.annotations.Nullable;

public final class ComponentUtils {

public static ConfigurationNodeBuilder newNodeBuilder() {
return new ConfigurationNodeBuilder();
}

public static ConfigurationNode createThresholdFilter(String level) {
return newNodeBuilder()
.setPluginName("ThresholdFilter")
.addAttribute("level", level)
.build();
}

public static ConfigurationNode createCompositeFilter(Iterable<? extends ConfigurationNode> filters) {
ConfigurationNodeBuilder builder = newNodeBuilder().setPluginName("Filters");
filters.forEach(builder::addChild);
return builder.build();
}

private ComponentUtils() {}

public static class ConfigurationNodeBuilder implements Supplier<ConfigurationNode> {

private @Nullable String pluginName;
private final Map<String, String> attributes = new TreeMap<>();
private final List<ConfigurationNode> children = new ArrayList<>();

protected ConfigurationNodeBuilder() {}

public ConfigurationNodeBuilder setPluginName(String pluginName) {
this.pluginName = pluginName;
return this;
}

public ConfigurationNodeBuilder addAttribute(String key, @Nullable String value) {
if (value != null) {
attributes.put(key, value);
}
return this;
}

public ConfigurationNodeBuilder addAttribute(String key, boolean value) {
attributes.put(key, String.valueOf(value));
return this;
}

public ConfigurationNodeBuilder addChild(ConfigurationNode child) {
children.add(child);
return this;
}

public ConfigurationNode build() {
if (pluginName == null) {
throw new ConfigurationConverterException("No plugin name specified");
}
return new ConfigurationNodeImpl(pluginName, attributes, children);
}

@Override
public ConfigurationNode get() {
return build();
}
}

private static final class ConfigurationNodeImpl implements ConfigurationNode {

private final String pluginName;
private final Map<String, String> attributes;
private final List<ConfigurationNode> children;

private ConfigurationNodeImpl(
final String pluginName,
final Map<String, String> attributes,
final Collection<ConfigurationNode> children) {
this.pluginName = pluginName;
this.attributes = Collections.unmodifiableMap(new TreeMap<>(attributes));
this.children = Collections.unmodifiableList(new ArrayList<>(children));
}

@Override
public String getPluginName() {
return pluginName;
}

@Override
public Map<String, String> getAttributes() {
return attributes;
}

@Override
public List<? extends ConfigurationNode> getChildren() {
return children;
}

private static void formatTo(ConfigurationNode node, StringBuilder builder, int indent) {
String indentation = getIndentation(indent);
builder.append(indentation).append("<").append(node.getPluginName());
for (final Map.Entry<String, String> entry : node.getAttributes().entrySet()) {
builder.append(" ")
.append(entry.getKey())
.append("=\"")
.append(entry.getValue())
.append("\"");
}
builder.append(">\n");
for (ConfigurationNode child : node.getChildren()) {
formatTo(child, builder, indent + 1);
builder.append('\n');
}
builder.append(indentation)
.append("</")
.append(node.getPluginName())
.append(">");
}

private static String getIndentation(int indent) {
return String.join("", Collections.nCopies(indent, " "));
}

@Override
public String toString() {
StringBuilder builder = new StringBuilder();
formatTo(this, builder, 0);
return builder.toString();
}
}
}
Loading

0 comments on commit f4392b1

Please sign in to comment.