Open-Source Wikis

/

Ansible

/

Plugins

/

Callback plugins

ansible/ansible

Callback plugins

Callback plugins are event consumers. The TaskQueueManager emits events as the run progresses (play start, task start, task ok, task failed, host unreachable, etc.); each loaded callback plugin can implement zero or more event handlers. Callbacks drive on-screen output, JUnit XML, log files, custom integrations.

Files

ansible-core ships five callbacks:

lib/ansible/plugins/callback/
├── __init__.py     # CallbackBase
├── default.py      # The colored, human-readable output you see when running ansible-playbook
├── minimal.py      # Spartan one-line-per-result
├── oneline.py      # Variant of minimal — used by ansible (ad-hoc)
├── junit.py        # Emit JUnit XML at end of run for CI consumption
└── tree.py         # Write per-host JSON files into a directory tree

Many more live in collections (community.general has slack, mail, syslog_json, profile_tasks, timer, etc.).

CallbackBase

Every callback inherits from lib/ansible/plugins/callback/__init__.py:CallbackBase. Implement only the events you care about:

class CallbackModule(CallbackBase):
    CALLBACK_VERSION = 2.0
    CALLBACK_TYPE = 'stdout'
    CALLBACK_NAME = 'mycorp.simple'

    def v2_runner_on_ok(self, result):
        host = result._host.get_name()
        task = result._task.get_name()
        self._display.display(f"OK: {host} {task}")

    def v2_runner_on_failed(self, result, ignore_errors=False):
        ...

Key class attributes:

  • CALLBACK_VERSION = 2.0 — locks in the v2 event API. Older 1.x callbacks are not loaded.
  • CALLBACK_TYPE'stdout' (only one such callback is loaded; controls main user output), 'aggregate' (multiple allowed; secondary output), 'notification' (e.g., chat integrations).
  • CALLBACK_NAME — the FQCN-style name used in ANSIBLE_STDOUT_CALLBACK config or callbacks_enabled.

Event taxonomy

The full v2 event list (from CallbackBase) covers play, task, runner, and meta events:

Group Events
Play v2_playbook_on_play_start, v2_playbook_on_no_hosts_matched
Task v2_playbook_on_task_start, v2_playbook_on_handler_task_start, v2_playbook_on_include
Runner v2_runner_on_start, v2_runner_on_ok, v2_runner_on_failed, v2_runner_on_unreachable, v2_runner_on_skipped, v2_runner_on_async_poll, v2_runner_on_async_ok, v2_runner_on_async_failed, v2_runner_retry, v2_runner_item_on_ok, v2_runner_item_on_failed, v2_runner_item_on_skipped
Stats v2_playbook_on_stats
File v2_playbook_on_notify, v2_playbook_on_no_hosts_remaining
Setup / Vars v2_playbook_on_vars_prompt
Misc v2_playbook_on_start, v2_playbook_on_setup

Each handler receives a structured event object. The most common is the TaskResult (from lib/ansible/executor/task_result.py), with attributes:

  • _host — the Host object.
  • _task — the Task object.
  • _result — the dict the module returned (or that the action plugin synthesized).
  • is_changed(), is_failed(), is_unreachable(), is_skipped() — predicates.

Multiple stdout callbacks aren't a thing

Only one CALLBACK_TYPE = 'stdout' callback is loaded — the one named in ANSIBLE_STDOUT_CALLBACK (default default). All other loaded callbacks are 'aggregate' or 'notification' and run in parallel.

The callbacks_enabled config option (or CALLBACKS_ENABLED env) controls which non-stdout callbacks are loaded. Many collection callbacks ship disabled by default and require explicit enabling.

How events get to callbacks

graph LR
    Worker[Worker process<br/>TaskExecutor] -->|FinalQueue| TQM[TQM main loop]
    TQM --> ROUTE[Route by event type]
    ROUTE --> ALLCB[For each loaded callback]
    ALLCB -->|hasattr v2_runner_on_ok| CB1[default.py: format and display]
    ALLCB -->|hasattr v2_runner_on_ok| CB2[junit.py: append to XML buffer]
    ALLCB -->|no handler defined| SKIP[skip]

Workers send CallbackSend messages over the result queue rather than calling callbacks directly — callbacks run in the controller process so they share state across hosts/workers.

The default callback

lib/ansible/plugins/callback/default.py is the one you see by default. It does:

  • Colored output: green for ok, red for failed, yellow for changed, magenta for unreachable, cyan for skipped.
  • Per-host suppression of verbose task results when verbosity is 0.
  • The end-of-run "PLAY RECAP" with stats per host.
  • --diff output rendering (when the task result includes a diff field).

Customize via env vars (ANSIBLE_DISPLAY_OK_HOSTS, ANSIBLE_DISPLAY_FAILED_STDERR, ANSIBLE_DISPLAY_SKIPPED_HOSTS, ANSIBLE_NOCOLOR, ANSIBLE_FORCE_COLOR).

minimal vs. oneline

minimal.py is "default minus a lot": no diffing, no banner. Used for cleaner CI output.

oneline.py is one-line-per-result, intended for tools that parse output. The ansible ad-hoc CLI defaults to it.

junit

lib/ansible/plugins/callback/junit.py builds an in-memory XML structure as tasks complete and writes it to disk in v2_playbook_on_stats. Each play becomes a JUnit <testsuite> and each task a <testcase>. Used by CI to surface test results in dashboards.

Configurable via:

  • ANSIBLE_JUNIT_OUTPUT_DIR
  • ANSIBLE_JUNIT_TASK_CLASS (whether to make each task its own class)
  • ANSIBLE_JUNIT_FAIL_ON_CHANGE
  • ANSIBLE_JUNIT_FAIL_ON_IGNORE

tree

lib/ansible/plugins/callback/tree.py writes one JSON file per host into a configured directory. Used for diagnostic and post-run analysis when you want structured per-host output.

Integration points

  • Loaded by: lib/ansible/executor/task_queue_manager.py:TaskQueueManager.load_callbacks().
  • Configured by: ANSIBLE_STDOUT_CALLBACK (single, type=stdout), CALLBACKS_ENABLED / ANSIBLE_CALLBACKS_ENABLED (list, types aggregate+notification).
  • Imported by: anything that wants to write event consumers — but normally the user just installs a collection that ships them.

Entry points for modification

  • A new logging integration — write a callback in a collection. Set CALLBACK_TYPE = 'aggregate' or 'notification'. Implement v2_runner_on_* handlers.
  • Replacing default output — write a 'stdout' callback. Set ANSIBLE_STDOUT_CALLBACK=mycorp.cool_stdout.
  • Adding a new event to the framework requires changes in CallbackBase (default no-op handlers), task_queue_manager.py (dispatch), and probably the strategy plugins (where the event would fire). Don't do this lightly.

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Callback plugins – Ansible wiki | Factory