Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
8fbab07
Improve Android example
ppkarwasz Oct 8, 2024
d7b58cd
Fix RAT failure
ppkarwasz Oct 9, 2024
e625caf
Replace `android-actions` with script
ppkarwasz Oct 9, 2024
7a9d57f
Rename tests appropriately
ppkarwasz Oct 9, 2024
65e8781
Use temurin as Java distribution
ppkarwasz Oct 9, 2024
2f187d2
Fix `curl` invocation
ppkarwasz Oct 9, 2024
8b202fa
Fix sdkmanager location
ppkarwasz Oct 9, 2024
caa27d1
Add exact number of licenses to accept
ppkarwasz Oct 9, 2024
a9dbcaa
Fix bug in project name
ppkarwasz Oct 9, 2024
bc45fbb
Try without setting up Android SDK
ppkarwasz Oct 9, 2024
c1262ac
Setup Gradle and list AVD devices
ppkarwasz Oct 9, 2024
1d5d03e
Find AVDManager on Github
ppkarwasz Oct 9, 2024
e99d9ab
Execute `avdmanager`
ppkarwasz Oct 9, 2024
02ca42e
Try setting up an AVD
ppkarwasz Oct 9, 2024
52d8627
Discover available system images
ppkarwasz Oct 9, 2024
24c9f58
Download system image for AVD
ppkarwasz Oct 9, 2024
4990c35
Accept license on image
ppkarwasz Oct 9, 2024
5e2f413
Try to start emulator
ppkarwasz Oct 9, 2024
56754eb
List installed packages on Github runner
ppkarwasz Oct 9, 2024
850f8e4
Also install `platform-tools`
ppkarwasz Oct 9, 2024
2c4dd97
Run emulator without sound and window
ppkarwasz Oct 9, 2024
3a9bc2a
Change platform to ARM8a
ppkarwasz Oct 9, 2024
a79e336
Fix package path
ppkarwasz Oct 9, 2024
f9b3e3a
Try another architecture
ppkarwasz Oct 9, 2024
f1a5820
Add emulator explicitly
ppkarwasz Oct 9, 2024
e8a8d64
Try default image
ppkarwasz Oct 9, 2024
9942c58
Try merging steps
ppkarwasz Oct 9, 2024
61b3d63
Try `reactivecircus/android-emulator-runner`
ppkarwasz Oct 9, 2024
a868370
Wait until `adb` detects the device
ppkarwasz Oct 9, 2024
d9fccac
Kill the emulator at the end
ppkarwasz Oct 9, 2024
7d2d8eb
Try using `logcat` to debug the AVD
ppkarwasz Oct 9, 2024
3e918f1
Don't block on logcat
ppkarwasz Oct 9, 2024
26a2fb8
Fix snippet that waits for the emulator
ppkarwasz Oct 10, 2024
b9c4887
Fix script
ppkarwasz Oct 10, 2024
2fa7bf0
Prevent script to fail on `adb` return value
ppkarwasz Oct 10, 2024
c2ea55f
Add README.adoc
ppkarwasz Oct 10, 2024
2824087
Hardcode Log4j version in Android sample
ppkarwasz Oct 10, 2024
25793cb
Merge remote-tracking branch 'apache/main' into feature/android-tests
ppkarwasz Oct 10, 2024
a4c8ef2
Allow overriding `log4j.version` from CLI
ppkarwasz Oct 10, 2024
6ae4ce7
Allow to specify a version in `android-test`
ppkarwasz Oct 10, 2024
2628492
Fix Spotless warnings
ppkarwasz Oct 10, 2024
3aa061e
Try to ensure emulator always stops
ppkarwasz Oct 10, 2024
d3d44a9
Fix typo in parameter name
ppkarwasz Oct 10, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 49 additions & 22 deletions .github/workflows/android-reusable-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -41,41 +41,68 @@ jobs:
- name: Setup Java
uses: actions/setup-java@b36c23c0d998641eff861008f374ee103c25ac73 # 4.4.0
with:
distribution: 'temurin'
java-version: 17

