Skip to content
Tony Trinh edited this page Nov 12, 2022 · 41 revisions

Configuration via XML

logback-android can be configured simply by creating assets/logback.xml, containing configuration XML. This file is read automatically upon loading the first logger from your code. Additional code configuration is not necessary.

XML Validation

Using the official logback-android XML schema definition file is recommended for XML validation, autocompletion, and hints in the configuration XML.

Add the xmlns, xmlns:xsi, and xsi:schemaLocation attributes to <configuration> as shown below:

<configuration
  xmlns="https://tony19.github.io/logback-android/xml"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="https://tony19.github.io/logback-android/xml https://cdn.jsdelivr.net/gh/tony19/logback-android/logback.xsd"
>
  ...
</configuration>

When viewing logback.xml from Android Studio (or other JetBrains IDEs), hover the xsi:schemaLocation's URI (which appears in red) to reveal the red light-bulb hint, and select Fetch remote resource.

When viewing logback.xml in VS Code, use RedHat's XML extension, which automatically fetches the remote XML schema definition.

XML Examples

Example 1: Basic configuration (single destination)

<configuration
  xmlns="https://tony19.github.io/logback-android/xml"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="https://tony19.github.io/logback-android/xml https://cdn.jsdelivr.net/gh/tony19/logback-android/logback.xsd"
>
  <!-- Create a file appender for a log in the application's data directory -->
  <appender name="file" class="ch.qos.logback.core.FileAppender">
    <file>/data/data/com.example/files/log/foo.log</file>
    <encoder>
      <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
    </encoder>
  </appender>

  <!-- Write INFO (and higher-level) messages to the log file -->
  <root level="INFO">
    <appender-ref ref="file" />
  </root>
</configuration>

Example 2: Advanced configuration (multiple destinations)

<configuration
  xmlns="https://tony19.github.io/logback-android/xml"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="https://tony19.github.io/logback-android/xml https://cdn.jsdelivr.net/gh/tony19/logback-android/logback.xsd"
>
  <property name="LOG_DIR" value="/data/data/com.example/files" />

  <!-- Create a logcat appender -->
  <appender name="logcat" class="ch.qos.logback.classic.android.LogcatAppender">
    <encoder>
      <pattern>%msg</pattern>
    </encoder>
  </appender>

  <!-- Create a file appender for TRACE-level messages -->
  <appender name="TraceLog" class="ch.qos.logback.core.FileAppender">
    <filter class="ch.qos.logback.classic.filter.LevelFilter">
      <level>TRACE</level>
      <onMatch>ACCEPT</onMatch>
      <onMismatch>DENY</onMismatch>
    </filter>

    <file>${LOG_DIR}/trace.log</file>

    <encoder>
      <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
    </encoder>
  </appender>

  <!-- Create a file appender for DEBUG-level messages -->
  <appender name="DebugLog" class="ch.qos.logback.core.FileAppender">
    <filter class="ch.qos.logback.classic.filter.LevelFilter">
      <level>DEBUG</level>
      <onMatch>ACCEPT</onMatch>
      <onMismatch>DENY</onMismatch>
    </filter>

    <file>${LOG_DIR}/debug.log</file>

    <encoder>
      <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
    </encoder>
  </appender>

  <!-- Write TRACE messages from class A to its own log -->
  <logger name="com.example.A" level="TRACE">
    <appender-ref ref="TraceLog" />
  </logger>

  <!-- Write DEBUG messages from class B to its own log -->
  <logger name="com.example.B" level="DEBUG">
    <appender-ref ref="DebugLog" />
  </logger>

  <!-- Write INFO (and higher-level) messages to logcat -->
  <root level="INFO">
    <appender-ref ref="logcat" />
  </root>
</configuration>

Special properties for XML config

Note these special properties are initialized when the first logger is instantiated (i.e., via LoggerFactory.getLogger()), but that must be done when the application context is available (e.g., in the onCreate method of your Application class or at any point thereafter). Otherwise, the special properties will resolve to empty strings.

  • DATA_DIR - The current user's internal files directory. Note this is not the application data directory despite the name. This variable will be renamed in a future release for clarity.
  • EXT_DIR - The external storage directory if mounted (e.g., if the SD card is inserted). If not mounted, this is null. Usage: ${EXT_DIR:-${DATA_DIR}}, which specifies the external storage directory if mounted. Otherwise, the internal files directory.
  • PACKAGE_NAME - The application package name (from AndroidManifest.xml)
  • VERSION_NAME - The application version name (from AndroidManifest.xml)
  • VERSION_CODE - The application version code (from AndroidManifest.xml)

Initialization search path

Even though assets/logback.xml is the first configuration loaded, this file could include other configuration files from within your JAR or from the host filesystem. This is achieved with the <includes> tag, containing a list of optional <include> tags (i.e., no error is thrown for nonexistent resources/files). The first <include> tag that points to an existent resource/file causes the remainder of the list to be ignored.

Example:

<configuration
  xmlns="https://tony19.github.io/logback-android/xml"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="https://tony19.github.io/logback-android/xml https://cdn.jsdelivr.net/gh/tony19/logback-android/logback.xsd"
>
  <includes>
    <include file="/sdcard/foo.xml"/>
    <include resource="assets/config/test.xml"/>
  </includes>
</configuration>

Prior to v1.0.8-1, the initialization search path was hard-coded, and that can be recreated with this configuration:

