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

# Monitoring

> Instrument your Forest back-end and database so slowdowns and errors surface before your operators report them

The Forest back-end runs in your infrastructure. The Forest UI calls it directly from your operators' browsers, and your data never transits through Forest servers. Forest therefore has no view of what happens inside your back-end or your database: that visibility sits on your side.

## Who monitors what

| Layer | Owner | What to watch |
| - | - | - |
| Forest UI and Forest servers | Forest | Availability, UI performance |
| Forest back-end (`/forest/*`, MCP server) | You | Latency, error rate, resources |
| Database | You | Slow queries, load generated by Forest |
| External APIs called by the back-end | You | Latency of computed fields and actions |

## Where to look when operators report slowness

Compare the back-end latency in your APM with the time the same request takes in the browser (DevTools, **Network** tab):

| APM latency | UI latency | Most likely cause | Next step |
| - | - | - | - |
| Slow | Slow | Database or queries | [Track the top Forest queries](#track-the-top-forest-queries-postgresql) |
| Fast | Slow | Network between the browser and the back-end: VPN, proxy, region | Check the network path from the operators' location |
| Fast | Fast | Forest UI | Contact Forest support with the request timings |

## Monitor the Forest endpoints

Install an APM on the Forest back-end: Datadog, Sentry, New Relic, or OpenTelemetry. Measure latency (P50, P95, P99) and 5xx rate **per collection and per operation type**, not only per route. A slow `list` on one large collection and a slow chart require different fixes.

| Operation type | Route |
| - | - |
| `list` | `GET /forest/{collection}`, `GET /forest/{collection}/{id}/relationships/{relation}` |
| `count` | `GET /forest/{collection}/count` |
| `search` | `list` route with a `search` query parameter |
| `chart` | `POST /forest/stats/{collection}` (charts configured in the UI), `/forest/_charts/{chart}` and `/forest/_charts/{collection}/{chart}` (charts defined in code, `GET` and `POST`) |
| `action` | `POST /forest/_actions/{collection}/{index}/{slug}`, or `POST /forest/_actions/{collection}/{slug}` with `useUnsafeActionEndpoint`; action forms call the same path plus `/hooks/load`, `/hooks/change` or `/hooks/search` |
| `export` | `GET /forest/{collection}.csv` |

In your APM, derive the collection and the operation type from these patterns, and attach both as tags. With a `prefix` option, the routes start with `/{prefix}/forest`: for example, `prefix: 'admin'` gives `/admin/forest/{collection}`.

Also track:

* **Resources:** CPU, memory, restarts and out-of-memory (OOM) events of the process or container.
* **External calls:** trace calls to external APIs as spans, and track the P95 of every computed field or action that calls one.

### Track MCP traffic separately

AI agents call the MCP server in bursts: dozens of requests within seconds, then nothing. Its endpoint is `/mcp`, or `<basePath>/mcp` when you mount it with a [`basePath`](/reference/agent-api/nodejs). Keep it in a dedicated dashboard with its own alerts, so these bursts neither trigger your operator alerts nor hide inside their averages.

### Send structured logs

Pass a custom logger to the Forest back-end so its logs reach your log platform as JSON. The Node.js back-end already logs one line per request, with the status, method, path and duration, for example `[200] GET /forest/users - 42ms`.

<Tabs>
  <Tab title="Node.js">
    ```javascript theme={null}
    const agent = createAgent({
      ...options,
      logger: (level, message, error) => {
        // Errors usually come as the third argument; some calls pass them as the second.
        const err = error ?? (message instanceof Error ? message : undefined);
        console.log(JSON.stringify({
          timestamp: new Date().toISOString(),
          service: 'forest-backend',
          level,
          message: message instanceof Error ? message.message : message,
          error: err?.stack,
        }));
      },
    });
    ```
  </Tab>

  <Tab title="Ruby">
    ```ruby theme={null}
    ForestAdminRails.configure do |config|
      config.logger = ->(level, message) {
        Rails.logger.add(
          level,
          { timestamp: Time.now.utc.iso8601, service: 'forest-backend', level: Logger::SEV_LABEL[level], message: message }.to_json
        )
      }
    end
    ```

    The back-end passes `level` as a `Logger` severity integer, and re-evaluates the lambda from its source: reference only constants and globals such as `Rails.logger` inside it, not local variables.
  </Tab>
</Tabs>

## Set alerts

Set thresholds per operation type, and fire an alert only when the condition lasts. A single slow request is not an incident.

| Alert | Starting threshold |
| - | - |
| P95 of `list`, `count`, `search` | Above 2 s for 10 minutes |
| P95 of `chart` | Above 5 s for 15 minutes |
| P95 of `action` | Above 5 s for 10 minutes |
| 5xx rate on any Forest route, MCP server included | Above 1% for 5 minutes |
| External probe on `/forest` (or `/{prefix}/forest`) | 3 consecutive failures, checked every minute |

Exclude `export` from latency alerts: CSV exports run long by design.

The probe calls the root Forest route, `/forest` or `/{prefix}/forest` if you set a `prefix`:

```bash theme={null}
curl -fsS https://your-backend.yourcompany.com/forest
```

<Warning>
  A successful probe confirms that the back-end process answers. It does not test the database connection: monitor database availability separately.
</Warning>

<Info>
  These thresholds are starting points. Adjust them once you have a baseline for your project.
</Info>

## Monitor the database

### Isolate Forest traffic

Connect the Forest back-end with a dedicated database user, and set `application_name` in the connection string. Both let your monitoring tools and `pg_stat_activity` filter Forest queries from the rest of your traffic.

```text theme={null}
postgres://forest_backend:<password>@db.internal:5432/app?application_name=forest-backend
```

Add a statement timeout on that user, so that a single Forest query cannot degrade production:

```sql theme={null}
ALTER ROLE forest_backend SET statement_timeout = '30s';
```

The setting applies to new sessions only: restart the Forest back-end, or recycle its database connection pool, so pooled connections pick it up.

<Warning>
  A 30-second timeout interrupts CSV exports and charts over large tables that legitimately run longer. Measure your longest expected queries first, then set the timeout above them, or serve exports and heavy charts from a read replica with a higher limit.
</Warning>

Run the Forest back-end in the same region as the database, with a connection pool sized for the expected load.

### Enable the slow query log

| Database | Setting |
| - | - |
| PostgreSQL | `log_min_duration_statement = 1000` (ms), lowered to `500` once a baseline exists |
| MySQL / MariaDB | `slow_query_log = ON`, `long_query_time = 1` |
| AWS RDS | Enable Performance Insights |
| MongoDB | `db.setProfilingLevel(1, { slowms: 100 })` |

### Track the top Forest queries (PostgreSQL)

On PostgreSQL 13 or later, add `pg_stat_statements` to `shared_preload_libraries`, restart PostgreSQL, then run `CREATE EXTENSION pg_stat_statements;`. List the 10 most expensive queries sent by the Forest user:

```sql theme={null}
SELECT calls,
       round(total_exec_time) AS total_ms,
       round(mean_exec_time)  AS mean_ms,
       left(query, 120)       AS query
FROM pg_stat_statements
WHERE userid = 'forest_backend'::regrole
ORDER BY total_exec_time DESC
LIMIT 10;
```

Run `EXPLAIN ANALYZE` on each of them. Look for sequential scans on large tables and for row estimates far from actual counts. To fix what you find, see [Performance](/guides/best-practices/performance).

## Capture a baseline and review it

* **Before go-live:** record the P95 per collection and operation type, and the top 10 queries. This baseline is the reference that proves a fix works.
* **Every week:** review the top 10 queries with your engineering team.
* **After every schema migration:** compare against the baseline. Missing indexes on new foreign keys or filtered columns cause most regressions.

## Related

<CardGroup cols={2}>
  <Card title="Performance" icon="gauge" href="/guides/best-practices/performance">
    Patterns to speed up computed fields, segments and filters.
  </Card>

  <Card title="Troubleshooting" icon="wrench" href="/guides/best-practices/troubleshooting">
    Common issues and how to fix them.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.