- name: Setup Android SDK
- name: Setup Gradle
uses: gradle/actions/setup-gradle@d156388eb19639ec20ade50009f3d199ce1e2808 # 4.1.0

- name: Enable KVM
run: |
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm

- name: Create AVD Device
id: avd
shell: bash
env:
ANDROID_SDK_VERSION: 10406996
ANDROID_SDK_CHECKSUM: 8919e8752979db73d8321e9babe2caedcc393750817c1a5f56c128ec442fb540
run: |
# Create home directory
export ANDROID_HOME=$HOME/.android/sdk
mkdir -p $ANDROID_HOME

# Download and verify the `cmdline-tools`
curl -O $ANDROID_HOME/cmdline-tools.zip https://dl.google.com/android/repository/commandlinetools-linux-${ANDROID_SDK_VERSION}_latest.zip
echo "$ANDROID_SDK_CHECKSUM $ANDROID_HOME/cmdline-tools.zip" | sha256sum -c
unzip -d $ANDROID_HOME/cmdline-tools{,.zip}
mv $ANDROID_HOME/cmdline-tools/{cmdline-tools,latest}

# Accept licenses
yes | $ANDROID_HOME/cmdline-tools/bin/sdkmanager --licenses

# Installing the remaining tools
$ANDROID_HOME/cmdline-tools/bin/sdkmanager tools platform-tools

# Exporting ANDROID_HOME and adding sdkmanager to the PATH
echo "ANDROID_HOME=$ANDROID_HOME" >> $GITHUB_ENV
echo $ANDROID_HOME/cmdline-tools/bin >> $GITHUB_PATH
# List installed and available packages
$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager --list
# Download images
echo y | $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager \
--install "system-images;android-31;default;x86_64" \
emulator platform-tools
# Create device
$ANDROID_HOME/cmdline-tools/latest/bin/avdmanager create avd \
--name generic-api-31-device \
--device "5.4in FWVGA" \
--package "system-images;android-31;default;x86_64"
# Run emulator
$ANDROID_HOME/emulator/emulator \
-no-audio -no-window \
-avd generic-api-31-device &
# Enabled the cleanup job
echo EMULATOR_STARTED=true >> $GITHUB_ENV
# Wait for device to go online
# It might take up to 5 minutes
for i in {1..300}; do
# Don't stop the script if `adb` fails
boot_completed=$($ANDROID_HOME/platform-tools/adb shell getprop sys.boot_completed 2> /dev/null || echo "0")
if [ "${boot_completed}" = "1" ]; then break; fi
sleep 1
done

- name: Build
id: build
shell: bash
env:
LOG4J_VERSION: ${{ inputs.log4j-version }}
run: |
log4j-samples-android/gradlew -p log4j-samples-gradlew \
log4j-samples-android/gradlew -p log4j-samples-android \
--console plain \
-Plog4jVersion=$LOG4J_VERSION \
-Plog4j.version=$LOG4J_VERSION \
build connectedCheck

- name: Remove AVD Device
if: ${{ always() && env.EMULATOR_STARTED == 'true' }}
shell: bash
run: |
# Kill the emulator
$ANDROID_HOME/platform-tools/adb -s emulator-5554 emu kill
# Delete the device
$ANDROID_HOME/cmdline-tools/latest/bin/avdmanager delete avd \
--name generic-api-31-device
14 changes: 6 additions & 8 deletions .github/workflows/android-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,16 @@ name: android-test

on:
workflow_dispatch:
schedule:
- cron: "29 17 * * *"
inputs:
log4j-version:
description: The version of Log4j Core to use
default: "2.25.0-SNAPSHOT"

permissions: read-all

jobs:

