An Android package usable for HTTP logging and passing analytics to a third-party SDK. Also included is an abstraction class for using Google's Firebase analytics platform.
The main functionality of the logging module is to provide a single source of truth for standardised code logging functionality that makes it simple to switch logging framework if desired.
The three packages have the following functionality:
- The
:apimodule contains logger interfaces. - The
:implmodule contains logger implementations and their associated Hilt modules for dependency injection into their constructors. - The
:testdoublemodule contains fake logger implementations for testing and their Hilt modules for dependency injection into their constructors.
In your build.gradle.kts files, for each module (and for each build flavor), add dependencies
needed for example:
// Refer to the uploaded packages for the latest version:
// https://github.com/orgs/govuk-one-login/packages?repo_name=mobile-android-logging
val loggingLibraryVersion: String by rootProject.extra("1.2.3")
// Both `impl` and `testdouble` artifacts provide the `api` module as a gradle `api` dependency.
implementation("uk.gov.logging:logging-impl:$loggingLibraryVersion")
testImplementation("uk.gov.logging:logging-testdouble:$loggingLibraryVersion")
androidTestImplementation("uk.gov.logging:logging-testdouble:$loggingLibraryVersion")To migrate to using version catalogs, see Gradle Version Catalogs.
In your root ./gradle/libs.versions.toml file:
[versions]
gov-logging = "1.2.3" # https://github.com/orgs/govuk-one-login/packages?repo_name=mobile-android-logging
[libraries]
uk-gov-logging-api = { module = "uk.gov.logging:logging-api", version.ref = "gov-logging"}
uk-gov-logging-impl = { module = "uk.gov.logging:logging-impl", version.ref = "gov-logging"}
uk-gov-logging-testdouble = { module = "uk.gov.logging:logging-testdouble", version.ref = "gov-logging"}Then in your build.gradle.kts files:
// Both `impl` and `testdouble` artifacts provide the `api` module as a gradle `api` dependency.
implementation(libs.uk.gov.logging.impl)
testImplementation(libs.uk.gov.logging.testdouble)
androidTestImplementation(libs.uk.gov.logging.testdouble)Despite there being usable Hilt modules in the logging module, these are mainly for
Android Logging. You should also create your own Singleton scoped Hilt modules to provide the
specific analytics logger implementation that you need, and also to provide the Firebase analytics
implementation. For example:
@InstallIn(SingletonComponent::class)
@Module
object AnalyticsSingletonModule {
@Provides
@Singleton
fun providesAnalyticsLogger(
analyticsLogger: FirebaseAnalyticsLogger
): AnalyticsLogger = MemorisedAnalyticsLogger(analyticsLogger)
}
@InstallIn(SingletonComponent::class)
@Module
class FirebaseSingletonModule {
@Provides
@Singleton
fun providesFirebaseAnalytics(): FirebaseAnalytics = Firebase.analytics
}Android Logger v2 offers a set of convenience functions for logging messages and exceptions. It also allows developers to record exceptions either with custom error keys or without custom error keys.
The logger provides the following functions:
debug–Logs debug messages.
info–Logs informational messages.
Error Logging
An error function that allows the consumer to set custom error keys, set a tag, and record an exception.
An error function that allows the consumer to log an error, set a tag, and record an exception, without setting custom error keys.
An error function that allows the consumer to log only error messages and set a tag, without recording an exception or custom error keys.
warning–Logs warning messages and allows the user to set tags.
class AndroidLogger(
private val crashLogger: CrashLogger,
) : Logger {
override fun debug(
tag: String,
message: String,
) {
if (BuildConfig.DEBUG) {
Log.d(tag, message)
}
crashLogger.log("I : $tag : $message")
}
override fun info(
tag: String,
message: String,
) {
if (BuildConfig.DEBUG) {
Log.i(tag, message)
}
crashLogger.log("I : $tag : $message")
}
override fun error(
tag: String,
message: String,
throwable: Throwable,
errorKeys: ErrorKeys,
) {
if (BuildConfig.DEBUG) {
Log.e(tag, message, throwable)
}
crashLogger.log(throwable, errorKeys)
}
override fun error(
tag: String,
message: String,
throwable: Throwable,
) {
if (BuildConfig.DEBUG) {
Log.e(tag, message, throwable)
}
crashLogger.log(throwable)
}
override fun error(
tag: String,
message: String,
) {
if (BuildConfig.DEBUG) {
Log.e(tag, message)
}
crashLogger.log("E: $tag : $message")
}
override fun warning(
tag: String,
message: String,
) {
if (BuildConfig.DEBUG) {
Log.w(tag, message)
}
crashLogger.log("W: $tag : $message")
}
}Android Logger v3 introduces a structured, entry-based logging API. Instead of calling individual log methods with separate String parameters, v3 enforces consistency in log configuration by using LogEntry sealed types to represent log events, with data classes predefined for each log level for the consumer's convenience.
Entries in the logger are processed through the log function. The sub-functions are still available for flexibility.
It also supports CustomKey types for setting typed key-value pairs on crash reports.
Logger The Logger interface is a functional interface with convenience sub-functions (debug, info, warning, error).
The logger provides the following sub-functions:
debug–Logs debug messages with a tag.
info–Logs informational messages with a tag.
warning–Logs warning messages with a tag.
verbose–Logs verbose messages with a tag.
Error Logging An error function that allows the consumer to log an error, set a tag, parse the exception, and set typed custom keys (StringKey, IntKey, DoubleKey, BooleanKey).
For examples showing how to use the Logger, see LoggerExample
LogEntry.Message interface extends the LogEntry sealed class, allowing sub data classes to extend the Message interface for predefined log levels:
| Entry | Description |
|---|---|
LogEntry.Verbose |
Sub data class for verbose entries |
LogEntry.Debug |
Sub data class for debug entries |
LogEntry.Info |
Sub data class for info entries |
LogEntry.Warn |
Sub data class for warn entries |
LogEntry.Exception interface extends the LogEntry sealed class, the Error data class extends Exception interface specifically for the error log level:
| Entry | Description |
|---|---|
LogEntry.Error |
Error entry with a tag and optional custom keys |
| Implementation | Description |
|---|---|
| MultiLogger | Funnel that passes all entries to a collection of sub-loggers. Each sub-logger is responsible for filtering and processing the entry types it cares about. |
| LogcatLogger | Logs LogEntry to Android log functions locally. Designed as a Logger specifically for log entries not ready to be logged remotely. |
| CrashlyticsLogger | Filters out isLocalOnly entries and logs entries to Firebase crashlytics. Use for remote crash reporting and error tracking. |
MemorisedLogger Stores entries in memory for assertions. Optionally decorates a sub-logger. Use in test fixtures with Hamcrest-matchers.
The packages can be found at here at Logging Module Packages.
The tags can be found at here at Logging Module Tags.
There has been a breaking change on some of the required parameters. We support both the original and the new GA4. However you will need to import the correct type based on your need.
// GA4 type
import uk.gov.logging.api.analytics.parameters.v2.RequiredParameters// Original type
import uk.gov.logging.api.analytics.parameters.RequiredParametersIf you need to use both GA4 and the original one in the same file, you will need to import as an alias
import uk.gov.logging.api.analytics.parameters.RequiredParameters
import uk.gov.logging.api.analytics.parameters.v2.RequiredParameters as RequiredParametersV2Implementation Guide One Login Mobile Application Data Schema V1.0.
Gradle secure hash algorithm (SHA) pinning is in place through the distributionSha256Sum attribute in gradle-wrapper.properties. This means the gradle-wrapper must upgrade through the ./gradlew wrapper command.
Example gradle-wrapper.properties
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionSha256Sum=2db75c40782f5e8ba1fc278a5574bab070adccb2d21ca5a6e5ed840888448046
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
networkTimeout=10000
validateDistributionUrl=true
Use the following command to update the gradle wrapper. Run the same command twice, reason.
./gradlew wrapper --gradle-version=8.10.2 --distribution-type=bin --gradle-distribution-sha256-sum=31c55713e40233a8303827ceb42ca48a47267a0ad4bab9177123121e71524c26Flags:
gradle-versionself explanatorydistribution-typeset tobinshort for binary refers to the gradle bin, often in this formatgradle-8.10.2-bin.zipgradle-distribution-sha256-sumthe SHA 256 checksum from this page, pick the binary checksum for the version used
The gradle wrapper update can include:
- gradle-wrapper.jar
- gradle-wrapper.properties
- gradlew
- gradlew.bat
You can use the following command to check the SHA 256 checksum of a file
shasum -a 256 gradle-8.10.2-bin.zipThere are GitHub Actions workflows for a hotfix pull request and merging a hotfix to a temporary hotfix branch.
The temporary hotfix branch is currently expected to be named "temp/hotfix". If a different name is
desired please edit the value under "branches:" in .github/workflows/on_push_hotfix.yml.
The hotfix branch name should be in the format "hotfix/M.m.p".
Once the hotfix PR has been approved and the "Squash and merge" button pressed, the merge title must be in the format "Merge pull request #xxx from govuk-one-login/release/M.m.p" to allow for the correct version to be extracted and used as a tag.