<configuration
  xmlns="https://tony19.github.io/logback-android/xml"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="https://tony19.github.io/logback-android/xml https://cdn.jsdelivr.net/gh/tony19/logback-android/logback.xsd"
>
  <includes>
    <include file="/sdcard/logback/logback-test.xml"/>
    <include file="/sdcard/logback/logback.xml"/>
    <include resource="assets/logback-test.xml"/>
    <include resource="assets/logback.xml"/>
  </includes>
</configuration>

Configuration in Code

If you prefer code-based configuration instead of the XML method above, you can use the logback classes directly to initialize logback-android as shown in the following examples. Note the direct usage of logback classes removes the advantage of the facade provided by SLF4J.

Code Examples

Example: Uses BasicLogcatConfigurator to redirect to logcat

package com.example;

import org.slf4j.LoggerFactory;

import ch.qos.logback.classic.android.BasicLogcatConfigurator;

import android.app.Activity;
import android.os.Bundle;

public class MainActivity extends Activity {
  static {
    BasicLogcatConfigurator.configureDefaultContext();
  }

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    org.slf4j.Logger log = LoggerFactory.getLogger(MainActivity.class);
    for (int i = 0; i < 10; i++) {
      log.info("hello world");
    }
  }

}

Example: Configures appenders directly

package com.example;

import org.slf4j.LoggerFactory;

import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.android.LogcatAppender;
import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.FileAppender;

import android.os.Bundle;
import android.app.Activity;

public class MainActivity extends Activity {

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    configureLogbackDirectly();

    org.slf4j.Logger log = LoggerFactory.getLogger(MainActivity.class);
    for (int i = 0; i < 10; i++) {
      log.info("hello world");
    }
  }

  private void configureLogbackDirectly() {
    // reset the default context (which may already have been initialized)
    // since we want to reconfigure it
    LoggerContext lc = (LoggerContext)LoggerFactory.getILoggerFactory();
    lc.stop();

    // setup FileAppender
    PatternLayoutEncoder encoder1 = new PatternLayoutEncoder();
    encoder1.setContext(lc);
    encoder1.setPattern("%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n");
    encoder1.start();

    FileAppender<ILoggingEvent> fileAppender = new FileAppender<ILoggingEvent>();
    fileAppender.setContext(lc);
    fileAppender.setFile(this.getFileStreamPath("app.log").getAbsolutePath());
    fileAppender.setEncoder(encoder1);
    fileAppender.start();

    // setup LogcatAppender
    PatternLayoutEncoder encoder2 = new PatternLayoutEncoder();
    encoder2.setContext(lc);
    encoder2.setPattern("[%thread] %msg%n");
    encoder2.start();

    LogcatAppender logcatAppender = new LogcatAppender();
    logcatAppender.setContext(lc);
    logcatAppender.setEncoder(encoder2);
    logcatAppender.start();

    // add the newly created appenders to the root logger;
    // qualify Logger to disambiguate from org.slf4j.Logger
    ch.qos.logback.classic.Logger root = (ch.qos.logback.classic.Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
    root.addAppender(fileAppender);
    root.addAppender(logcatAppender);
  }
}

Example: Configures by XML file

// snip…

private void configureLogbackByFilePath() {
  // reset the default context (which may already have been initialized)
  // since we want to reconfigure it
  LoggerContext lc = (LoggerContext)LoggerFactory.getILoggerFactory();
  lc.stop();

  JoranConfigurator config = new JoranConfigurator();
  config.setContext(lc);

  try {
    config.doConfigure("/path/to/config.xml");
  } catch (JoranException e) {
    e.printStackTrace();
  }
}

Example: Configures by in-memory XML string

// snip…

static final String LOGBACK_XML =
    "<configuration>" +
      "<appender name='FILE' class='ch.qos.logback.core.FileAppender'>" +
         "<file>foo.log</file>" +
         "<append>false</append>" +
         "<encoder>" +
           "<pattern>%-4r [%t] %-5p %c{35} - %m%n</pattern>" +
         "</encoder>" +
       "</appender>" +
       "<root level='DEBUG'>" +
         "<appender-ref ref='FILE' />" +
       "</root>" +
    "</configuration>"
    ;

private void configureLogbackByString() {
  // reset the default context (which may already have been initialized)
  // since we want to reconfigure it
  LoggerContext lc = (LoggerContext)LoggerFactory.getILoggerFactory();
  lc.stop();

  JoranConfigurator config = new JoranConfigurator();
  config.setContext(lc);

  InputStream stream = new ByteArrayInputStream(LOGBACK_XML.getBytes());
  try {
    config.doConfigure(stream);
  } catch (JoranException e) {
    e.printStackTrace();
  }
}

ProGuard

When optimizing your application with ProGuard, include the following rules to prevent logback-android and SLF4J calls from being removed (unless that's desired):

# Issue #229
-keepclassmembers class ch.qos.logback.classic.pattern.* { <init>(); }

# The following rules should only be used if you plan to keep
# the logging calls in your released app.
-keepclassmembers ch.qos.logback.** { *; }
-keepclassmembers org.slf4j.impl.** { *; }
-keepattributes *Annotation*

If you don't use the mailing features of logback (i.e., the SMTPAppender), you might encounter an error while exporting your app with ProGuard. To resolve this, add the following rule:

-dontwarn ch.qos.logback.core.net.*

Resources