How to NOT print items in an Ansible loop error without no_log

Solution 1:

There is a way to achieve desired behavior in multuple tasks with ansible's error handling. You can register output of no_log task and print only non-secret part in subsequent debug:

- hosts: localhost
  connection: local
  tasks:
  - block:
    - shell: "/bin/false"
      loop:
      - name: "item1"
        some_value: "my value"
        secret: "p4$$w0rd"
      - name: "item2"
        some_value: "my value"
        secret: "p4$$w0rd"
      loop_control:
        label: "{{ item.name }}"
      # Capture tasks output
      register: my_task
      no_log: true

    # Run this if task above fails
    rescue:

    # Print msg, stderr or whatever needed
    - debug:
        msg: "{{ item }}"
      loop: "{{ my_task | json_query('results[*].msg') }}"

    # finally fail a play
    - fail:

In case if you need always print output (not only on task failure) use always instead of rescue and fail task on condition:

- hosts: localhost
  connection: local
  tasks:
  - block:
    - shell: "/bin/true"
      loop:
      - name: "item1"
        some_value: "my value"
        secret: "p4$$w0rd"
      - name: "item2"
        some_value: "my value"
        secret: "p4$$w0rd"
      loop_control:
        label: "{{ item.name }}"
      register: my_task
      no_log: true

    always:
    - debug:
        msg: "{{ item }}"
      loop: "{{ my_task | json_query('results[*].msg') }}"

    - fail:
      when: my_task['failed'] | default(false)

Update (2021-04-20)

As pointed by Lucas code above have number of downsides. Main idea was that output can be registered and filtered afterwards. Other parts of code are opinionated examples. There is certainly room for improvement. For example here is code that addresses issues 1, 4, 6, (and partially 2):

- hosts: localhost
  connection: local
  tasks:
  - block:
    - shell: "/bin/true"
      register: my_task
      no_log: true
      # Register all loop-related keys so same loop settings can be reused in debug
      <<: &loop
        loop:
        - name: "item1"
          some_value: "my value"
          secret: "p4$$w0rd"
        - name: "item2"
          some_value: "my value"
          secret: "p4$$w0rd"
        loop_control:
          label: "{{ item.name }}"
          index_var: index

    always:
    - debug:
        # match result with item by index
        msg: "{{ my_task['results'][index]['msg'] | default('ok') }}"
      # reuse loop from main task
      <<: *loop

    - fail:
      when: my_task['failed'] | default(false)

As for now it seems there is no way to implement desired behavior w/o workarounds. Ansible recommends writing logs to secure location in case it contains sensitive data.