Skip to content

Commit b5a633a

Browse files
committed
Initial implementation based on Loune's example
1 parent c80326e commit b5a633a

File tree

8 files changed

+541
-0
lines changed

8 files changed

+541
-0
lines changed
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package net.loune.log4j2android;
2+
3+
import net.loune.log4j2android.AutoCloseConfigDescriptor.ConfigDescriptor;
4+
import org.apache.logging.log4j.core.LoggerContext;
5+
import org.apache.logging.log4j.core.config.Configuration;
6+
import org.apache.logging.log4j.core.config.ConfigurationFactory;
7+
import org.apache.logging.log4j.core.config.ConfigurationSource;
8+
import org.apache.logging.log4j.core.impl.ContextAnchor;
9+
import org.apache.logging.log4j.core.selector.ContextSelector;
10+
11+
import java.net.URI;
12+
import java.util.List;
13+
14+
/**
15+
* Created by loune on 16/05/2015.
16+
*/
17+
public class AndroidContextSelector implements ContextSelector {
18+
19+
private static LoggerContext CONTEXT = new LoggerContext("Default");
20+
21+
//private static boolean isStarted = false;
22+
23+
private void start(LoggerContext context) {
24+
try (AutoCloseConfigDescriptor descriptor = AndroidLog4jHelper.getConfig()) {
25+
if (descriptor.config.isPresent()) {
26+
ConfigDescriptor config = descriptor.config.get();
27+
ConfigurationSource source = new ConfigurationSource(config.inputStream, config.url);
28+
Configuration configuration = ConfigurationFactory.getInstance().getConfiguration(context, source);
29+
context.start(configuration);
30+
} else {
31+
context.start();
32+
}
33+
}
34+
}
35+
36+
@Override
37+
public LoggerContext getContext(final String fqcn, final ClassLoader loader, final boolean currentContext) {
38+
if (!CONTEXT.isStarted()) {
39+
start(CONTEXT);
40+
}
41+
final LoggerContext ctx = ContextAnchor.THREAD_CONTEXT.get();
42+
return ctx != null ? ctx : CONTEXT;
43+
}
44+
45+
46+
@Override
47+
public LoggerContext getContext(final String fqcn, final ClassLoader loader, final boolean currentContext,
48+
final URI configLocation) {
49+
if (!CONTEXT.isStarted()) {
50+
start(CONTEXT);
51+
}
52+
final LoggerContext ctx = ContextAnchor.THREAD_CONTEXT.get();
53+
return ctx != null ? ctx : CONTEXT;
54+
}
55+
56+
public LoggerContext locateContext(final String name, final String configLocation) {
57+
if (!CONTEXT.isStarted()) {
58+
start(CONTEXT);
59+
}
60+
return CONTEXT;
61+
}
62+
63+
@Override
64+
public void removeContext(final LoggerContext context) {
65+
}
66+
67+
@Override
68+
public List<LoggerContext> getLoggerContexts() {
69+
if (!CONTEXT.isStarted()) {
70+
start(CONTEXT);
71+
}
72+
73+
return List.of(CONTEXT);
74+
}
75+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package net.loune.log4j2android;
2+
3+
import android.app.Instrumentation;
4+
import android.content.Context;
5+
6+
import androidx.annotation.NonNull;
7+
import java.io.File;
8+
9+
import static net.loune.log4j2android.PluginProcessorHelper.injectPlugins;
10+
11+
/**
12+
* Created by loune on 9/04/2016.
13+
*/
14+
public class AndroidLog4jHelper {
15+
public static final String FILENAME = "log4j2.properties";
16+
17+
private static ContextProxy savedContext;
18+
19+
static {
20+
// Temporary init with a NO-OP configuration.
21+
// If the user wants, they will call initialize later; otherwise we will log nothing
22+
savedContext = new ContextProxy();
23+
24+
System.setProperty("Log4jContextSelector", "net.loune.log4j2android.AndroidContextSelector");
25+
System.setProperty("log4j2.disable.jmx", "true");
26+
27+
injectPlugins("net.loune.log4j2android", AndroidLookup.class, LogcatAppender.class);
28+
}
29+
30+
public static void initialize(Context context) {
31+
initialize(new ContextProxy(context));
32+
}
33+
34+
public static void initialize(Instrumentation instrumentation) {
35+
Context appContext = instrumentation.getTargetContext();
36+
Context testContext = instrumentation.getContext();
37+
// Test context goes first so it overrides the app context
38+
initialize(new ContextProxy(testContext, appContext));
39+
}
40+
41+
private static void initialize(ContextProxy proxy) {
42+
savedContext = proxy;
43+
}
44+
45+
public static File getFilesDir() {
46+
return savedContext.getFilesDir();
47+
}
48+
49+
public static File getExternalFilesDir(String type) {
50+
return savedContext.getExternalFilesDir(type);
51+
}
52+
53+
@NonNull
54+
public static AutoCloseConfigDescriptor getConfig() {
55+
return savedContext.getConfig(FILENAME);
56+
}
57+
58+
59+
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package net.loune.log4j2android;
2+
3+
import org.apache.logging.log4j.core.LogEvent;
4+
import org.apache.logging.log4j.core.config.plugins.Plugin;
5+
import org.apache.logging.log4j.core.lookup.StrLookup;
6+
7+
import java.io.File;
8+
9+
/**
10+
* Created by loune on 16/05/2015.
11+
*/
12+
@Plugin(name = "android", category = "Lookup")
13+
public class AndroidLookup implements StrLookup {
14+
/**
15+
* Lookup the value for the key.
16+
* @param key the key to be looked up, may be null
17+
* @return The value for the key.
18+
*/
19+
public String lookup(String key) {
20+
File filesDir = AndroidLog4jHelper.getFilesDir();
21+
if ("filesdir".equals(key)) {
22+
return filesDir.getAbsolutePath();
23+
}
24+
25+
File externalFilesDir = AndroidLog4jHelper.getExternalFilesDir(null);
26+
if ("externalfilesdir".equals(key)) {
27+
if (externalFilesDir == null) {
28+
return null;
29+
}
30+
return externalFilesDir.getAbsolutePath();
31+
}
32+
33+
if ("logfilesdir".equals(key)) {
34+
File baseDir = externalFilesDir == null ? filesDir : externalFilesDir;
35+
return new File(baseDir, "logs").getAbsolutePath();
36+
}
37+
38+
return null;
39+
}
40+
41+
/**
42+
* Lookup the value for the key using the data in the LogEvent.
43+
* @param event The current LogEvent.
44+
* @param key the key to be looked up, may be null
45+
* @return The value associated with the key.
46+
*/
47+
public String lookup(LogEvent event, String key) {
48+
return lookup(key);
49+
}
50+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package net.loune.log4j2android;
2+
3+
import java.io.Closeable;
4+
import java.io.InputStream;
5+
import java.net.URL;
6+
import java.util.Optional;
7+
import lombok.AccessLevel;
8+
import lombok.AllArgsConstructor;
9+
import lombok.Data;
10+
import lombok.Getter;
11+
import lombok.RequiredArgsConstructor;
12+
import lombok.SneakyThrows;
13+
14+
import static lombok.AccessLevel.PRIVATE;
15+
16+
@RequiredArgsConstructor
17+
public class AutoCloseConfigDescriptor implements AutoCloseable {
18+
private static final AutoCloseConfigDescriptor EMPTY = new AutoCloseConfigDescriptor(Optional.empty());
19+
20+
@Getter
21+
@AllArgsConstructor(access = PRIVATE)
22+
public static class ConfigDescriptor {
23+
public final InputStream inputStream;
24+
public final URL url;
25+
}
26+
27+
/** @noinspection OptionalUsedAsFieldOrParameterType */
28+
public final Optional<ConfigDescriptor> config;
29+
30+
public static AutoCloseConfigDescriptor empty() {
31+
return EMPTY;
32+
}
33+
34+
public static AutoCloseConfigDescriptor config(InputStream inputStream, URL url) {
35+
return new AutoCloseConfigDescriptor(Optional.of(new ConfigDescriptor(inputStream, url)));
36+
}
37+
38+
@Override
39+
public void close() {
40+
config.map(ConfigDescriptor::getInputStream)
41+
.ifPresent(this::closeIt);
42+
}
43+
44+
@SneakyThrows
45+
private void closeIt(Closeable closeable) {
46+
closeable.close();
47+
}
48+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package net.loune.log4j2android;
2+
3+
import android.content.Context;
4+
import android.content.res.AssetManager;
5+
import java.io.File;
6+
import java.io.IOException;
7+
import java.net.URL;
8+
9+
import static java.util.Arrays.asList;
10+
11+
class ContextProxy {
12+
private final Context[] contexts;
13+
14+
public ContextProxy(Context... contexts) {
15+
this.contexts = contexts;
16+
}
17+
18+
public AutoCloseConfigDescriptor getConfig(String filename) {
19+
for (Context context : contexts) {
20+
AutoCloseConfigDescriptor result = getConfig(context, filename);
21+
if (result.config.isPresent()) {
22+
return result;
23+
}
24+
}
25+
26+
return AutoCloseConfigDescriptor.empty();
27+
}
28+
29+
public AutoCloseConfigDescriptor getConfig(Context context, String filename) {
30+
try {
31+
AssetManager assets = context.getAssets();
32+
String[] list = assets.list("");
33+
if (list == null || !asList(list).contains(filename)) {
34+
return AutoCloseConfigDescriptor.empty();
35+
} else {
36+
return AutoCloseConfigDescriptor.config(assets.open(filename), new URL("file:///" + filename));
37+
}
38+
39+
} catch (IOException e) {
40+
throw new IllegalStateException("Error reading the configuration file");
41+
}
42+
}
43+
44+
public File getFilesDir() {
45+
return contexts[0].getFilesDir();
46+
}
47+
48+
public File getExternalFilesDir(String type) {
49+
return contexts[0].getExternalFilesDir(type);
50+
}
51+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package net.loune.log4j2android;
2+
3+
import lombok.RequiredArgsConstructor;
4+
import lombok.experimental.Delegate;
5+
import org.apache.logging.log4j.core.LogEvent;
6+
import org.apache.logging.log4j.core.impl.ThrowableProxy;
7+
8+
@RequiredArgsConstructor
9+
public class HideThrown implements LogEvent {
10+
@Delegate
11+
private final LogEvent original;
12+
13+
@Override
14+
public Throwable getThrown() {
15+
return null;
16+
}
17+
18+
@Override
19+
public ThrowableProxy getThrownProxy() {
20+
return null;
21+
}
22+
}

0 commit comments

Comments
 (0)