When running ansible-playbook
, you might notice task outputs appearing as hard-to-read JSON blobs instead of the clean, line-by-line format you'd expect from standard streams. This happens because Ansible by default wraps command output in its result dictionary structure.
# Example of problematic output
TASK [Run database migration] **************************************************
ok: [webserver1]
{
"changed": true,
"cmd": "python manage.py migrate",
"delta": "0:00:02.345678",
"stdout": "Operations to perform:\n Apply all migrations: admin, auth, contenttypes, sessions\nApplying contenttypes.0001_initial... OK",
"stderr": "Warning: Debug mode is on\n",
"stderr_lines": ["Warning: Debug mode is on"],
"stdout_lines": [
"Operations to perform:",
" Apply all migrations: admin, auth, contenttypes, sessions",
"Applying contenttypes.0001_initial... OK"
]
}
Here are several approaches to improve output readability:
1. Using stdout_callback
Edit your ansible.cfg
:
[defaults]
stdout_callback = debug
# or for more compact output:
# stdout_callback = yaml
2. Per-task Debugging with register
For specific tasks where you need clean output:
- name: Run database migrations
command: python manage.py migrate
register: migrate_output
changed_when: false
- name: Display clean migration output
debug:
msg: "{{ migrate_output.stdout_lines }}"
3. Using the 'debug' Module for Critical Output
- name: Get system information
shell: uname -a
register: system_info
- debug:
var: system_info.stdout
For production environments, consider these configurations:
[defaults]
# For better error visibility
display_failed_stderr = true
# For multi-line output handling
display_skipped_hosts = false
display_ok_hosts = false
Here's how to handle output in a Django deployment playbook:
- name: Collect static files
command: python manage.py collectstatic --noinput
register: collectstatic_out
changed_when: "'0 static files copied' not in collectstatic_out.stdout"
- name: Show collectstatic output
debug:
msg: "{{ collectstatic_out.stdout_lines | join('\n') }}"
To properly format error streams:
- name: Run critical process
command: /opt/app/start.sh
register: app_start
ignore_errors: yes
- name: Show application errors
debug:
msg: "{{ app_start.stderr_lines | default([]) | join('\n') }}"
when: app_start.failed
When running ansible-playbook foo.yaml
, you've likely encountered output like this:
TASK [Django: Create superuser] *********************
fatal: [lorem]: FAILED! => {"changed": false, "cmd": "python3 -m django createsuperuser\n --noinput\n --username \"admin\"\n --email \"admin@example.com\"", "msg": "\n:stderr: CommandError: You must use --full_name with --noinput.\n", ...}
This unreadable JSON blob occurs because Ansible by default wraps command output in its result object structure.
Add these configurations to your ansible.cfg
:
[defaults]
stdout_callback = yaml
bin_ansible_callbacks = True
display_failed_stderr = True
Or set these environment variables:
export ANSIBLE_STDOUT_CALLBACK=yaml
export ANSIBLE_DISPLAY_FAILED_STDERR=True
For specific tasks, use these YAML attributes:
- name: Run script with clean output
command: /path/to/script.sh
register: script_output
changed_when: false
no_log: false
- name: Display clean output
debug:
msg: "{{ script_output.stdout_lines }}"
Install and configure the debug
callback plugin:
[defaults]
stdout_callback = debug
Or create a custom callback plugin by placing this in callback_plugins/clean_output.py
:
from ansible.plugins.callback import CallbackBase
class CallbackModule(CallbackBase):
CALLBACK_VERSION = 2.0
CALLBACK_NAME = 'clean_output'
def v2_runner_on_failed(self, result, **kwargs):
self._display.display("FAILED: " + result._result.get('msg', ''))
def v2_runner_on_ok(self, result):
if 'stdout' in result._result:
self._display.display(result._result['stdout'])
Here's how to properly handle Django management commands:
- name: Run Django migrate
command: "python manage.py migrate"
register: migrate_out
changed_when: "'No migrations to apply' not in migrate_out.stdout"
- name: Show migration output
debug:
var: migrate_out.stdout_lines
verbosity: 1
This produces clean, line-by-line output while still capturing all execution details.