Skip to content

Logging

The SAP Cloud Platform SDK for Android allows you to write logs to the console in a customizable format and upload these logs to the SAP Cloud Platform for later analysis.

The SDK uses Simple Logging Facade for Java (SLF4J) for its logging API and logback-android for the logging implementation. For more information about SLF4J, refer to the SLF4J documentation. For more information about logback-android, refer to the documentation on the logback-android site.

Notes

  • If you don't need to upload logs, you don't have to use the SDK's logging API. Make sure you don't call either of the Logging.initialize() methods. It will be up to you to configure the logging implementation.

  • If you want to use another SLF4J logging implementation instead of logback, such as Log4j or java.util.logging, you can do that too. In that case you will not be able to use the SDK's logging API. Make sure you don't call either of the Logging.initialize() methods, and make sure you don't add the logback dependencies to your gradle build.

  • You can use a non-SLF4J logger too, but you will not get any logging from the SDK if you do that.

  • If you want to implement your own log upload, see the documentation for Client Log Upload Service.

Logging Overview

Logger Hierarchies

Loggers are hierarchically grouped and separated by dots. For example, a logger named "com.sap.cloud.mobile" is a parent of a logger named "com.sap.cloud.mobile.foundation". This lets you apply a configuration to a collection of loggers:

  • Loggers that have a common group prefix can be configured together. This includes setting the log level.

  • By default, all loggers inherit their log level configuration from the root logger. Changing the log level at the root logger changes the log level for all loggers, while changing the log level of another logger changes the log levels of all loggers belonging to the same group.

Logging Recommendations and Guidelines

Log messages are often the only source of information for your application users about what went wrong. For this reason, you'll want to capture enough useful information to help your users troubleshoot errors.

This section provides some useful recommendations and guidelines.

Log Level Recommendations

Logging severity levels allow you to assign a level of importance to a log message. However, if you log too many events without properly distinguishing between the importance of the messages, you can end up generating too many logs.

Therefore, it's important that you understand when to use each log level. SAP recommends that you use the log levels as follows:

  • debug and info log levels to trace the current states of your application. Log these messages extensively. Use these logs when your code handles different conditions of an operation, or when the state of your app changes such as in the case of a lost connection.

  • warn log level for errors that allow your app to function without having an impact on the user experience.

  • error log level for errors that put your app’s stability at risk or indicate a malfunction, incorrect usage, or runtime error. Typical examples include catching an error or exception, detecting an illegal user input, or situations where your app is about to terminate unexpectedly.

Recommendation

SAP recommends that you provide your users with a mechanism in your application, such as a configuration setting, that lets them enable or disable different log levels.

Log Message Guidelines

As well as logging messages at the proper levels and in the correct locations, it's equally important that you log useful messages. A simple message such as "Error!" isn't helpful when you're analyzing what went wrong.

Use these guidelines when you're writing messages:

  • Clear – Keep your wording simple, avoid complex sentences, and put yourself in the shoes of someone who does not know your code. Explain what is going on and what the problem could be.

  • Brief – Try to keep each message to no more than one or two lines. Make sure it contains enough information to allow your users to analyze what happened.

  • Complete – Make sure your error message contains all information necessary to debug the error and understand what the application was doing. Don't use log messages such as "I am here!" or "Finished!" Instead, use log messages like "NumberGenerator: User entered illegal number. Number was 256. Showing error popup".

  • Root cause – When you catch an error or exception and log a message, make sure you also log the root cause, that is, the error you have caught. This lets you trace back to where the error originated. This is invaluable information when it comes to debugging.

Data Privacy and Security

Logging is an invaluable tool for error analysis and must be an essential part of your application’s source code. However, logging can pose a threat to data privacy. Make sure that you never log private data that must not leave the app or device.

Data privacy depends on your specific use case and on the data privacy regulations of the countries where you plan to ship your application.

