Optimizing Log Strategy in Cucumber Test Automation with Java using Log4J

Posted by HemaSundar Penugonda on Feb 08, 2025

In any test automation framework, an effective logging strategy plays a crucial role in improving maintainability and debugging efficiency. Well-structured logs provide valuable insights into test execution, making it easier to analyze failures and understand test behavior.

Below are some key reasons why logging is essential:

  • Facilitates efficient debugging and issue resolution.
  • Enhances test reports by providing detailed step-by-step execution insights.

Do We Need a Third-Party Logging Library?

Java provides built-in logging utilities through the System.out class, which allow logs to be printed to the console or written to files. However, achieving advanced logging features such as rolling file logs, HTML report integration, or structured logging requires extensive custom implementation. To simplify this, third-party libraries like SLF4J, Log4J, or Logback can be leveraged.

Among these, Log4J is widely adopted in test automation projects. This article focuses on configuring Log4J in a Cucumber-based test automation framework to implement an efficient logging strategy.

Aspect-Oriented Programming (AOP) and Logging

Logging is not a core business requirement but a cross-cutting concern. To maintain clean code architecture, it is essential to separate logging logic from business logic. This ensures better maintainability and modularity.

Implementing a Custom Log Appender

To seamlessly integrate logs into Cucumber reports, a custom Log4J appender can be created. Below is an implementation of a CucumberLogAppender that captures log messages and associates them with the respective Cucumber scenario.


import com.hema.acs.api.bags.test_data_beans.ScenarioContext;
import org.apache.logging.log4j.core.Appender;
import org.apache.logging.log4j.core.Core;
import org.apache.logging.log4j.core.Filter;
import org.apache.logging.log4j.core.LogEvent;
import org.apache.logging.log4j.core.appender.AbstractAppender;
import org.apache.logging.log4j.core.config.Property;
import org.apache.logging.log4j.core.config.plugins.Plugin;
import org.apache.logging.log4j.core.config.plugins.PluginAttribute;
import org.apache.logging.log4j.core.config.plugins.PluginElement;
import org.apache.logging.log4j.core.config.plugins.PluginFactory;

@Plugin(
        name = "CucumberLogAppender",
        category = Core.CATEGORY_NAME,
        elementType = Appender.ELEMENT_TYPE)
public class CucumberLogAppender extends AbstractAppender {
    protected CucumberLogAppender(String name, Filter filter) {
        super(name, filter, null, true, Property.EMPTY_ARRAY);
    }

    @PluginFactory
    public static CucumberLogAppender createAppender(@PluginAttribute("name") String name, @PluginElement("Filter") Filter filter) {
        return new CucumberLogAppender(name, filter);
    }

    @Override
    public void append(LogEvent event) {
        ScenarioContext.getInstance().getCurrentScenario().log(event.getMessage().getFormattedMessage());
    }
}

Configuring Log4J2 for the Framework

The following Log4J2 JSON configuration demonstrates how to set up different logging appenders, including console logging, rolling file logging, and our custom CucumberLogAppender:

{
  "configuration": {
    "status": "error",
    "name": "JSONConfigDemo",
    "packages": "com.hemasundar.test_automation.test_automation_api",
    "ThresholdFilter": {
      "level": "debug"
    },
    "CustomLevels": {
      "CustomLevel": {
        "name": "NAME_TEST",
        "intLevel": "50"
      }
    },
    "appenders": {
      "Console": {
        "name": "STDOUT",
        "PatternLayout": {
          "pattern": "%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n"
        }
      },
      "CucumberLogAppender": {
        "name": "STDOUT",
        "PatternLayout": {
          "pattern": "%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n"
        }
      },
      "RollingFile": {
        "name": "File",
        "fileName": "target/surefire-reports/log.log",
        "filePattern": "logs/backup-%d{MM-dd-yy-HH-mm-ss}-%i.log.gz",
        "PatternLayout": {
          "pattern": "%d{yyyy-MM-dd HH:mm:ss} %-5p %c{1}:%L - %m%n"
        },
        "Policies": {
          "SizeBasedTriggeringPolicy": {
            "size": "100 MB"
          }
        },
        "DefaultRolloverStrategy": {
          "max": "10"
        }
      }
    },
    "loggers": {
      "root": {
        "level": "debug",
        "AppenderRef": [
          {
            "ref": "STDOUT",
            "level": "info"
          },
          {
            "ref": "File",
            "level": "info"
          }
        ]
      }
    }
  }
}

Utilizing Logging in Code

Once Log4J is configured, logging can be seamlessly integrated into the code. The following example demonstrates logging scenario details before execution.


import com.hema.acs.api.bags.api_requests.DedomenaApis;
import com.hema.acs.api.bags.test_data_beans.ScenarioContext;
import com.hema.acs.api.bags.utils.CustomLogging;
import io.cucumber.java.*;
import lombok.extern.log4j.Log4j2;

import java.util.ArrayList;
import java.util.List;

/**
 * @author hemasundarpenugonda
 */
@Log4j2
public class ServiceHooks {

    @Before
    public void printScenarioDetails(Scenario scenario) {
        log.info(scenario.getUri());
        log.info(scenario.getName());
    }
}

Conclusion

A well-structured logging strategy in a test automation framework enhances debugging capabilities and improves test reporting. By integrating Log4J with Cucumber, we can capture meaningful logs, ensure better traceability, and make troubleshooting more efficient. Implementing a custom log appender allows direct integration with Cucumber reports, making logs more relevant and useful for test analysis.

By following the principles of Aspect-Oriented Programming (AOP), we maintain a clean separation of concerns, ensuring that logging does not interfere with business logic. Implementing such strategies makes test automation frameworks more efficient, scalable, and maintainable.