android-test:
strategy:
fail-fast: false
matrix:
log4j-version: [ "2.25.0-SNAPSHOT" ]
uses: apache/logging-log4j-samples/.github/workflows/android-reusable-test.yaml@main
uses: apache/logging-log4j-samples/.github/workflows/android-reusable-test.yaml@feature/android-tests
with:
log4j-version: ${{ matrix.log4j-version }}
log4j-version: ${{ inputs.log4j-version }}
12 changes: 9 additions & 3 deletions README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,26 @@ limitations under the License.

This repository contains several examples to help the user with some of the more advanced Log4j features.

xref:log4j-samples-android/README.adoc[`log4j-samples-android`]::
Explains how to use Log4j Core on Android.

xref:log4j-samples-configuration/README.adoc[`log4j-samples-configuration`]::
Explains how to use a custom programmatic configuration,
Explains how to use a custom programmatic configuration.

xref:log4j-samples-flume-embedded/README.adoc[`log4j-samples-flume-embedded`]::
Explains how to use an embedded Flume agent to send logs to https://flume.apache.org[Apache Flume],
Explains how to use an embedded Flume agent to send logs to https://flume.apache.org[Apache Flume.]

xref:log4j-samples-flume-remote/README.adoc[`log4j-samples-flume-remote`]::
Explains how to send logs to https://flume.apache.org[Apache Flume],

xref:log4j-samples-graalvm/README.adoc[`log4j-samples-graalvm`]::
Explains how to use Log4j API and its implementation to create native GraalVM images.

xref:log4j-samples-jlink/README.adoc[`log4j-samples-jlink`]::
An example of JLink custom JRE.

xref:log4j-samples-loggerProperties/README.adoc[`log4j-samples-loggerProperties`]::
Explains how to write a custom property lookup,
Explains how to write a custom property lookup.

