Introduction

Callback plugins customize what Ansible does during execution — change output format, profile task timing, send notifications on failure, or integrate with monitoring. They hook into events like task start, task completion, play end, and runner failure.

Built-in Callbacks

Change Output Format

# ansible.cfg
[defaults]
stdout_callback = yaml
CallbackDescription
defaultStandard output
yamlYAML-formatted (most readable)
jsonJSON output
denseMinimal one-line per task
debugVerbose debug output
minimalBare minimum
nullNo output
treeSave output per host to files
unixyUnix-style condensed

Enable Additional Callbacks

[defaults]
stdout_callback = yaml
callbacks_enabled = timer, profile_tasks, profile_roles

Profile Tasks (Performance)

# ansible.cfg
[defaults]
callbacks_enabled = timer, profile_tasks

Output shows timing for every task:

PLAY RECAP ****
Monday 12 April 2026  14:30:00 +0000 (0:00:05.123)  0:01:23.456 ***

===============================================================================
Install packages ------------------------------------------ 45.23s
Deploy configuration -------------------------------------- 12.45s
Start services -------------------------------------------- 5.12s
Gather facts ---------------------------------------------- 3.89s

profile_roles

callbacks_enabled = profile_roles

Shows total time per role:

Role                          Time
nginx                         23.45s
myapp                         45.12s
monitoring                    12.33s

Notification Callbacks

Slack Notification

# ansible.cfg
[defaults]
callbacks_enabled = community.general.slack

[callback_slack]
webhook_url = https://hooks.slack.com/services/T00/B00/xxx
channel = #ansible
username = ansible-bot

Email on Failure

[defaults]
callbacks_enabled = community.general.mail

[callback_mail]
to = ops@example.com
sender = ansible@example.com
smtphost = smtp.example.com

Log to Syslog

[defaults]
callbacks_enabled = community.general.syslog_json

Log to File (JSON)

[defaults]
callbacks_enabled = community.general.log_plays
log_path = /var/log/ansible/plays.log

Custom Callback Plugin

# callback_plugins/custom_notify.py
from ansible.plugins.callback import CallbackBase

class CallbackModule(CallbackBase):
    CALLBACK_VERSION = 2.0
    CALLBACK_TYPE = 'notification'
    CALLBACK_NAME = 'custom_notify'
    CALLBACK_NEEDS_ENABLED = True

    def v2_runner_on_failed(self, result, ignore_errors=False):
        if not ignore_errors:
            host = result._host.get_name()
            task = result._task.get_name()
            msg = f"FAILED: {task} on {host}"
            # Send to your notification system
            self._display.warning(msg)

    def v2_playbook_on_stats(self, stats):
        hosts = sorted(stats.processed.keys())
        failures = False
        for h in hosts:
            summary = stats.summarize(h)
            if summary['failures'] > 0 or summary['unreachable'] > 0:
                failures = True
        
        if failures:
            self._display.warning("PLAYBOOK HAD FAILURES!")
            # Send alert

    def v2_runner_on_ok(self, result):
        pass  # Task succeeded

    def v2_runner_on_skipped(self, result):
        pass  # Task skipped
# ansible.cfg
[defaults]
callback_plugins = ./callback_plugins
callbacks_enabled = custom_notify

Event Hooks

HookFires When
v2_playbook_on_startPlaybook begins
v2_playbook_on_play_startPlay begins
v2_playbook_on_task_startTask begins
v2_runner_on_okTask succeeds
v2_runner_on_failedTask fails
v2_runner_on_skippedTask skipped
v2_runner_on_unreachableHost unreachable
v2_playbook_on_statsPlaybook ends (summary)
v2_on_anyAny event

Practical Patterns

CI/CD with JSON Output

# Machine-readable output for CI
ANSIBLE_STDOUT_CALLBACK=json ansible-playbook site.yml > output.json
# Parse results
import json
with open('output.json') as f:
    results = json.load(f)
    for play in results['plays']:
        for task in play['tasks']:
            for host, result in task['hosts'].items():
                if result.get('failed'):
                    print(f"FAILED: {task['task']['name']} on {host}")

Performance Debugging

# Find slow tasks
[defaults]
callbacks_enabled = timer, profile_tasks, profile_roles
# Sort tasks by duration
ansible-playbook site.yml 2>&1 | grep -E "^[A-Za-z].*---" | sort -t'-' -k2 -rn

ARA (Ansible Run Analysis)

pip install ara

# ansible.cfg
[defaults]
callbacks_enabled = ara_default

ARA provides a web UI to browse and analyze playbook runs.

Troubleshooting

Callback Not Loading

# List available callbacks
ansible-doc -t callback -l

# Check specific callback
ansible-doc -t callback yaml

# Verify callbacks_enabled in config
ansible-config dump | grep callback

Permission Denied on Custom Plugin

Ensure the file is readable and the directory is in callback_plugins path:

[defaults]
callback_plugins = ./callback_plugins

Conclusion

Callback plugins customize Ansible's behavior during execution. Use yaml for readable output, profile_tasks to find slow tasks, and notification callbacks for alerting. For CI/CD, use json output for machine parsing. Custom callbacks let you integrate with any monitoring or notification system. Always enable timer and profile_tasks in production to track performance over time.