When a test fails in CI at 2 AM, you cannot see the browser. You cannot attach a debugger. All you have is the log file. Good logging is not optional — it is your primary debugging tool for remote execution. Log4j2 is the industry standard.
<!-- Add to pom.xml -->
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-core</artifactId>
<version>2.23.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-api</artifactId>
<version>2.23.1</version>
<scope>test</scope>
</dependency><?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Properties>
<Property name="LOG_PATTERN">
%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level
%logger{36} - %msg%n
</Property>
</Properties>
<Appenders>
<!-- Console — shows in terminal/IDE -->
<Console name="Console" target="SYSTEM_OUT">
<PatternLayout pattern="${LOG_PATTERN}" />
</Console>
<!-- File — saved for debugging later -->
<RollingFile name="File"
fileName="logs/test-execution.log"
filePattern=
"logs/test-execution-%d{yyyy-MM-dd}.log">
<PatternLayout pattern="${LOG_PATTERN}" />
<Policies>
<SizeBasedTriggeringPolicy size="10MB" />
</Policies>
<DefaultRolloverStrategy max="5" />
</RollingFile>
</Appenders>
<Loggers>
<!-- Suppress noisy Selenium internal logs -->
<Logger name="org.openqa.selenium" level="WARN" />
<Root level="INFO">
<AppenderRef ref="Console" />
<AppenderRef ref="File" />
</Root>
</Loggers>
</Configuration>| DO Log These | DO NOT Log These |
|---|---|
| Test start and end with test name | Passwords or tokens |
| Navigation: "Navigating to /banking" | Credit card numbers or PII |
| User actions: "Clicking login button" | Every single findElement call |
| Verification results: "Balance is 5000" | Selenium internal wait iterations |
| Step numbers in E2E: "Step 3: Transfer" | Full HTML page source |
| Data provider values (non-sensitive) | Binary data or screenshots as text |
| Error details on failure | Chatty debug messages in production runs |
// What good test logging looks like:
public class BankingLoginTests extends BaseTest {
private static final Logger logger =
LogManager.getLogger(BankingLoginTests.class);
@Test
public void testSuccessfulLogin() {
logger.info("=== Test: Successful Login ===");
navigateTo("/banking");
logger.info("Navigated to Banking portal");
LoginPage loginPage = new LoginPage(getDriver());
logger.info("Entering credentials for: testuser");
DashboardPage dashboard = loginPage.loginAs(
"testuser", "password123");
logger.info("Verifying dashboard loaded");
Assert.assertTrue(dashboard.isLoaded());
String balance = dashboard.getAccountBalance();
logger.info("Account balance: " + balance);
logger.info("=== Test Completed Successfully ===");
}
}
// Log output:
// 2025-01-15 10:30:01.123 [main] INFO BankingLoginTests
// - === Test: Successful Login ===
// 2025-01-15 10:30:01.456 [main] INFO BaseTest
// - Navigating to: https://testerrank.com/banking
// 2025-01-15 10:30:02.789 [main] INFO BankingLoginTests
// - Navigated to Banking portal
// 2025-01-15 10:30:02.790 [main] INFO BankingLoginTests
// - Entering credentials for: testuser
// 2025-01-15 10:30:03.012 [main] INFO LoginPage
// - Logging in as: testuser
// 2025-01-15 10:30:03.456 [main] INFO BankingLoginTests
// - Verifying dashboard loaded
// 2025-01-15 10:30:03.789 [main] INFO BankingLoginTests
// - Account balance: Rs. 50,000
// 2025-01-15 10:30:03.790 [main] INFO BankingLoginTests
// - === Test Completed Successfully ===Q: How do you use logging in your test automation framework?
A: We use Log4j2 with two appenders: Console for real-time monitoring during development, and RollingFile that saves logs to the logs/ directory for post-execution debugging. Each class gets its own logger via LogManager.getLogger(ClassName.class), so log messages show which class produced them. We log test lifecycle events (start, pass, fail), navigation, user actions, data values (non-sensitive), and assertion results. In BasePage, methods like click() and type() log automatically, so page interactions are traced without extra code in tests. We suppress noisy Selenium internal logs by setting org.openqa.selenium to WARN level.
Set the Selenium logger to WARN in log4j2.xml. Without this, Selenium floods your log file with hundreds of internal messages per test, making it impossible to find your actual test steps.
Never log passwords. Not even in debug mode. Not even in your local test runs. The habit will carry over to production, and one day that log file will end up in a Jira ticket or Slack channel. Log the username only: logger.info("Logging in as: " + username)
Key Point: Log4j2 outputs to console and file. Log every test step, navigation, and data value — but never passwords. This is your primary debugging tool in CI.