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

# Control Plane Logs

> Audit Control Plane API calls, understand log sources, and permission who can view logs

export const G = ({term, anchor, children}) => {
  const href = anchor ? `/docs/glossary/index#${anchor}` : `/docs/glossary/index`;
  return <a href={href} className="glossary-link" style={{
    textDecoration: "underline",
    textDecorationLine: "underline",
    textDecorationColor: "#6b7280",
    textDecorationThickness: "1px",
    textUnderlineOffset: "2px",
    color: "inherit",
    transition: "text-decoration-color 0.2s ease",
    borderBottom: "none"
  }} onMouseEnter={e => e.target.style.textDecorationColor = "#fff"} onMouseLeave={e => e.target.style.textDecorationColor = "#6b7280"}>
  {children || term}
</a>;
};

> **Outcome:** You can find Control Plane audit logs by source, search them, and restrict who can view logs.
> **Prerequisites:** Access to the Formal console, or an API key with Logs access. See [Permissions](/docs/guides/core-concepts/permissions).

## Overview

Every authenticated request to Formal's <G anchor="control-plane">Control Plane</G> is written as an audit log. That includes calls from the <G anchor="formal-console">Formal console</G>, the [public API](/docs/api/introduction), Terraform, and machine auth such as [AWS SigV4](/docs/api/aws-sigv4-authentication).

Control Plane logs share the same Logs experience as Connector, Endpoint, and workflow activity. Use the **Source** filter to switch streams. See [Logs](/docs/guides/observability/logs) for search, aggregations, export, and retention.

## Log Sources

Formal tags every log with a `source` field. Choose a source in the Formal console, or filter with Quickwit (`source:control-plane`).

| Source            | `source` value  | What it records                                                             | Typical producers                                                    |
| ----------------- | --------------- | --------------------------------------------------------------------------- | -------------------------------------------------------------------- |
| **Connector**     | `connector`     | Queries, commands, and sessions through Formal Connectors                   | Connectors proxying Postgres, MySQL, SSH, HTTP, Kubernetes, and more |
| **Endpoint**      | `desktop`       | Traffic through the [Formal Endpoint](/docs/guides/client-apps/desktop-app) | Endpoint users (for example LLM or HTTP resources)                   |
| **Control Plane** | `control-plane` | Authenticated Control Plane API calls                                       | Console, public API, Terraform, SDKs, machine users                  |
| **Workflow**      | `workflow`      | Workflow runs and step activity                                             | [Workflows](/docs/guides/configuration/workflows)                    |

<Note>
  Prefer `source` over `event_type` when filtering Control Plane activity.
  Control Plane logs use `source:control-plane` and fields under
  `control_plane_request.*`. Connector and Endpoint logs use `event_type`
  values such as `request` or `session-start`.
</Note>

[Log configurations](/docs/guides/core-concepts/connectors/logs) (encryption and policy-input retention) apply to Connector and Endpoint sources only. They do not change Control Plane API audit logs.

## What Gets Logged

Formal records a Control Plane log for each authenticated API call after the handler returns. Unauthenticated traffic (for example health checks) is not logged.

Each entry includes:

| Field             | Description                                                                                                               |
| ----------------- | ------------------------------------------------------------------------------------------------------------------------- |
| **Timestamp**     | When the API call started                                                                                                 |
| **User**          | Formal user (email, id, type, groups). Machine users may include AWS principal fields                                     |
| **API key**       | API key id and name when the caller used an API key                                                                       |
| **Command**       | RPC or handler name (for example `UpdatePolicy`) and type (`create`, `read`, `update`, `delete`, or `other`)              |
| **Service**       | Service that handled the call (for example `PoliciesService`, `LogsService`)                                              |
| **Method**        | HTTP method (`GET`, `POST`, and so on)                                                                                    |
| **URL**           | Connect procedure path or HTTP path                                                                                       |
| **Arguments**     | Request payload. Fields marked sensitive in the API schema are redacted                                                   |
| **IP address**    | Client IP                                                                                                                 |
| **Duration**      | End-to-end request duration                                                                                               |
| **Policy action** | Whether Control Plane [permissions](/docs/guides/core-concepts/permissions) allowed the call: `allow`, `deny`, or `error` |
| **Error**         | Error message when the call failed                                                                                        |

