Skip to content

Commit 9358051

Browse files
author
Kevin Meurer
authored
[LOG4J2-2400] Initial implementation of RedisAppender. (#1)
* Initial implementation of Redis appender. * Add unit test support for Redis Appender. * Clean up implementation. * Add site documentation and clean up imports.
1 parent ef22be0 commit 9358051

File tree

11 files changed

+793
-1
lines changed

11 files changed

+793
-1
lines changed

log4j-redis/pom.xml

Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!-- ~ Licensed to the Apache Software Foundation (ASF) under one or more ~ contributor license agreements. See the NOTICE
3+
file distributed with ~ this work for additional information regarding copyright ownership. ~ The ASF licenses this file
4+
to You under the Apache License, Version 2.0 ~ (the "License"); you may not use this file except in compliance with ~ the
5+
License. You may obtain a copy of the License at ~ ~ http://www.apache.org/licenses/LICENSE-2.0 ~ ~ Unless required by applicable
6+
law or agreed to in writing, software ~ distributed under the License is distributed on an "AS IS" BASIS, ~ WITHOUT WARRANTIES
7+
OR CONDITIONS OF ANY KIND, either express or implied. ~ See the License for the specific language governing permissions and
8+
~ limitations under the License. -->
9+
10+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
11+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
12+
<parent>
13+
<groupId>org.apache.logging.log4j</groupId>
14+
<artifactId>log4j</artifactId>
15+
<version>3.0.0-SNAPSHOT</version>
16+
</parent>
17+
<modelVersion>4.0.0</modelVersion>
18+
19+
<artifactId>log4j-redis</artifactId>
20+
<name>Apache Log4j Redis</name>
21+
<description>
22+
Apache Log4j Redis.
23+
</description>
24+
<properties>
25+
<log4jParentDir>${basedir}/..</log4jParentDir>
26+
<docLabel>Log4j Redis Documentation</docLabel>
27+
<projectDir>/log4j-redis</projectDir>
28+
<module.name>org.apache.logging.log4j.redis</module.name>
29+
</properties>
30+
31+
<dependencies>
32+
<dependency>
33+
<groupId>org.apache.logging.log4j</groupId>
34+
<artifactId>log4j-core</artifactId>
35+
</dependency>
36+
<!-- Used for Redis appender -->
37+
<dependency>
38+
<groupId>redis.clients</groupId>
39+
<artifactId>jedis</artifactId>
40+
<version>2.9.0</version>
41+
</dependency>
42+
<!-- Test Dependencies -->
43+
<dependency>
44+
<groupId>junit</groupId>
45+
<artifactId>junit</artifactId>
46+
</dependency>
47+
<dependency>
48+
<groupId>org.apache.logging.log4j</groupId>
49+
<artifactId>log4j-api</artifactId>
50+
<type>test-jar</type>
51+
</dependency>
52+
<dependency>
53+
<groupId>org.apache.logging.log4j</groupId>
54+
<artifactId>log4j-core</artifactId>
55+
<type>test-jar</type>
56+
</dependency>
57+
<!-- Mocking framework for use with JUnit -->
58+
<dependency>
59+
<groupId>org.mockito</groupId>
60+
<artifactId>mockito-core</artifactId>
61+
<scope>test</scope>
62+
</dependency>
63+
<!-- Required for AsyncLoggers -->
64+
<dependency>
65+
<groupId>com.lmax</groupId>
66+
<artifactId>disruptor</artifactId>
67+
<scope>test</scope>
68+
</dependency>
69+
</dependencies>
70+
<build>
71+
<plugins>
72+
<plugin>
73+
<groupId>org.apache.felix</groupId>
74+
<artifactId>maven-bundle-plugin</artifactId>
75+
<configuration>
76+
<instructions>
77+
<Fragment-Host>org.apache.logging.log4j.core.appender.mom.redis</Fragment-Host>
78+
<Export-Package>*</Export-Package>
79+
</instructions>
80+
</configuration>
81+
</plugin>
82+
</plugins>
83+
</build>
84+
<reporting>
85+
<plugins>
86+
<plugin>
87+
<groupId>org.apache.maven.plugins</groupId>
88+
<artifactId>maven-changes-plugin</artifactId>
89+
<version>${changes.plugin.version}</version>
90+
<reportSets>
91+
<reportSet>
92+
<reports>
93+
<report>changes-report</report>
94+
</reports>
95+
</reportSet>
96+
</reportSets>
97+
<configuration>
98+
<issueLinkTemplate>%URL%/show_bug.cgi?id=%ISSUE%</issueLinkTemplate>
99+
<useJql>true</useJql>
100+
</configuration>
101+
</plugin>
102+
<plugin>
103+
<groupId>org.apache.maven.plugins</groupId>
104+
<artifactId>maven-checkstyle-plugin</artifactId>
105+
<version>${checkstyle.plugin.version}</version>
106+
<configuration>
107+
<!--<propertiesLocation>${vfs.parent.dir}/checkstyle.properties</propertiesLocation> -->
108+
<configLocation>${log4jParentDir}/checkstyle.xml</configLocation>
109+
<suppressionsLocation>${log4jParentDir}/checkstyle-suppressions.xml</suppressionsLocation>
110+
<enableRulesSummary>false</enableRulesSummary>
111+
<propertyExpansion>basedir=${basedir}</propertyExpansion>
112+
<propertyExpansion>licensedir=${log4jParentDir}/checkstyle-header.txt</propertyExpansion>
113+
</configuration>
114+
</plugin>
115+
<plugin>
116+
<groupId>org.apache.maven.plugins</groupId>
117+
<artifactId>maven-javadoc-plugin</artifactId>
118+
<version>${javadoc.plugin.version}</version>
119+
<configuration>
120+
<bottom><![CDATA[<p align="center">Copyright &#169; {inceptionYear}-{currentYear} {organizationName}. All Rights Reserved.<br />
121+
Apache Logging, Apache Log4j, Log4j, Apache, the Apache feather logo, the Apache Logging project logo,
122+
and the Apache Log4j logo are trademarks of The Apache Software Foundation.</p>]]></bottom>
123+
<!-- module link generation is completely broken in the javadoc plugin for a multi-module non-aggregating project -->
124+
<detectOfflineLinks>false</detectOfflineLinks>
125+
<linksource>true</linksource>
126+
</configuration>
127+
<reportSets>
128+
<reportSet>
129+
<id>non-aggregate</id>
130+
<reports>
131+
<report>javadoc</report>
132+
</reports>
133+
</reportSet>
134+
</reportSets>
135+
</plugin>
136+
<plugin>
137+
<groupId>com.github.spotbugs</groupId>
138+
<artifactId>spotbugs-maven-plugin</artifactId>
139+
<configuration>
140+
<fork>true</fork>
141+
<jvmArgs>-Duser.language=en</jvmArgs>
142+
<threshold>Normal</threshold>
143+
<effort>Default</effort>
144+
<excludeFilterFile>${log4jParentDir}/spotbugs-exclude-filter.xml</excludeFilterFile>
145+
</configuration>
146+
</plugin>
147+
<plugin>
148+
<groupId>org.apache.maven.plugins</groupId>
149+
<artifactId>maven-jxr-plugin</artifactId>
150+
<version>${jxr.plugin.version}</version>
151+
<reportSets>
152+
<reportSet>
153+
<id>non-aggregate</id>
154+
<reports>
155+
<report>jxr</report>
156+
</reports>
157+
</reportSet>
158+
<reportSet>
159+
<id>aggregate</id>
160+
<reports>
161+
<report>aggregate</report>
162+
</reports>
163+
</reportSet>
164+
</reportSets>
165+
</plugin>
166+
<plugin>
167+
<groupId>org.apache.maven.plugins</groupId>
168+
<artifactId>maven-pmd-plugin</artifactId>
169+
<version>${pmd.plugin.version}</version>
170+
<configuration>
171+
<targetJdk>${maven.compiler.target}</targetJdk>
172+
</configuration>
173+
</plugin>
174+
</plugins>
175+
</reporting>
176+
</project>
Lines changed: 202 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,202 @@
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+
18+
package org.apache.logging.log4j.redis.appender;
19+
20+
import org.apache.logging.log4j.core.Appender;
21+
import org.apache.logging.log4j.core.Filter;
22+
import org.apache.logging.log4j.core.Layout;
23+
import org.apache.logging.log4j.core.LogEvent;
24+
import org.apache.logging.log4j.core.appender.AbstractAppender;
25+
import org.apache.logging.log4j.core.config.Node;
26+
import org.apache.logging.log4j.core.config.plugins.Plugin;
27+
import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
28+
import org.apache.logging.log4j.core.config.plugins.PluginBuilderFactory;
29+
import org.apache.logging.log4j.core.config.plugins.validation.constraints.Required;
30+
import org.apache.logging.log4j.core.layout.AbstractStringLayout;
31+
32+
import java.io.Serializable;
33+
import java.nio.charset.Charset;
34+
import java.util.Objects;
35+
import java.util.concurrent.TimeUnit;
36+
37+
/**
38+
* Sends log events to a Redis Queue. All logs are appended Redis lists via the RPUSH command.
39+
*/
40+
@Plugin(name = "Redis", category = Node.CATEGORY, elementType = Appender.ELEMENT_TYPE, printObject = true)
41+
public final class RedisAppender extends AbstractAppender {
42+
43+
private RedisAppender(final String name, final Layout<? extends Serializable> layout, final Filter filter,
44+
final boolean ignoreExceptions, final RedisManager manager) {
45+
super(name, filter, layout, ignoreExceptions);
46+
this.manager = Objects.requireNonNull(manager, "Redis Manager");
47+
}
48+
49+
/**
50+
* Builds RedisAppender instances.
51+
* @param <B> The type to build
52+
*/
53+
public static class Builder<B extends Builder<B>> extends AbstractAppender.Builder<B>
54+
implements org.apache.logging.log4j.core.util.Builder<RedisAppender> {
55+
56+
@PluginAttribute("keys")
57+
private String[] keys;
58+
59+
@PluginAttribute(value = "host")
60+
@Required(message = "No Redis hostname provided")
61+
private String host;
62+
63+
@PluginAttribute(value = "port")
64+
private int port;
65+
66+
@PluginAttribute(value = "ssl")
67+
private boolean ssl = false;
68+
69+
@SuppressWarnings("resource")
70+
@Override
71+
public RedisAppender build() {
72+
return new RedisAppender(getName(), getLayout(), getFilter(), isIgnoreExceptions(), getRedisManager());
73+
}
74+
75+
public Charset getCharset() {
76+
if (getLayout() instanceof AbstractStringLayout) {
77+
return ((AbstractStringLayout) getLayout()).getCharset();
78+
} else {
79+
return Charset.defaultCharset();
80+
}
81+
}
82+
83+
String[] getKeys() {
84+
return keys;
85+
}
86+
87+
String getHost() {
88+
return host;
89+
}
90+
91+
boolean getSsl() {
92+
return ssl;
93+
}
94+
95+
int getPort() {
96+
return port;
97+
}
98+
99+
public B withKeys(final String key) {
100+
this.keys = new String[]{key};
101+
return asBuilder();
102+
}
103+
104+
public B withKeys(final String[] keys) {
105+
this.keys = keys;
106+
return asBuilder();
107+
}
108+
109+
public B withHost(final String host) {
110+
this.host = host;
111+
return asBuilder();
112+
}
113+
114+
public B withPort(final int port) {
115+
this.port = port;
116+
return asBuilder();
117+
}
118+
119+
public B withSsl(final boolean ssl) {
120+
this.ssl = ssl;
121+
return asBuilder();
122+
}
123+
124+
RedisManager getRedisManager() {
125+
return new RedisManager(
126+
getConfiguration().getLoggerContext(),
127+
getName(),
128+
getKeys(),
129+
getHost(),
130+
getPort(),
131+
getSsl(),
132+
getCharset()
133+
);
134+
}
135+
}
136+
137+
/**
138+
* Creates a builder for a RedisAppender.
139+
* @return a builder for a RedisAppender.
140+
*/
141+
@PluginBuilderFactory
142+
public static <B extends Builder<B>> B newBuilder() {
143+
return new Builder<B>().asBuilder();
144+
}
145+
146+
private final RedisManager manager;
147+
148+
@Override
149+
public void append(final LogEvent event) {
150+
if (event.getLoggerName() != null && event.getLoggerName().startsWith("org.apache.redis")) {
151+
LOGGER.warn("Recursive logging from [{}] for appender [{}].", event.getLoggerName(), getName());
152+
} else {
153+
try {
154+
tryAppend(event);
155+
} catch (final Exception e) {
156+
error("Unable to write to Redis in appender [" + getName() + "]", event, e);
157+
}
158+
}
159+
}
160+
161+
private void tryAppend(final LogEvent event) {
162+
final Layout<? extends Serializable> layout = getLayout();
163+
final byte[] header = layout.getHeader();
164+
final byte[] body = layout.toByteArray(event);
165+
166+
int len = (header != null ? header.length : 0) + body.length;
167+
byte[] data = new byte[len];
168+
if (header != null) {
169+
System.arraycopy(header, 0, data, 0, header.length);
170+
System.arraycopy(body, 0, data, header.length, body.length);
171+
} else {
172+
System.arraycopy(body, 0, data, 0, body.length);
173+
}
174+
manager.send(data);
175+
}
176+
177+
@Override
178+
public void start() {
179+
setStarting();
180+
manager.startup();
181+
setStarted();
182+
}
183+
184+
@Override
185+
public boolean stop(final long timeout, final TimeUnit timeUnit) {
186+
setStopping();
187+
boolean stopped = super.stop(timeout, timeUnit, false);
188+
stopped &= manager.stop(timeout, timeUnit);
189+
setStopped();
190+
return stopped;
191+
}
192+
193+
@Override
194+
public String toString() {
195+
return "RedisAppender{" +
196+
"name=" + getName() +
197+
", host=" + manager.getHost() +
198+
", port=" + manager.getPort() +
199+
", keys=" + manager.getKeysAsString() +
200+
'}';
201+
}
202+
}

0 commit comments

Comments
 (0)