You should also keep in mind that logging can be a security risk. Logging passwords or other user credentials may give attackers a chance to compromise your application and your users’ security. Make sure that every developer is aware of the risks and the potential consequences of logging data.

Using the SAP Logging API

Prerequisites

To enable logging in your mobile app, you require logback-android. To install logback-android, go to logback-android and follow the Quick Start instructions.

Adding Logback to Your Project

To include logback in your project, add the following dependency to your gradle build:

1
2
3
dependencies {
  implementation 'com.github.tony19:logback-android:1.1.1-11'
}

Getting a Named Logger

In classes where you want to add logging, get a named logger from the LoggerFactory and then log at the level you need to. For example:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
org.slf4j.Logger log = LoggerFactory.getLogger("com.my.package");
log.info("info");
log.warn("warn");
log.debug("debug");
log.error("error");
log.trace("trace");

log.info("info {}", "info");
log.warn("warn {}", "warn");
log.debug("debug {}", "debug");
log.error("error {}", "error");
log.trace("trace {}", "trace");
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
val log = LoggerFactory.getLogger("com.my.package")
log.info("info")
log.warn("warn")
log.debug("debug")
log.error("error")
log.trace("trace")

log.info("info {}", "info")
log.warn("warn {}", "warn")
log.debug("debug {}", "debug")
log.error("error {}", "error")
log.trace("trace {}", "trace")

The mobile service does not support trace logging. Any messages logged as "trace" will be converted to "path" when uploaded.

Initializing Logging

The best place to initialize logging is in the application's main activity. To set up logging, call one of the following initialize methods:

1
Logging.initialize(context);
1
Logging.initialize(context)

or

1
Logging.initialize(context, new Logging.ConfigurationBuilder().initialLevel(Level.DEBUG).logToConsole(true).build());
1
2
3
4
Logging.initialize(
    context,
    Logging.ConfigurationBuilder().initialLevel(Level.DEBUG).logToConsole(true).build()
)

The simple form of initialize sets the log level to ERROR. It uses four rolling log files with maximum file size of 1000 KB for each log file. This method provides no console output and no expiration. It is the recommended setting for production code.

You can also set one or more configuration option using a Logging.ConfigurationBuilder to build a parameter list. Any configuration option not set will use the default. See the Logging API documentation for more details.

Logs are written to a rolling log. Logs are written to a maximum size. When the limit is reached, the oldest log entries (the oldest log file) are deleted to make room for new log entries. The maximum size is logFileCount x logFileSize.

Setting Logging Levels

Logging level controls which log messages are written to the log. For example, when the log level is set to ERROR, DEBUG messages are not written to the log. See the logback documentation for more information.

Logging.initialize() sets the initial level for the whole application. You can also do this using the following code:

1
Logging.getRootLogger().setLevel(Level.ERROR);
1
Logging.getRootLogger().level = Level.ERROR

Uploading Logs

You can add code to upload logs to the mobile service cockpit for review. In your application, call:

1
Logging.uploadLog(client, settingsParameters);
1
Logging.uploadLog(client, settingsParameters)

where:

  • client is the OKHttpClient session already established with the mobile service, and
  • settingsParameters are the application's settings parameters.