`control_plane_request.policy_action` audits whether **that API call** was allowed. It is separate from who may **view** logs later (see [Permissions for Viewing Logs](#permissions-for-viewing-logs)).

## View Control Plane Logs

<Steps>
  <Step title="Open Logs">
    Go to [Logs](https://app.formal.ai/logs) in the Formal console.
  </Step>

  <Step title="Select Control Plane">
    Set **Source** to **Control Plane**. The table columns switch to command, service, user, method, and policy action.
  </Step>

  <Step title="Inspect an entry">
    Click a row to open the details panel. Review arguments, identity, and any error.
  </Step>
</Steps>

**Verify:** Run a Control Plane change (for example update a policy), then confirm a matching row appears under **Source → Control Plane**.

## Search Examples

```text theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
# All Control Plane audit logs
source:control-plane

# Denied Control Plane API calls
source:control-plane AND control_plane_request.policy_action:deny

# Updates by a specific user
source:control-plane AND control_plane_request.command.type:update AND control_plane_request.user.email:alice@example.com

# Calls against the Policies service
source:control-plane AND control_plane_request.service:PoliciesService

# Machine users authenticated with AWS SigV4
source:control-plane AND control_plane_request.user.type:machine AND control_plane_request.user.aws_account_id:123456789012
```

### Control Plane Field Reference

| Field                                          | Type      | Description                                      |
| ---------------------------------------------- | --------- | ------------------------------------------------ |
| `source`                                       | String    | Always `control-plane` for these logs            |
| `timestamp`                                    | DateTime  | When the call started (RFC3339)                  |
| `control_plane_request.command.name`           | String    | API command (for example `UpdatePolicy`)         |
| `control_plane_request.command.type`           | String    | `create`, `read`, `update`, `delete`, or `other` |
| `control_plane_request.service`                | String    | Service name                                     |
| `control_plane_request.method`                 | String    | HTTP method                                      |
| `control_plane_request.url`                    | String    | Procedure or path                                |
| `control_plane_request.user.email`             | String    | Caller email                                     |
| `control_plane_request.user.id`                | String    | Caller user id                                   |
| `control_plane_request.user.type`              | String    | `human` or `machine`                             |
| `control_plane_request.user.groups`            | \[]String | Caller groups                                    |
| `control_plane_request.user.aws_principal_arn` | String    | AWS principal ARN (SigV4 machine auth)           |
| `control_plane_request.user.aws_account_id`    | String    | AWS account ID (SigV4 machine auth)              |
| `control_plane_request.api_key.id`             | String    | API key id when present                          |
| `control_plane_request.api_key.name`           | String    | API key name when present                        |
| `control_plane_request.ip_address`             | String    | Client IP                                        |
| `control_plane_request.policy_action`          | String    | `allow`, `deny`, or `error`                      |
| `control_plane_request.duration`               | String    | Request duration                                 |
| `control_plane_request.args`                   | Object    | Redacted request payload                         |
| `control_plane_request.error`                  | String    | Error message when the call failed               |

## Permissions for Viewing Logs

There is no separate permission per log source. Access to Connector, Desktop, Control Plane, and workflow logs is gated by the **Logs** application in Control Plane [permissions](/docs/guides/core-concepts/permissions).

If a user cannot open [Logs](https://app.formal.ai/logs) or call Logs APIs, they cannot view Control Plane audit logs in Formal. Formal returns HTTP 403 for blocked requests.

### Restrict Logs to Security Admins

```Rego theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
package formal.app

import future.keywords.in
import future.keywords.if

default allow = true

allow := false if {
  input.app.name == "Logs"
  not "security-admins" in input.user.groups
}
```

Users outside `security-admins` cannot list or manage logs. Everyone else keeps allow-by-default access to other applications.

### Allow Read-Only Logs Access

```Rego theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
package formal.app

import future.keywords.in
import future.keywords.if

default allow = false

# Security admins: full access
allow if {
  "security-admins" in input.user.groups
}

# Auditors: read Logs only
allow if {
  input.app.name == "Logs"
  input.app.command.type == "read"
  "auditors" in input.user.groups
}
```

Auditors can search and view logs (`List`, `Get`) but cannot change log configurations or related write commands.

### Useful Logs Commands

| Goal                           | `input.app.name` | Example `input.app.command.name`                   | `input.app.command.type` |
| ------------------------------ | ---------------- | -------------------------------------------------- | ------------------------ |
| Search or open a log           | `Logs`           | `ListLogsV2`, `GetLog`                             | `read`                   |
| Natural-language search        | `Logs`           | `GetLogsFromNaturalLanguage`                       | `read`                   |
| Aggregations                   | `Logs`           | `ListLogAggregations`, `GetGroupByAggregation`     | `read`                   |
| Manage retention / log configs | `Logs`           | `CreateLogConfiguration`, `UpdateLogConfiguration` | `create` / `update`      |

<Warning>
  Permissions evaluate the **Logs** application as a whole. They do not filter
  by `source`. A user who can read Logs can query Control Plane, Connector,
  Desktop, and workflow logs (subject to any query they run).
</Warning>

## Forwarding Control Plane Logs

When you configure a [Log Integration](/docs/guides/integrations/log), Formal forwards Control Plane logs with every other source.

For object storage (S3, GCS, Azure Blob), Control Plane objects use this key prefix:

```text theme={"languages":{"custom":["/languages/cel.json","/languages/rego.json"]}}
formal/logs/control-plane/{year}/{month}/{day}/{file}
```

Other sources use the same layout with `connector`, `desktop`, or `workflow` in place of `control-plane`.

## Best Practices

<AccordionGroup>
  <Accordion title="Monitor denied Control Plane calls" icon="ban">
    Alert on `control_plane_request.policy_action:deny` in Formal or your SIEM.
    Spikes often mean misconfigured permissions or probing.
  </Accordion>

  <Accordion title="Limit who can view Logs" icon="user-lock">
    Restrict the **Logs** application to security or audit groups. Control Plane
    logs include API arguments and identity context.
  </Accordion>

  <Accordion title="Pair with change control" icon="clipboard-check">
    Use Control Plane logs to audit who changed policies, connectors, and users.
    Filter by `control_plane_request.command.type:update` or `:delete`.
  </Accordion>

  <Accordion title="Forward for long-term retention" icon="pipe">
    Keep console search for day-to-day work. Forward to S3 or your SIEM for
    multi-year compliance archives.
  </Accordion>
</AccordionGroup>

## Next Steps

<CardGroup cols={2}>
  <Card title="Logs" icon="file-lines" href="/docs/guides/observability/logs">
    Search, aggregations, export, and retention
  </Card>

  <Card title="Permissions" icon="shield-check" href="/docs/guides/core-concepts/permissions">
    Configure OPA access to Formal applications
  </Card>

  <Card title="Log Integration" icon="pipe" href="/docs/guides/integrations/log">
    Forward logs to S3, Datadog, or Splunk
  </Card>

  <Card title="Logs API" icon="code" href="/docs/api/introduction">
    Query logs with the public API (`LogsService`)
  </Card>
</CardGroup>
