In OPA, what does a single decision log entry record about a policy decision?
answer
- one JSON record per evaluation
- identifies the query and the answer
- carries the input document verbatim
- names the bundle revision in force
- decision_id ties the record together
basics
~10 sEach OPA decision log entry is a JSON record of one policy evaluation: a unique decision_id, the queried policy path, the input document, the result returned, the bundle revision in force, and a timestamp.
solid answer
~40 sOPA's decision log plugin emits one JSON event per policy evaluation. The event carries a `decision_id` unique to that evaluation, the policy `path` that was queried, the `input` document OPA was given, the `result` it returned, a `bundles` section naming the revision of each loaded bundle, a `timestamp`, and a `labels` block identifying the OPA instance. It is emitted for every decision the server answers, not only denials. Two sinks are configured under `decision_logs`: `console: true` writes each event as JSON on OPA's own log output, while a `service` sink buffers events in memory, batches and compresses them, and POSTs them to an HTTP endpoint. The entry records what OPA was asked and what it answered — not which rule inside the policy produced that answer.
go deeper
Be ready to list the fields from memory: decision_id, the queried path, input, result, bundle revision, timestamp. Say that OPA logs allows as well as denies, and name the console and HTTP service sinks.
Expect to explain why the bundle revision belongs in the record and what the service sink does that the console sink does not — buffering, batching, compression and retry, and the in-memory buffer that implies.
Show that you plan for volume and for content: one event per request on a hot path, and an input field that carries whatever the caller sent. Say what you would do about each before switching the plugin on in production.
Own the position that the decision log is a telemetry stream with an owner, a cost and a data-classification question, and decide deliberately what the organisation is allowed to conclude from it.
## What the decision log plugin is A running OPA answers queries: something hands it an `input` document, OPA evaluates a policy path against it, and OPA returns a result. Nothing about that exchange is durable — the caller sees the answer and moves on. The **decision log plugin** is the part of OPA that turns each of those exchanges into a record and ships it somewhere. It is configured in OPA's configuration file under a `decision_logs` section, and it is off unless you configure it. It is worth being precise about scope: this is OPA's *decision* log, not its diagnostic log. The diagnostic log is OPA telling you about itself — bundle downloads, plugin state, errors. The decision log is OPA telling you what it decided, one entry per evaluation. ## The shape of an entry Each event is a JSON object. The fields you will be asked about: - **`decision_id`** — an identifier unique to this one evaluation. It is what lets a caller that received an answer and an operator reading the log stream agree they are talking about the same decision. - **`path`** — the policy path that was queried, for example `kubernetes/admission/deny`. This is the *query*, not the rule that fired. - **`input`** — the input document verbatim, as OPA received it. This is the field that makes decision logs a data-handling problem as well as an observability feature: whatever the caller sent, the log now contains. - **`result`** — what OPA returned. For a policy whose entry point yields a set of violation messages, that is the set; for a boolean rule, the boolean. - **`bundles`** — the bundles OPA had loaded, each with its **revision**. This pins the answer to a specific version of the policy. - **`timestamp`** — when the decision was made. - **`labels`** — the OPA instance's identity, including its id and version, plus any labels the operator set in configuration. This is how you tell which of forty OPA sidecars produced an entry. - **`metrics`** — timing information, present when metrics are enabled. - **`erased`** — present when a mask policy removed fields, listing the paths it removed. The revision field deserves a second look, because it is the one juniors skip. Policy changes over time. Two entries with the same `path` and an identical `input` can legitimately carry different results if a bundle was activated between them. Without the revision, the log tells you the engine changed its mind and gives you no way to say why. With it, you can go to the exact policy version that produced each answer. ## Every decision, not just the interesting ones A common wrong assumption is that the decision log is a denial log. It is not. OPA emits an entry for evaluations that allow as readily as for ones that deny — the plugin does not know or care which outcome your policy considers bad. That is what makes decision logs useful for questions like "is this rule ever actually hitting anything?", and it is also why the volume can be surprising: a busy admission or authorization path produces one entry per request. ## The two sinks **Console.** Setting `console: true` makes OPA write each event as JSON on its own log output. In a container this is the cheapest possible integration — whatever already collects stdout and stderr collects your decisions too. It is also the sink with the fewest guarantees of its own: there is no batching, no retry, and delivery is entirely the surrounding log pipeline's problem. **HTTP service.** Pointing `decision_logs.service` at a service defined in OPA's `services` block makes the plugin buffer events in memory, batch them, compress them, and POST them to that service. It backs off and retries on failure. This is the production shape, and it introduces the property that matters most for anyone treating the log as a record: the buffer is *in memory and bounded*. Events sit there between the decision and the upload. Both can be enabled at once — a common setup during a rollout, where console output is what an engineer greps locally and the service sink is what feeds the central pipeline. ## What an entry does not tell you It records the question and the answer. It does not record the reasoning: which rule body was satisfied, which expression was undefined, which of five violation conditions matched. If you need that, the decision log is the pointer — the `decision_id`, the `input`, the bundle revision — not the explanation.
- Where can OPA send decision log events, and how do the two sinks differ?Setting `console: true` writes each event as JSON on OPA's own log output, so an existing container log collector picks it up with no extra plumbing and no batching or retry of its own. Pointing `decision_logs.service` at an HTTP service makes OPA buffer events in memory, batch and compress them, POST them, and retry with backoff. Both can be enabled at the same time.
- Why does an entry carry the bundle revision?It pins the answer to a specific version of the policy. If a bundle is activated at 14:00, two entries with the same path and the same input can legitimately differ before and after — the revision is what tells you which ruleset produced each result. Without it you can see the engine change its mind but not attribute the change.
- Does OPA log an entry when the policy allows the request?Yes. The plugin emits one event per evaluation the server answers, regardless of outcome; it has no notion of which result your policy considers a failure. That is why it can answer questions like whether a rule ever matches anything, and also why volume on a busy admission path is one event per request.
It is a till receipt, not a security camera: it shows what was asked for, what was handed back, at what time and under which price list — but not the clerk's reasoning.
saying these in an interview costs you the question
- Says decision logs only record denials
- Thinks the entry names which rule fired
- Assumes OPA persists decision logs to disk
- Confuses decision logs with OPA's diagnostic logs
- Ignores the bundle revision in the entry