---
title: "App Logs"
slug: "app-logs"
updated: 2026-02-23T04:05:06Z
published: 2026-02-23T04:05:06Z
---

> ## Documentation Index
> Fetch the complete documentation index at: https://developer.rocket.chat/llms.txt
> Use this file to discover all available pages before exploring further.

# App Logs

App logs can be viewed from the app’s **Logs** tab. Here, the app’s activities are displayed, allowing admins to analyze what happened and debug issues. For an overview of the types of logs available, refer to the [App Development Lifecycle](/v1/docs/app-development-lifecycle) document.

In this document, we will learn how to:

- Access the logger correctly.
- Structure slash commands, endpoints, and helper classes.
- Handle the special case of scheduler processors.

## How to access the logger

The **ILogger** interface is used to log events at different levels to the system and the database. It is automatically imported into your app’s main class. Refer to the [Rocket.Chat Apps TypeScript Definition](https://rocketchat.github.io/Rocket.Chat.Apps-engine/interfaces/accessors_ILogger.ILogger.html) for details on the interface’s methods and properties.

Let’s learn how to access the logger in different cases.

> [!WARNING]
> Rocket.Chat version **8.2.0** fixes an issue where app logs were lost in nested requests. The [PR #38374](https://github.com/RocketChat/Rocket.Chat/pull/38374) ensures that the logger is persisted throughout the request handling with no mix-ups. This creates some requirements for app developers that should be kept in mind. This document guides you on how to access the logs correctly.

### Inside the `App` subclass

The following code snippet shows you how to access the logger:

```typescript
import {
    IAppAccessors,
    ILogger,
} from '@rocket.chat/apps-engine/definition/accessors';
import { App } from '@rocket.chat/apps-engine/definition/App';
import { IAppInfo } from '@rocket.chat/apps-engine/definition/metadata';

export class HelloWorldApp extends App {
    constructor(info: IAppInfo, logger: ILogger, accessors: IAppAccessors) {
        super(info, logger, accessors);
        this.getLogger().debug('some_message', extraData);
        this.getLogger().error('something_went_wrong', { err });
    }
}
```

Use `this.getLogger()` from within your `App` subclass. Call the logger only when you need it. For example:

```typescript
// ✅ Recommended
export class MyApp extends App {
	public async initialize(/* ... */) {
		this.getLogger().debug('init');
	}
}
```

The engine will provide a correctly scoped logger for every request.

**Do not** cache the logger across executions as shown in the following code snippet:

```typescript
// ❌ Anti‑pattern: caching the logger instance
export class MyApp extends App {
	private logger = this.getLogger();

	public async initialize(/* ... */) {
		// This may use a logger from a different request context
		this.logger.debug('init');
	}
}
```

### Inside external classes

External classes refer to the classes that don’t extend `App`, such as slash commands, API endpoints, providers, and helpers. In these classes, the logger must be called in the following way:

- Keep a reference to the app object internally.
- Use that reference to obtain the logger.

The following sample code snippet shows the recommended pattern:

```typescript
// ✅ Recommended
import type { App } from '@rocket.chat/apps-engine/definition/App';
import type { IHttp, IModify, IPersistence, IRead } from '@rocket.chat/apps-engine/definition/accessors';
import type { ISlashCommand, ISlashCommandPreview, ISlashCommandPreviewItem, SlashCommandContext } from '@rocket.chat/apps-engine/definition/slashcommands';

export class MySlashcommand implements ISlashCommand {
	/* Required properties omitted for brevity */

	constructor(private readonly app: App) {} // This is the reference

	public async executor(context: SlashCommandContext, read: IRead, modify: IModify, http: IHttp, persis: IPersistence): Promise<void> {
		this.app.getLogger().debug('Hello!'); // Here the reference is called for logging
	}
}
```

Here, `MySlashcommand` is a separate class that implements `ISlashCommand`. It does not extend `App`. To reach the logger, it uses a reference to the app instance it stores internally. Keeping the app reference (`this.app`) allows the runtime to replace that app with a request-scoped proxy when executing your code, so that `this.app.getLogger()` always maps to the right logger.

**Do not** capture the logger itself at the time of construction as shown in the following example:

```typescript
// ❌ Anti‑pattern
export class MySlashcommand implements ISlashCommand {
	/* Required properties omitted for brevity */
	private logger: ILogger;

	constructor(app: App) {
		this.logger = app.getLogger();
	}

	public async executor(context: SlashCommandContext, read: IRead, modify: IModify, http: IHttp, persis: IPersistence): Promise<void> {
		this.app.getLogger().debug('Hello!');
	}
}
```

### Endpoints, outbound providers, and other composable objects

Any “provider” object that the engine executes for you (API endpoints, slash command classes, outbound communication providers, etc.) should follow the same pattern:

- Keep a reference to the app.
- Log via `this.app.getLogger()`.

The following code snippet shows an example:

```typescript
import type {
	ApiEndpoint,
	IApiRequest,
	IApiResponse,
} from '@rocket.chat/apps-engine/definition/api';
import type { App } from '@rocket.chat/apps-engine/definition/App';

export class MyEndpoint implements ApiEndpoint {
	public path = 'my-endpoint';

	constructor(private readonly app: App) {} // The reference to App

	public async get(request: IApiRequest): Promise<IApiResponse> {
		this.app.getLogger().debug('my_endpoint_get_called', { query: request.query }); // The reference is called for logging

		return {
			status: 200,
			content: { ok: true },
		};
	}
}
```

### Scheduler processors

Scheduler processors are a special case for logging because of how they are registered. Take a look at the following example:

```typescript
public async extendConfiguration(configuration: IConfigurationExtend) {
	configuration.scheduler.registerProcessors([
		{
			id: 'first',
			// `this` is lexically bound at definition time, not set by the runtime
			processor: async (jobData) => this.getLogger().debug(`[${ new Date() }] this is a task`, jobData),
		},
	]);
}
```

It is common to register the processor function as an arrow function. But this poses a challenge for us to inject the correct app reference to its execution. In this case, you have two options for logging:

1. Register a reference to a method inside your `App` class without binding `this`. This is the most ergonomic option. Refer to the following code sample:

```typescript
export class SimpleMessageEndpointApp extends App {
	public async extendConfiguration(configuration: IConfigurationExtend) {
		configuration.scheduler.registerProcessors([
			{
				id: 'MyProcessor',
				// NOTE: make sure NOT TO `.bind(this)` !
				processor: this.myProcessorHandler,
			},
		]);
	}

	private async myProcessorHandler(jobData): Promise<void> {
		this.getLogger().debug('Hello there!')
	}
}
```

1. The second option is to register a regular function, either named or anonymous, and use this as a reference to your `App` instance (which the runtime injects later on). While this is possible, it may be confusing for eventual maintenance. Refer to the following code sample:

```typescript
public async extendConfiguration(configuration: IConfigurationExtend) {
	configuration.scheduler.registerProcessors([
		{
			id: 'MyProcessor',
			processor: async function MyProcessorHandler(jobData) {
				this.getLogger().debug(`[${ new Date() }] this is a task`, jobData);
			},
		},
	]);
}
```