To be notified when the upload is complete, or to get upload progress, implement Logging.UploadListener as follows:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class MyClass implements Logging.UploadListener {

    // Logging event listeners
    public void onError(Throwable throwable) {
        if (throwable instanceof HttpException) {
            if (((HttpException)throwable).code() == 403) {
                Toast.makeText(getApplicationContext(),"Client log upload is not enabled for the application", Toast.LENGTH_SHORT).show();
            } else {
                Toast.makeText(getApplicationContext(),throwable.getMessage(), Toast.LENGTH_SHORT).show();
            }
        } else if (throwable instanceof IOException) {
            Toast.makeText(getApplicationContext(),throwable.getMessage(), Toast.LENGTH_SHORT).show();
        }
    }

    public void onSuccess() {
        Toast.makeText(getApplicationContext(),"Upload sucessful", Toast.LENGTH_SHORT).show();    
    }

    public void onProgress(final int percent) {
        progressBar.setProgress(percent);
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class MyClass : Logging.UploadListener {

    // Logging event listeners
    override fun onError(throwable: Throwable) {
        if (throwable is HttpException) {
            if (throwable.code() == 403) {
                Toast.makeText(
                    applicationContext,
                    "Client log upload is not enabled for the application",
                    Toast.LENGTH_SHORT
                ).show()
            } else {
                Toast.makeText(applicationContext, throwable.message, Toast.LENGTH_SHORT).show()
            }
        } else if (throwable is IOException) {
            Toast.makeText(applicationContext, throwable.message, Toast.LENGTH_SHORT).show()
        }
    }

    override fun onSuccess() {
        Toast.makeText(applicationContext, "Upload sucessful", Toast.LENGTH_SHORT).show()
    }

    override fun onProgress(percent: Int) {
        progressBar.progress = percent
    }
}

And add that implementation to the listener list by calling:

1
Logging.addLogUploadListener(uploadListener);
1
Logging.addLogUploadListener(uploadListener)

Setting the Upload Type

By default, when a log upload is reqested, current log data is removed from the log and temporarily stored. The temporary store is then uploaded. If the upload succeeds the temporary store is deleted.

If the upload is cancelled or does not succeed, the default behaviour is to delete the old temporary store the next time an upload is called.

You can control what happens to the temporary store when you upload the log, by adding a third parameter to Logging.uploadLog(client, settingsParameters); that sets the upload type. The types are:

  • Logging.UploadType.DEFAULT Upload the current contents of the log. If there is an old log that did not get uploaded from a previous upload attempt, the old log is deleted and not uploaded.

  • Logging.UploadType.LAST If there is an old log that did not get uploaded from a previous upload attempt, upload the old log but do not upload the current contents of the log.

  • Logging.UploadType.MERGE Upload the current contents of the log, and if there is an old log that did not get uploaded from a previous upload attempt, also upload the old log. You should use some caution with this choice. If repeated attempts to upload the log with this upload type fail, the application could run out of storage space.

You can test for the presence of logs that did not get uploaded by calling Logging.hasPendingLog().

Note

false is always returned if an upload is in progress.

Correlation IDs

Correlation IDs are sent by the client to the SAP Cloud Platform Mobile Services server and are used to trace client requests on the server. A correlation ID will automatically be added to log records when logger requests are made from a given thread during the request/response flow.

The request must be sent using an OkHttpClient configured with the CorrelationInterceptor.

1
2
3
4
5
CorrelationInterceptor correlationInterceptor = new CorrelationInterceptor();

OkHttpClient okHttpClient = new OkHttpClient.Builder()
        .addInterceptor(correlationInterceptor)
        .build();
1
2
3
4
5
val correlationInterceptor = CorrelationInterceptor()

val okHttpClient = OkHttpClient.Builder()
    .addInterceptor(correlationInterceptor)
    .build()

Customizating Console Logging

You can customize what is written to the console and how it is written out. To see the current format, call:

1
String pattern = Logging.getConsoleLoggerPattern();
1
val pattern = Logging.getConsoleLoggerPattern()

To set the format, call:

1
Logging.setConsoleLoggerPattern(pattern);
1
Logging.setConsoleLoggerPattern(pattern)

For more information on patterns, go to the logback website.

Configuring Logging in SAP Cloud Platform Mobile Services

Your application must be configured in the SAP Cloud Platform Mobile Services cockpit to accept uploaded logs. To do that:

  1. From the cockpit, configure your application and select Client Policies.
  2. Scroll down to Log Policy and select Log Upload.

See Defining Client Log Policy for more information.

To view the log in the cockpit, select Logs > Log and Trace, then select your log from the list. You may have to adjust the time range filter to see your log.