DEV Community

Cover image for How to View Logs and Diffs of Custom Scripts in Ansible
Ganesh Kumar
Ganesh Kumar

Posted on

How to View Logs and Diffs of Custom Scripts in Ansible

Hello, I'm Ganesh. I'm working on FreeDevTools online, currently building a single platform for all development tools, cheat codes, and TL; DRs — a free, open-source hub where developers can quickly find and use tools without the hassle of searching the internet.

When you are using Ansible to orchestrate servers, you often need to step outside standard modules and run a custom Python or Bash script.

The problem is the "Black Box" effect. You run a script, Ansible says changed: [localhost], and you move on. But did the script actually work? Did it update your configuration file? If so, what specific lines changed?

Standard Ansible output is often too quiet. In this post, I will walk through how to go from a execution to a comprehensive output that shows you exactly what your custom script is doing, including file diffs and clean text formatting.

The Problem

Let's say you have a Python script (custom_generator.py) that generates a configuration file. A basic Ansible task looks like this:

- name: Run custom configuration script
  ansible.builtin.command:
    cmd: "python3 /opt/scripts/custom_generator.py --create"
Enter fullscreen mode Exit fullscreen mode

When you run this, Ansible reports "Changed." That is it. You don't know if the configuration file was created, if it overwrote existing settings, or if the script silently handled an exception.

To fix this, we need a robust pattern:
Check -> Backup -> Execute -> Compare -> Display.

Step 1: The Setup and Backup

Before we run the script, we need to know the current state of the server. We check if the target configuration file exists and, if it does, we create a temporary backup. This allows us to compare "Before" vs. "After" later on.

- name: Check if config file exists
  ansible.builtin.stat:
    path: /opt/app/config.conf
  register: pre_check

- name: Backup existing config
  ansible.builtin.copy:
    src: /opt/app/config.conf
    dest: /tmp/config.conf.bak
    remote_src: true
  when: pre_check.stat.exists
Enter fullscreen mode Exit fullscreen mode

Step 2: Executing the Script

Now we run the script. A critical detail often missed here is the working directory. If your script writes a file using a relative path, Ansible might dump it in the user's home directory (like /root/) instead of where the script lives.

Always use chdir to ensure the script runs in the correct context:

- name: Generate configuration
  become: true
  ansible.builtin.command:
    cmd: "python3 {{ custom_script_path }} --create"
    chdir: "{{ custom_script_path | dirname }}"
Enter fullscreen mode Exit fullscreen mode

Step 3: Difference Check

This is the most important part. We want to see what changed.

If the file is new, we want to see the whole file (marked as additions).
If the file existed, we only want to see the lines that changed.

We can achieve this using the Linux diff command and some Jinja2 logic. If the backup file doesn't exist, we compare the new file against /dev/null.

- name: Diff Changes
  ansible.builtin.shell: >
    diff -u {{ '/tmp/config.conf.bak' if pre_check.stat.exists else '/dev/null' }} 
    /opt/app/config.conf || true
  register: config_diff
  changed_when: config_diff.stdout != ""
Enter fullscreen mode Exit fullscreen mode

Note: We add || true because the diff command returns an exit code of 1 when it finds differences, which would normally cause Ansible to crash. We want to capture that output, not fail.

Step 4: Handling "No Changes"

Sometimes, your script runs but the configuration remains exactly the same. In that case, diff returns nothing. However, purely for verification purposes, you might still want to see the content of the file to ensure it looks correct.

We add a fallback task that reads the full file content only if the diff was empty.

- name: Read full content (fallback for no changes)
  ansible.builtin.command: "cat /opt/app/config.conf"
  register: full_content
  changed_when: false
  when: config_diff.stdout == ""
Enter fullscreen mode Exit fullscreen mode

Step 5: Formated Display

Finally, we display the data. Standard Ansible debug output is messy—it returns a list of strings wrapped in JSON brackets and quotes, which is hard to read.

To make it human-readable, we use the | join('\n') Jinja2 filter. We also use a conditional logic to decide whether to show the Diff (if changes occurred) or the Full Content (if no changes occurred).

Here is the final consolidated block:

- name: Display Location and Content
  ansible.builtin.debug:
    msg: |
      Config File Location: /opt/app/config.conf
      ----------------------------------------------------
      {{ (config_diff.stdout_lines if config_diff.stdout != '' else full_content.stdout_lines) | join('\n') }}
Enter fullscreen mode Exit fullscreen mode

The Result

By implementing this block, your Ansible output transforms from a vague "Changed" status into a clear, actionable report.

If the file is new or changed:

Config File Location: /opt/app/config.conf
----------------------------------------------------
--- /tmp/config.conf.bak
+++ /opt/app/config.conf
@@ -1,5 +1,5 @@
 # App Config
- port=80
+ port=8080
  debug=false
Enter fullscreen mode Exit fullscreen mode

If the file is unchanged:

Config File Location: /opt/app/config.conf
----------------------------------------------------
# App Config
port=8080
debug=false
Enter fullscreen mode Exit fullscreen mode

This approach gives you complete visibility into your custom scripts, ensuring you never have to guess what is happening on your remote servers.


FreeDevTools

I’ve been building for FreeDevTools.

A collection of UI/UX-focused tools crafted to simplify workflows, save time, and reduce friction when searching for tools and materials.

Any feedback or contributions are welcome!

It’s online, open-source, and ready for anyone to use.

👉 Check it out: FreeDevTools

⭐ Star it on GitHub: freedevtools

Top comments (0)