Skip to main content

Logging, Debugging and Testing Backend Code

Series: AEM Backend Essentials — Part 6

Updated 5 min readMarkdown
Part of seriesAEM Backend Essentials
Logging, Debugging and Testing Backend Code

On AEM as a Cloud Service you can't SSH into a server, edit a file or attach a debugger to Production. That makes three habits essential: logging that tells you what happened, local debugging that finds problems early, and automated tests that catch them before the pipeline does. This post covers all three, and finishes the Essentials series.

Logging with SLF4J

Use SLF4J in every class that needs to log:

private static final Logger LOG = LoggerFactory.getLogger(MyService.class);

LOG.info("Imported {} items from {}", count, source);
LOG.warn("Catalog API returned status {} for id {}", status, id);
LOG.error("Could not update {}", path, exception);

Habits that pay off:

  • Use placeholders ({}) instead of string concatenation. They cost almost nothing when the level is disabled
  • Pass the exception as the last argument so the stack trace is logged
  • Pick the level on purpose: ERROR for failures needing attention, WARN for problems handled, INFO for key events, DEBUG for detail you only want when investigating
  • Never log secrets, tokens or personal data
  • Don't use System.out or e.printStackTrace(). They bypass log levels and routing

Configuring Log Levels

You control a package's log level through an OSGi configuration, for example:

org.apache.sling.commons.log.LogManager.factory.config~myproject.cfg.json

{
  "org.apache.sling.commons.log.names": ["com.myproject"],
  "org.apache.sling.commons.log.level": "INFO",
  "org.apache.sling.commons.log.file": "logs/error.log"
}

Put a more verbose level (DEBUG) in a Dev-only folder, such as config.dev, so Production stays quiet and fast:

osgiconfig/config/        → INFO for com.myproject
osgiconfig/config.dev/    → DEBUG for com.myproject

On Cloud Service, plan to send your application logs to the standard error log rather than creating custom log files. Check Adobe's current logging documentation for what's supported on your environment type.

Where to Read Logs

Environment How
Local SDK crx-quickstart/logs/error.log, and the web console's log pages
Cloud Service Download or tail logs through Cloud Manager and the Developer Console. You can also forward logs to a service such as Splunk
AEM 6.5 Log files on the server, the web console, and any custom log files you configured

When you read a log, search for your logger name (for example com.myproject), and look just before the first error. The cause is usually a few lines earlier than the stack trace.

Debugging Locally

Debugging is a local activity on Cloud Service. Start the SDK Quickstart with a Java debug agent:

java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005 -jar aem-sdk-quickstart-*.jar

Then create a remote debug configuration in your IDE pointing at port 5005, set breakpoints in your servlet or service, and trigger the request.

Two tips:

  • Debug against content and configuration that resembles Dev, including run modes. A bug that depends on a configuration value won't show up with the defaults
  • The local SDK is a single instance, so it can't reproduce cluster problems such as a job running twice. Test those on a Dev environment

Unit Testing with AEM Mocks

Fast tests catch most mistakes before deployment. The wcm.io AEM Mocks library provides a simulated AEM context, so you can test services, servlets and Sling Models without a running instance.

@ExtendWith(AemContextExtension.class)
class GreetingServiceImplTest {

    private final AemContext context = new AemContext();

    @Test
    void usesConfiguredGreeting() {
        GreetingService service = context.registerInjectActivateService(
                new GreetingServiceImpl(), Map.of("greeting", "Welcome"));

        assertEquals("Welcome, Sam", service.greet("Sam"));
    }
}

registerInjectActivateService creates the component the way OSGi would: it injects references and calls your @Activate method with the configuration you supply.

Testing a Servlet

@ExtendWith(AemContextExtension.class)
class PageSummaryServletTest {

    private final AemContext context = new AemContext();

    @Test
    void returnsJsonWithPath() throws Exception {
        context.create().resource("/content/myproject/en/home",
                "jcr:primaryType", "cq:Page");
        context.currentResource("/content/myproject/en/home");

        PageSummaryServlet servlet = new PageSummaryServlet();
        servlet.doGet(context.request(), context.response());

        assertEquals("application/json", context.response().getContentType());
        assertTrue(context.response().getOutputAsString().contains("/content/myproject/en/home"));
    }
}

What to test:

  • Logic in services: the cases, the edge cases and the failures
  • Servlet input validation: bad parameters should give 400, not a stack trace
  • Sling Models: with missing properties as well as complete content

What AEM Mocks can't tell you: whether your permissions, run-mode configuration, Dispatcher rules or cluster behavior are right. Those need a real environment.

AEM as a Cloud Service vs 6.5

Topic AEM 6.5 AEM as a Cloud Service
Reading logs On the server, in the console Cloud Manager, the Developer Console and optional log forwarding
Custom log files Allowed Plan around the standard logs
Debug on a deployed environment Possible if you have access Not on Stage or Production. Debug locally
Log level changes Instant in the web console Via configuration in the pipeline. Prefer Dev-only DEBUG
Test coverage Optional Part of the Cloud Manager quality gates, so tests matter more

Common Pitfalls

  • Logging inside a tight loop at INFO. It floods the log and slows the system. Log a summary instead
  • Catching an exception and logging only the message. Pass the exception itself
  • Tests that need a running AEM. Use AEM Mocks and keep them fast
  • Relying on DEBUG in Production. Add the information you need at INFO, or reproduce on Dev with DEBUG
  • Assuming local equals Cloud. Always verify on a Dev environment

Series Wrap-Up

That completes AEM Backend Essentials. You can now:

  • Explain how OSGi bundles and components start, run and stop
  • Wire services together with references, and diagnose wiring problems
  • Configure components per environment with run modes, variables and secrets
  • Access the repository safely with service users, and close resolvers properly
  • Expose functionality with Sling servlets registered by resource type
  • Log, debug and test your backend code

The AEM Backend Advanced series builds on this with factory configurations, filters and listeners, jobs and schedulers, workflows, external integrations, security and the habits that keep code Cloud-ready.

About the author

  • AEM Technical Lead experienced with building and architecting solutions on AEM Sites and Assets. Sharing practical tutorials, real-world implementation lessons, and best practices to help fellow developers and leads navigate AEM.

What's your go-to technique when something works locally but fails on a Cloud Service Dev environment? Logs, remote debugging, a test, or something else?

AEM Backend Essentials

Part 6 of 6

OSGi, services, configuration, service users, servlets and testing: the core backend skills for AEM as a Cloud Service, with 6.5 differences.

Start from the beginningOSGi in AEM: Bundles, Components and the LifecycleSeries: AEM Backend Essentials — Part 1