--- # Container Logs Collection Playbook # Collect logs from multiple containers for troubleshooting # Usage: ansible-playbook playbooks/container_logs.yml -e "service_name=plex" # Usage: ansible-playbook playbooks/container_logs.yml -e "service_pattern=immich" # Usage: ansible-playbook playbooks/container_logs.yml -e "collect_all=true" - name: Collect Container Logs hosts: "{{ host_target | default('all') }}" gather_facts: yes vars: target_service_name: "{{ service_name | default('') }}" target_service_pattern: "{{ service_pattern | default('') }}" target_collect_all: "{{ collect_all | default(false) }}" target_log_lines: "{{ log_lines | default(100) }}" target_log_since: "{{ log_since | default('1h') }}" output_dir: "/tmp/container_logs/{{ ansible_date_time.date }}" target_include_timestamps: "{{ include_timestamps | default(true) }}" target_follow_logs: "{{ follow_logs | default(false) }}" tasks: - name: Validate input parameters fail: msg: "Specify either service_name, service_pattern, or collect_all=true" when: - target_service_name == "" - target_service_pattern == "" - not (target_collect_all | bool) - name: Check if Docker is running systemd: name: docker register: docker_status failed_when: docker_status.status.ActiveState != "active" - name: Create local log directory file: path: "{{ output_dir }}/{{ inventory_hostname }}" state: directory mode: '0755' delegate_to: localhost - name: Create remote log directory file: path: "{{ output_dir }}/{{ inventory_hostname }}" state: directory mode: '0755' - name: Get specific service container shell: 'docker ps -a --filter "name={{ target_service_name }}" --format "{%raw%}{{.Names}}{%endraw%}"' register: specific_container when: target_service_name != "" changed_when: false - name: Get containers matching pattern shell: 'docker ps -a --filter "name={{ target_service_pattern }}" --format "{%raw%}{{.Names}}{%endraw%}"' register: pattern_containers when: target_service_pattern != "" changed_when: false - name: Get all containers shell: 'docker ps -a --format "{%raw%}{{.Names}}{%endraw%}"' register: all_containers when: target_collect_all | bool changed_when: false - name: Combine container lists set_fact: target_containers: >- {{ (specific_container.stdout_lines | default([])) + (pattern_containers.stdout_lines | default([])) + (all_containers.stdout_lines | default([]) if target_collect_all | bool else []) }} - name: Display target containers debug: msg: | 📦 CONTAINER LOG COLLECTION =========================== 🖥️ Host: {{ inventory_hostname }} 📋 Target Containers: {{ target_containers | length }} {% for container in target_containers %} - {{ container }} {% endfor %} 📏 Log Lines: {{ target_log_lines }} ⏰ Since: {{ target_log_since }} - name: Fail if no containers found fail: msg: "No containers found matching the criteria" when: target_containers | length == 0 - name: Get container information shell: | docker inspect {{ item }} --format=' Container: {{ item }} Image: {%raw%}{{.Config.Image}}{%endraw%} Status: {%raw%}{{.State.Status}}{%endraw%} Started: {%raw%}{{.State.StartedAt}}{%endraw%} Restart Count: {%raw%}{{.RestartCount}}{%endraw%} Health: {%raw%}{{if .State.Health}}{{.State.Health.Status}}{{else}}No health check{{end}}{%endraw%} ' register: container_info loop: "{{ target_containers }}" changed_when: false - name: Collect container logs shell: | echo "=== CONTAINER INFO ===" > {{ output_dir }}/{{ inventory_hostname }}/{{ item }}.log docker inspect {{ item }} --format=' Container: {{ item }} Image: {%raw%}{{.Config.Image}}{%endraw%} Status: {%raw%}{{.State.Status}}{%endraw%} Started: {%raw%}{{.State.StartedAt}}{%endraw%} Restart Count: {%raw%}{{.RestartCount}}{%endraw%} Health: {%raw%}{{if .State.Health}}{{.State.Health.Status}}{{else}}No health check{{end}}{%endraw%} ' >> {{ output_dir }}/{{ inventory_hostname }}/{{ item }}.log echo "" >> {{ output_dir }}/{{ inventory_hostname }}/{{ item }}.log echo "=== CONTAINER LOGS ===" >> {{ output_dir }}/{{ inventory_hostname }}/{{ item }}.log {% if target_include_timestamps | bool %} docker logs {{ item }} --since={{ target_log_since }} --tail={{ target_log_lines }} -t >> {{ output_dir }}/{{ inventory_hostname }}/{{ item }}.log 2>&1 {% else %} docker logs {{ item }} --since={{ target_log_since }} --tail={{ target_log_lines }} >> {{ output_dir }}/{{ inventory_hostname }}/{{ item }}.log 2>&1 {% endif %} loop: "{{ target_containers }}" ignore_errors: yes - name: Get container resource usage shell: 'docker stats {{ target_containers | join(" ") }} --no-stream --format "table {%raw%}{{.Container}}\t{{.CPUPerc}}\t{{.MemUsage}}\t{{.NetIO}}\t{{.BlockIO}}{%endraw%}"' register: container_stats when: target_containers | length > 0 ignore_errors: yes - name: Save container stats copy: content: | Container Resource Usage - {{ ansible_date_time.iso8601 }} Host: {{ inventory_hostname }} {{ container_stats.stdout }} dest: "{{ output_dir }}/{{ inventory_hostname }}/container_stats.txt" when: container_stats.stdout is defined - name: Check for error patterns in logs shell: | echo "=== ERROR ANALYSIS ===" > {{ output_dir }}/{{ inventory_hostname }}/error_summary.txt echo "Host: {{ inventory_hostname }}" >> {{ output_dir }}/{{ inventory_hostname }}/error_summary.txt echo "Timestamp: {{ ansible_date_time.iso8601 }}" >> {{ output_dir }}/{{ inventory_hostname }}/error_summary.txt echo "" >> {{ output_dir }}/{{ inventory_hostname }}/error_summary.txt for container in {{ target_containers | join(' ') }}; do echo "=== $container ===" >> {{ output_dir }}/{{ inventory_hostname }}/error_summary.txt # Count error patterns error_count=$(docker logs $container --since={{ target_log_since }} 2>&1 | grep -i -E "(error|exception|failed|fatal|panic)" | wc -l) warn_count=$(docker logs $container --since={{ target_log_since }} 2>&1 | grep -i -E "(warn|warning)" | wc -l) echo "Errors: $error_count" >> {{ output_dir }}/{{ inventory_hostname }}/error_summary.txt echo "Warnings: $warn_count" >> {{ output_dir }}/{{ inventory_hostname }}/error_summary.txt # Show recent errors if [ $error_count -gt 0 ]; then echo "Recent Errors:" >> {{ output_dir }}/{{ inventory_hostname }}/error_summary.txt docker logs $container --since={{ target_log_since }} 2>&1 | grep -i -E "(error|exception|failed|fatal|panic)" | tail -5 >> {{ output_dir }}/{{ inventory_hostname }}/error_summary.txt fi echo "" >> {{ output_dir }}/{{ inventory_hostname }}/error_summary.txt done when: target_containers | length > 0 ignore_errors: yes - name: Create summary report copy: content: | 📊 CONTAINER LOG COLLECTION SUMMARY =================================== 🖥️ Host: {{ inventory_hostname }} 📅 Collection Time: {{ ansible_date_time.iso8601 }} 📦 Containers Processed: {{ target_containers | length }} 📏 Log Lines per Container: {{ target_log_lines }} ⏰ Time Range: {{ target_log_since }} 📋 CONTAINERS: {% for container in target_containers %} - {{ container }} {% endfor %} 📁 LOG FILES LOCATION: {{ output_dir }}/{{ inventory_hostname }}/ 📄 FILES CREATED: {% for container in target_containers %} - {{ container }}.log {% endfor %} - container_stats.txt - error_summary.txt - collection_summary.txt (this file) 🔍 QUICK ANALYSIS: Use these commands to analyze the logs: # View error summary cat {{ output_dir }}/{{ inventory_hostname }}/error_summary.txt # Search for specific patterns grep -i "error" {{ output_dir }}/{{ inventory_hostname }}/*.log # View container stats cat {{ output_dir }}/{{ inventory_hostname }}/container_stats.txt # Follow live logs (if needed) {% for container in target_containers[:3] %} docker logs -f {{ container }} {% endfor %} dest: "{{ output_dir }}/{{ inventory_hostname }}/collection_summary.txt" - name: Display collection results debug: msg: | ✅ LOG COLLECTION COMPLETE ========================== 🖥️ Host: {{ inventory_hostname }} 📦 Containers: {{ target_containers | length }} 📁 Location: {{ output_dir }}/{{ inventory_hostname }}/ 📄 Files Created: {% for container in target_containers %} - {{ container }}.log {% endfor %} - container_stats.txt - error_summary.txt - collection_summary.txt 🔍 Quick Commands: # View errors: cat {{ output_dir }}/{{ inventory_hostname }}/error_summary.txt # View stats: cat {{ output_dir }}/{{ inventory_hostname }}/container_stats.txt ========================== - name: Archive logs (optional) archive: path: "{{ output_dir }}/{{ inventory_hostname }}" dest: "{{ output_dir }}/{{ inventory_hostname }}_logs_{{ ansible_date_time.epoch }}.tar.gz" remove: no when: archive_logs | default(false) | bool delegate_to: localhost