# 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:

```java
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`

```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:

```plaintext
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:

```bash
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**](http://wcm.io) **AEM Mocks** library provides a simulated AEM context, so you can test services, servlets and Sling Models without a running instance.

```java
@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

```java
@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/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.
