我正在尝试在 android 中实现 log4j,在 gradle 中使用 v2.24.1 依赖项:
implementation 'org.apache.logging.log4j:log4j-api:2.24.1'
implementation 'org.apache.logging.log4j:log4j-core:2.24.1'
我在 src/main/assets 目录中有一个 log4j2.properties 配置:
# Root logger configuration
status = error
name = PropertiesConfig
# Appenders
appender.console.type = Console
appender.console.name = Console
appender.console.target = SYSTEM_OUT
appender.console.layout.type = PatternLayout
appender.console.layout.pattern = [%d{yyyy-MM-dd HH:mm:ss.SSS}] %-5p- %m%n
# Root logger level and appenders
rootLogger.level = INFO
rootLogger.appenderRefs = stdout
rootLogger.appenderRef.stdout.ref = Console
logger.com.example.log4japi.MainActivity.name = com.example.log4japi.MainActivity
logger.com.example.log4japi.MainActivity.level = DEBUG
logger.com.example.log4japi.MainActivity.additivity = false
logger.com.example.log4japi.MainActivity.appenderRefs = stdout
logger.com.example.log4japi.MainActivity.appenderRef.stdout.ref = Console
在我的主要活动中,我尝试使用上面定义的 com.example.log4japi.MainActivity 的记录器。该活动包含一个用于显示当前日志级别的文本视图、一个用于打开对话框和更改日志级别的按钮,以及用于打印每个级别的日志消息的其他按钮:
package com.example.log4japi;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.Button;
import android.widget.TextView;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.core.config.Configurator;
import java.util.Arrays;
public class MainActivity extends AppCompatActivity {
private int logLevelIdx;
Logger log = LogManager.getLogger(MainActivity.class);
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//TextView displaying Log Level
TextView logLevelTxt = findViewById(R.id.logLevelTxt);
logLevelTxt.setText(log.getLevel().name());
//Change log level
Button setLogLevelBtn = findViewById(R.id.setLogLevelBtn);
setLogLevelBtn.setOnClickListener(v -> {
AlertDialog.Builder alertDialog = new AlertDialog.Builder(this);
alertDialog.setTitle("Set log level");
String[] logLevels = new String[]{"OFF", "FATAL", "ERROR", "WARN", "INFO", "DEBUG", "TRACE", "ALL"};
logLevelIdx = Arrays.asList(logLevels).indexOf(log.getLevel().name());
alertDialog.setSingleChoiceItems(logLevels, logLevelIdx, (dialog, which) -> logLevelIdx = which);
alertDialog.setPositiveButton("Select", (dialog, which) -> {
Configurator.setLevel(log.getName(), Level.valueOf(Arrays.asList(logLevels).get(logLevelIdx)));
logLevelTxt.setText(log.getLevel().name());
dialog.dismiss();
});
alertDialog.setNegativeButton("Cancel", (dialog, which) -> dialog.dismiss());
alertDialog.create();
alertDialog.show();
});
//Log messages
Button fatalLogBtn = findViewById(R.id.fatalLogBtn);
fatalLogBtn.setOnClickListener(v -> log.fatal("Current log level is: " + log.getLevel().name()));
Button errorLogBtn = findViewById(R.id.errorLogBtn);
errorLogBtn.setOnClickListener(v -> log.error("Current log level is: " + log.getLevel().name()));
Button warnLogBtn = findViewById(R.id.warnLogBtn);
warnLogBtn.setOnClickListener(v -> log.warn("Current log level is: " + log.getLevel().name()));
Button infoLogBtn = findViewById(R.id.infoLogBtn);
infoLogBtn.setOnClickListener(v -> log.info("Current log level is: " + log.getLevel().name()));
Button debugLogBtn = findViewById(R.id.debugLogBtn);
debugLogBtn.setOnClickListener(v -> log.debug("Current log level is: " + log.getLevel().name()));
Button traceLogBtn = findViewById(R.id.traceLogBtn);
traceLogBtn.setOnClickListener(v -> log.trace("Current log level is: " + log.getLevel().name()));
}
}
由于某种原因,配置未被读取或初始化。这会导致我尝试使用的任何记录器的默认日志级别为 ERROR。我也无法使用
Configurator.setLevel
更改这些记录器的级别(除非我使用根记录器)。
我尝试从属性文件中手动导入配置,如下所示:
// Manually read in log4j2.properties
LoggerContext context = (LoggerContext) LogManager.getContext(false);
try {
InputStream inputStream = getAssets().open("log4j2.properties");
ConfigurationSource source = new ConfigurationSource(inputStream);
context.start(ConfigurationFactory.getInstance().getConfiguration(context, source));
} catch (IOException e) {
e.printStackTrace();
}
但这会产生以下堆栈跟踪:
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.log4japi, PID: 10971
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.log4japi/com.example.log4japi.MainActivity}: java.lang.UnsupportedOperationException: This parser does not support specification "Unknown" version "0.0"
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3449)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3601)
at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:85)
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2066)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:223)
at android.app.ActivityThread.main(ActivityThread.java:7656)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)
Caused by: java.lang.UnsupportedOperationException: This parser does not support specification "Unknown" version "0.0"
at javax.xml.parsers.DocumentBuilderFactory.setXIncludeAware(DocumentBuilderFactory.java:475)
at org.apache.logging.log4j.core.config.xml.XmlConfiguration.enableXInclude(XmlConfiguration.java:226)
at org.apache.logging.log4j.core.config.xml.XmlConfiguration.newDocumentBuilder(XmlConfiguration.java:186)
at org.apache.logging.log4j.core.config.xml.XmlConfiguration.<init>(XmlConfiguration.java:87)
at org.apache.logging.log4j.core.config.xml.XmlConfigurationFactory.getConfiguration(XmlConfigurationFactory.java:46)
at org.apache.logging.log4j.core.config.ConfigurationFactory$Factory.getConfiguration(ConfigurationFactory.java:565)
at com.example.log4japi.MainActivity.onCreate(MainActivity.java:40)
at android.app.Activity.performCreate(Activity.java:8000)
at android.app.Activity.performCreate(Activity.java:7984)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1309)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:3422)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3601)
at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:85)
at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135)
at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2066)
at android.os.Handler.dispatchMessage(Handler.java:106)
at android.os.Looper.loop(Looper.java:223)
at android.app.ActivityThread.main(ActivityThread.java:7656)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)
如何解决读取配置的问题?
由于某种原因,配置未被读取或初始化。
在您的项目中,配置文件位于
src/main/assets
中,它不是部署在类路径的根目录中,而是部署在 assets
子文件夹中。要将文件部署到类路径的根目录,请将其放入 src/main/resources
。
我尝试从属性文件中手动导入配置,如下所示:
// Manually read in log4j2.properties LoggerContext context = (LoggerContext) LogManager.getContext(false); try { InputStream inputStream = getAssets().open("log4j2.properties"); ConfigurationSource source = new ConfigurationSource(inputStream); context.start(ConfigurationFactory.getInstance().getConfiguration(context, source)); } catch (IOException e) { e.printStackTrace(); }
ConfigurationSource
类类似于上传文件:您需要指定上传文件的内容和类型。 Log4j Core 从 ConfigurationSource.getLocation()
的文件扩展名推断出配置文件的type。如果此方法返回
null
或未知的文件扩展名,则采用默认的 XML 配置格式。
由于 Android XML 解析器中缺乏 XInclude 功能,这会触发 LOG4J2-3531 错误。
要解决此问题,您需要在创建 ConfigurationSource
时指定配置文件的
name:
private ConfigurationSource getConfiguration(String asset) throws IOException {
// Create a Source to specify the name of the configuration file
Source source = new Source(URI.create("log4j2.properties"));
// Create a ConfigurationSource with the contents of the configuration file
byte[] buffer = new byte[1024];
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try (InputStream inputStream = getAssets().open(asset)) {
int len;
while ((len = inputStream.read(buffer)) != -1) {
baos.write(buffer, 0, len);
}
}
return new ConfigurationSource(source, baos.toByteArray(), 0L);
}