xref:log4j-spring-cloud-config-sample-application/README.md[`log4j-spring-cloud-config-sample-application`]::
An example of Spring Boot application that reads its logging configuration from a Spring Cloud Configuration Server.
Expand Down
40 changes: 40 additions & 0 deletions log4j-samples-android/README.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
////
Licensed to the Apache Software Foundation (ASF) under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The ASF licenses this file to You under the Apache License, Version 2.0
(the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at

https://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
////
= Apache Log4j Samples: Android application

This project is an example of Android application that uses
https://logging.apache.org/log4j/2.x/manual/api.html[the Log4j API]
for logging together with its reference implementation
https://logging.apache.org/log4j/2.x/manual/implementation.html[Log4j Core].

To build the project, run:

[source,shell]
----
./gradlew build
----

inside this directory.

Since version `2.25.0` this application is part of the integration tests for a Log4j release.

== Known Android limitations

The Android platform does not implement all the features of a standard JRE, which will prevent you from using all the features offered by Log4j Core.
For a list of known limitations, see
https://logging.apache.org/log4j/2.x/faq.html#android[our Android F.A.Q. entry].

25 changes: 19 additions & 6 deletions log4j-samples-android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ plugins {

android {
compileSdk 34
namespace "org.apache.logging.log4j.samples.android"

defaultConfig {
applicationId "org.apache.logging.log4j.samples.android"
Expand Down Expand Up @@ -48,17 +49,29 @@ android {
}
}

def log4jVersion = providers.gradleProperty("log4j.version").get()

dependencies {

implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.11.0'
implementation 'androidx.appcompat:appcompat:1.7.0'
implementation 'com.google.android.material:material:1.12.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'

testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.2.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1'
androidTestImplementation 'org.assertj:assertj-core:3.26.3'

// Log4j
implementation group: 'org.apache.logging.log4j', name: 'log4j-api', version: log4jVersion
implementation group: 'org.apache.logging.log4j', name: 'log4j-core', version: log4jVersion
androidTestImplementation(group: 'org.apache.logging.log4j', name: 'log4j-core-test', version: log4jVersion) {
exclude group: 'com.google.code.java-allocation-instrumenter'
exclude group: 'org.apache.logging.log4j', module: 'log4j-api-test'
exclude group: 'org.hamcrest'
exclude group: 'org.junit.jupiter'
exclude group: 'org.junit.platform'
exclude group: 'org.springframework'
}

//Log4j
implementation 'org.apache.logging.log4j:log4j-api:2.24.1'
implementation 'org.apache.logging.log4j:log4j-core:2.24.1'
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to you under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.logging.log4j.samples.android;

import static androidx.test.espresso.Espresso.onView;
import static androidx.test.espresso.action.ViewActions.click;
import static androidx.test.espresso.assertion.ViewAssertions.doesNotExist;
import static androidx.test.espresso.assertion.ViewAssertions.matches;
import static androidx.test.espresso.matcher.ViewMatchers.isDisplayed;
import static androidx.test.espresso.matcher.ViewMatchers.withId;
import static androidx.test.espresso.matcher.ViewMatchers.withText;
import static org.assertj.core.api.Assertions.assertThat;

import androidx.test.ext.junit.rules.ActivityScenarioRule;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import java.util.AbstractMap;
import java.util.Arrays;
import java.util.Map;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.core.Appender;
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.core.appender.ConsoleAppender;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.config.LoggerConfig;
import org.apache.logging.log4j.core.test.appender.ListAppender;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

/**
* Instrumented test, which will execute on an Android device.
*/
@RunWith(AndroidJUnit4.class)
public class MainActivityTest {

@Rule
public ActivityScenarioRule<MainActivity> activityScenarioRule = new ActivityScenarioRule<>(MainActivity.class);

private static Configuration configuration;
private static ListAppender appender;
private static Logger logger;

@BeforeClass
public static void setup() {
LoggerContext context = LoggerContext.getContext(false);
configuration = context.getConfiguration();
appender = configuration.getAppender("LIST");
logger = context.getLogger(MainActivity.class);
}

@Test
public void hasExpectedConfiguration() {
assertThat(configuration.getConfigurationSource().getLocation())
.startsWith("jar:file:")
.endsWith("!/log4j2-test.xml");
// Check appenders
Appender console = configuration.getAppender("CONSOLE");
assertThat(console).as("Console Appender").isInstanceOf(ConsoleAppender.class);
Appender list = configuration.getAppender("LIST");
assertThat(list).as("List Appender").isInstanceOf(ListAppender.class);
// Check logger configurations
assertThat(configuration.getLoggers()).hasSize(2);
assertThat(configuration.getRootLogger().getExplicitLevel()).isEqualTo(Level.INFO);
assertThat(configuration.getRootLogger().getAppenders())
.hasSize(2)
.containsExactly(entry("CONSOLE", console), entry("LIST", list));
assertThat(configuration.getLoggerConfig(MainActivity.class.getName()))
.isNotNull()
.extracting(LoggerConfig::getExplicitLevel)
.isEqualTo(Level.DEBUG);
}

@Test
public void logMessagesAreDelivered() {
assertThat(logger.getLevel()).isEqualTo(Level.DEBUG);
appender.clear();
for (int buttonId : MainActivity.buttonIds) {
onView(withId(buttonId)).perform(click());
}
Level[] expectedLevels = {Level.FATAL, Level.ERROR, Level.WARN, Level.INFO, Level.DEBUG};
String[] expectedMessages =
Arrays.stream(expectedLevels).map(l -> l + "-Hello " + l + "!").toArray(String[]::new);
assertThat(appender.getMessages()).hasSize(expectedLevels.length).containsExactly(expectedMessages);
}

@Test
public void logLevelChanges() {
assertThat(logger.getLevel()).isEqualTo(Level.DEBUG);
onView(withId(R.id.setLogLevelBtn)).perform(click());
onView(withText("Set log level")).check(matches(isDisplayed()));
onView(withText("ERROR")).perform(click());
onView(withText("Select")).perform(click());
onView(withText("Set log level")).check(doesNotExist());
assertThat(logger.getLevel()).isEqualTo(Level.ERROR);
}

private static Map.Entry<String, Appender> entry(String name, Appender appender) {
return new AbstractMap.SimpleImmutableEntry<>(name, appender);
}
}
Loading