Logging
When something goes wrong (and it will), logs are your best friend. The SDK uses SLF4J with Logback-Android — industry standards that give you full control over what gets logged and where.
Why Use Logging?
Logging is essential for:
- Debugging — Track down issues during development
- Monitoring — Understand application behavior in production
- Troubleshooting — Diagnose problems reported by users
- Auditing — Keep records of device operations
Quick Setup
1. Add Dependencies
In your app/build.gradle:
dependencies {
// SLF4J API
implementation 'org.slf4j:slf4j-api:2.0.17'
// Logback-Android implementation
implementation 'com.github.tony19:logback-android:3.0.0'
// ... your other dependencies
}
2. Create the Config File
Create app/src/main/assets/logback.xml:
<configuration xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="https://tony19.github.io/logback-android/xml"
xsi:schemaLocation="https://tony19.github.io/logback-android/xml https://cdn.jsdelivr.net/gh/tony19/logback-android/logback.xsd">
<appender name="logcat" class="ch.qos.logback.classic.android.LogcatAppender">
<tagEncoder>
<pattern>%logger{12}</pattern>
</tagEncoder>
<encoder>
<pattern>[%-20thread] %msg</pattern>
</encoder>
</appender>
<appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>${LOGS_DIR}/app.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>${LOGS_DIR}/app.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<maxFileSize>1MB</maxFileSize>
<maxHistory>100</maxHistory>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<!-- Set Log Level of specific package -->
<logger name="com.example.sdk" level="INFO" />
<root level="DEBUG">
<appender-ref ref="logcat" />
<appender-ref ref="FILE" />
</root>
</configuration>
3. Initialize the Log Directory
In your Application class:
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
// Set log directory BEFORE any logging happens
System.setProperty("LOGS_DIR", getFilesDir().getAbsolutePath() + "/logs");
}
}
Register it in AndroidManifest.xml:
<application
android:name=".MyApplication"
... >
Done. Logging is ready.
Using Loggers
Create a Logger
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class PrintManager {
private final Logger logger = LoggerFactory.getLogger(PrintManager.class);
// Now use logger.debug(), logger.info(), etc.
}
Log Levels
| Level | When to Use | Example |
|---|---|---|
debug | Detailed diagnostic info | Variable values, method calls |
info | Normal operations | Successful connections, state changes |
warn | Something unusual | Deprecated API usage, recoverable errors |
error | Something failed | Failed operations, caught exceptions |
Example Usage
public class DeviceOperations {
private final Logger logger = LoggerFactory.getLogger(DeviceOperations.class);
public void connectToDevice(DeviceConnection device) {
logger.debug("Attempting connection to: {}", device.getName());
deviceAdapter.connect()
.thenAccept(unused -> {
logger.info("Successfully connected to {}", device.getName());
})
.exceptionally(error -> {
logger.error("Connection failed for device: {}", device.getName(), error);
return null;
});
}
public void printLabel(String template, String data, int copies) {
logger.debug("Print request: template={}, copies={}", template, copies);
if (copies > 100) {
logger.warn("Large print job requested: {} copies", copies);
}
printerAdapter.print(template, data, copies)
.thenAccept(unused -> logger.info("Print job completed: {} copies", copies))
.exceptionally(error -> {
logger.error("Print failed: template={}", template, error);
return null;
});
}
}
Best Practices
1. Use Parameterized Logging
// ✅ Good — parameters only evaluated if level is enabled
logger.debug("Printing {} copies of {}", copies, templateName);
// ❌ Avoid — string concatenation always happens
logger.debug("Printing " + copies + " copies of " + templateName);
2. Log Exceptions Properly
Pass the exception as the last parameter for full stack traces:
// ✅ Good — full stack trace in logs
logger.error("Operation failed for device {}", deviceName, exception);
// ❌ Avoid — loses stack trace
logger.error("Operation failed: " + exception.getMessage());
3. Include Context
// ✅ Good — includes context
logger.error("Failed to print label '{}' on printer '{}'",
labelName, printerName, exception);
// ❌ Avoid — missing context
logger.error("Print failed", exception);
4. Don't Log Sensitive Data
// ❌ Avoid — don't log passwords, tokens, personal data
logger.debug("Login with username: {} and password: {}", username, password);
// ✅ Good — log safe information only
logger.debug("Login attempt for user: {}", username);
5. Use Appropriate Levels
// ✅ Good — appropriate levels
logger.debug("Processing item {} of {}", current, total);
logger.info("Connection established");
logger.warn("Retrying connection, attempt {}", attempt);
logger.error("Failed to connect", exception);
// ❌ Avoid — everything as ERROR
logger.error("Processing item"); // Should be DEBUG
Configuration Options
Log Levels
Control verbosity in logback.xml:
<!-- See everything from the SDK -->
<logger name="com.averydennison.addevicemanager" level="DEBUG" />
<!-- Only errors from noisy libraries -->
<logger name="some.chatty.library" level="ERROR" />
<!-- Default level for everything else -->
<root level="INFO">
<appender-ref ref="LOGCAT" />
</root>
Level hierarchy: DEBUG < INFO < WARN < ERROR
Setting level to INFO means you see INFO, WARN, and ERROR — but not DEBUG.
File Rotation
Control how logs are stored:
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<!-- Daily rotation with index for size overflow -->
<fileNamePattern>${LOGS_DIR}/app.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<!-- Split file when it hits 1MB -->
<maxFileSize>1MB</maxFileSize>
<!-- Keep 7 days of logs -->
<maxHistory>7</maxHistory>
<!-- Never use more than 10MB total -->
<totalSizeCap>10MB</totalSizeCap>
</rollingPolicy>
Log Format
Customize what each line looks like:
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
Pattern elements:
%d{...}— Date/time format%thread— Thread name%-5level— Log level (padded to 5 characters)%logger{36}— Logger name (truncated to 36 characters)%msg— Log message%n— Line separator
Accessing Log Files
Log files are stored in the app's private storage:
File logsDir = new File(getFilesDir(), "logs");
File[] logFiles = logsDir.listFiles();
if (logFiles != null) {
for (File logFile : logFiles) {
logger.info("Log file: {}, size: {} bytes",
logFile.getName(), logFile.length());
}
}
Sharing Log Files
To share logs with support teams:
public void shareLogs() {
File logsDir = new File(getFilesDir(), "logs");
File latestLog = new File(logsDir, "app.log");
if (latestLog.exists()) {
Uri logUri = FileProvider.getUriForFile(
this,
"com.example.myapp.fileprovider",
latestLog
);
Intent shareIntent = new Intent(Intent.ACTION_SEND);
shareIntent.setType("text/plain");
shareIntent.putExtra(Intent.EXTRA_STREAM, logUri);
shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
startActivity(Intent.createChooser(shareIntent, "Share logs"));
}
}
Troubleshooting
Logs Not Appearing
-
Check log level — Ensure your logger level allows the message:
<root level="DEBUG"> <!-- Must be DEBUG or lower to see debug messages --> -
Verify logback.xml — Ensure file is in
src/main/assets/ -
Check Application class — Verify
LOGS_DIRis set before any logging
Log Files Not Created
-
Check directory — Verify the directory path exists and is writable:
File logsDir = new File(getFilesDir(), "logs");
if (!logsDir.exists()) {
boolean created = logsDir.mkdirs();
logger.info("Logs directory created: {}", created);
} -
Verify property — Ensure
LOGS_DIRsystem property is set in Application.onCreate()
Performance Issues
If logging impacts performance:
- Reduce log level in production (INFO or WARN)
- Limit file size and history in
logback.xml - Use async appender for file logging:
<appender name="ASYNC_FILE" class="ch.qos.logback.classic.AsyncAppender">
<appender-ref ref="FILE" />
</appender>
Next Steps
| Guide | What You'll Learn |
|---|---|
| Getting Started | SDK setup and initialization |
| Callbacks | Monitor SDK events |
| Error Handling | Handle failures gracefully |
| Telemetry | Device analytics and usage data |
For more information about Logback-Android, visit: https://github.com/tony19/logback-android