Developers / Admins / Artificial Intelligence / DevOps

Debug Apex With Open Source AI Models and GitHub Actions

By Bassem Marji

In modern Salesforce environments, understanding application behavior is essential for maintaining system performance, debugging issues, and optimizing Apex code. Debug logs are a treasure trove of operational insights, but they are also notoriously dense and time-consuming to read.

This is where the AI-Powered Logs Analyzer pipeline comes into play. Designed as a fully automated GitHub Actions workflow, it connects to your Salesforce org, retrieves relevant debug logs, and analyzes them with a locally-hosted AI model from Ollama. The result is a concise, structured report that highlights problems, performance bottlenecks, and coding anti-patterns; all without manual log inspection.

The Hidden Cost of the Traditional Salesforce Debugging

Every Salesforce Developer knows the pain: a user reports slow performance, an unexpected error occurs in production, or worse, silent failures that go unnoticed until they compound into major issues. The traditional debugging approach involves:

  1. Manual Log Collection: Developers manually navigate to the Developer Console or Setup menu to download individual debug logs.
  2. Time-Intensive Analysis: Hours spent reading through verbose log outputs, searching for relevant error messages or performance indicators.
  3. Pattern Recognition Overhead: Analysts must mentally correlate issues across multiple logs to identify systemic problems.
  4. Reactive Approach: Problems are only investigated after they’re reported by users.
  5. Limited Scale: Manual analysis doesn’t scale beyond a few logs at a time.

A CI/CD Solution for Intelligent Salesforce Log Review

This article dives into a fully automated GitHub Actions pipeline designed to revolutionize how Salesforce teams handle debug log analysis. The workflow seamlessly:

  • Retrieves recent Apex debug logs from a Salesforce org.
  • Downloads and organizes them for processing.
  • Analyzes the logs using a local large language model (LLM) via Ollama while ensuring no sensitive data leaves the environment.
  • Identifies critical issues such as errors, performance bottlenecks, governor limit usage, and common code anti-patterns.
  • Generates a clear, structured markdown report summarizing the findings.
  • Uploads the report as a pipeline artifact for easy access.

Built with security and privacy at its core, this solution keeps all data within the GitHub Actions runner without sending sensitive log data to external APIs.

Core Technologies Powering the Log Analysis Pipeline

To understand how this pipeline delivers intelligent, secure, and automated insights from Salesforce debug logs, let us explore the key technologies that form its foundation:

CI/CD (Continuous Integration and Continuous Delivery/Deployment)

CI/CD is an automated software engineering practice that enables teams to integrate code changes frequently and deploy them reliably. In the context of Salesforce development, CI/CD pipelines help catch issues before they reach production by automating testing, validation, and analysis.

Ollama

Ollama is an open-source platform that allows developers to run large language models (LLMs) locally with minimal setup. It supports models like “mistral”, “mistral:instruct”, and others through a simple command-line interface and REST API. By hosting the LLM on the same runner as the pipeline, Ollama ensures that sensitive debug logs never leave the secure environment, eliminating data privacy risks associated with cloud-based AI services.

Key advantages:

  • Data Sovereignty: Logs are analyzed entirely in-memory, with no external data transmission to an LLM or agent API provider.
  • Offline Operation: Once the model is downloaded, no internet connection is required.
  • Developer Flexibility: Easy integration into scripts and automation tools.

Mistral

Mistral is an open-source large language model developed by Mistral AI, optimized for reasoning, code comprehension, and structured output generation. In this pipeline, it acts as an AI-powered log analyst, parsing complex Salesforce debug logs and extracting meaningful insights.

Why Mistral fits perfectly:

  • Strong Reasoning Capabilities: Accurately interprets log patterns, timestamps, and error hierarchies.
  • Efficient Inference: Runs quickly even on modest hardware, making it ideal for time-sensitive CI/CD jobs.
  • Instruction-Tuned Variants: Follows prompts precisely while ensuring consistent markdown formatting and rule adherence.
  • MIT License: Free to use and modify, supporting long-term sustainability without vendor lock-in.

Mistral is prompted to:

  • Identify errors, performance bottlenecks, and governor limit usage.
  • Detect anti-patterns such as SOQL or DML operations inside loops.
  • Output findings in a clean, structured markdown table.
  • Reference source log files for traceability.

By combining Mistral’s intelligence with strict output constraints, the pipeline transforms raw, unstructured logs into actionable, developer-ready reports all within minutes and without compromising security.

The Solution Architecture

Join us to explore how this pipeline works and why it represents an advancement in the future of Salesforce DevOps.

Overview

This workflow is initiated manually and runs in two sequential jobs:

  1. collect-logs: Queries Salesforce, downloads logs, and uploads them as artifacts.
  2. analyze-logs: Uses Ollama to run AI analysis in batches, producing a structured report.

Prerequisites

Before implementing the pipeline, ensure these critical components are prepared:

  1. GitHub Repository: Sign in to GitHub and create a private repository for your Salesforce project as illustrated below.
  1. Generate Salesforce Authentication URL: With Salesforce CLI installed on your machine, authenticate to your Salesforce org and generate the target org authentication URL by running the following command in your terminal: > sf force:auth:web:login --alias <OrgAlias> --instance-url <OrgURL>  --set-default

Then, follow these steps:

  • When the browser opens, log in to Salesforce.
  • Close the tab after successful authentication.
  • Execute this command in the terminal: > sf org display --target-org <OrgAlias> --verbose

Maintain the value shown after Sfdx Auth Url for later use.

  • Critical Security Note: SFDX Auth URLs function as full-access credentials. Treat them as highly sensitive:
    • They grant permissions equivalent to the associated Salesforce user.
    • Most dev orgs use admin-level profiles (risking total org compromise if leaked).
  • Always:
    • Use service accounts with least-privilege access.
    • Never expose in code, logs, or unsecured locations
  1. Store the collected authentication URL in GitHub Secrets:
    • Navigate to Repository Settings → Secrets and Variables → Actions
    • Select new repository secret and add the following:
      • Secret Name: ORG_SFDX_URL
      • Description: SFDX authentication URL for your Salesforce org
  1. Enable Workflow Permissions: Ensure that permissions for actions and reusable workflows are granted by enabling the “Allow all actions and reusable workflows” setting:
  1. Debug Log Execution and Collection: This pipeline analyzes existing debug logs but does not trigger their creation. Salesforce debug logs are only generated when trace flags are proactively configured for users, Apex classes, or triggers before transaction execution.

Implementation

  • Create a new file named “LogsAnalyzer.yml” in your repository’s /.github/workflows/ folder.
  • Copy the supplied YAML code snippet into the new file.
  • Save and commit your changes.
YAML Code Snippet

name: AI Powered Logs Analyzer

on:
  workflow_dispatch:
    inputs:
      org_alias:
        type: string
        description: "Salesforce org alias"
        required: true
        default: "dev"
      days_back:
        type: number
        description: "Number of days back to retrieve logs"
        required: true
        default: 7
      model_name:
        type: choice
        description: "Ollama model to use"
        options:
          - mistral
          - mistral:instruct
        default: mistral

env:
  ORG_ALIAS: ${{ github.event.inputs.org_alias }}
  DAYS_BACK: ${{ github.event.inputs.days_back }}
  MODEL_NAME: ${{ github.event.inputs.model_name }}
  NODE_VERSION: "20"
  LOG_DIR: "sf-logs"

jobs:
  collect-logs:
    name: Setup Environment & Collect Logs
    runs-on: ubuntu-latest
    outputs:
      log_count: ${{ steps.query.outputs.log_count }}
      has_logs: ${{ steps.query.outputs.has_logs }}
    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}


      - name: Install dependencies
        run: |
          npm install --global @salesforce/cli
          sf plugins:update
          sudo apt-get update && sudo apt-get install -y jq
      
      - name: 🔐 Authenticate to Salesforce Org
        run: |
          echo "${{ secrets.ORG_SFDX_URL }}" | sf org login sfdx-url --alias $ORG_ALIAS --set-default --sfdx-url-stdin
          sf org list

      - name: Query Debug Logs
        id: query
        timeout-minutes: 10
        run: |
          # Calculate start time (UTC)
          START_TIME=$(date -u -d "$DAYS_BACK days ago" '+%Y-%m-%dT%H:%M:%S.000Z')
          echo "🔍 Querying logs since: $START_TIME"
          
          # Create log directory
          mkdir -p "$LOG_DIR"
          
          # Query logs with proper SOQL syntax
          QUERY="SELECT Id, Application, DurationMilliseconds, LogLength, LogUserId, Operation, StartTime, Status \
                   FROM ApexLog \
                  WHERE StartTime >= $START_TIME \
                  ORDER BY StartTime DESC \
                  LIMIT 100"          
          echo "Executing SOQL query..."
          sf data query \
            --query "$QUERY" \
            --target-org "$ORG_ALIAS" \
            --result-format json > logs_query.json
            
          # Extract log information
          log_count=$(jq -r '.result.totalSize' logs_query.json)
          
          if [ "$log_count" -eq 0 ]; then
            echo "⚠️ No logs found in the last $DAYS_BACK days"
            echo "log_count=0" >> $GITHUB_OUTPUT
            echo "has_logs=false" >> $GITHUB_OUTPUT
            echo "No logs found in the last $DAYS_BACK days" > "$LOG_DIR/no-logs-found.txt"
          else
            echo "📊 Found $log_count debug logs"
            echo "log_count=$log_count" >> $GITHUB_OUTPUT
            echo "has_logs=true" >> $GITHUB_OUTPUT
            
            # Save log metadata
            jq '.result.records[] | {Id, Application, DurationMilliseconds, LogLength, Operation, StartTime, Status}' \
              logs_query.json > "$LOG_DIR/log_metadata.json"
          fi

      - name: Download Log Contents
        if: steps.query.outputs.has_logs == 'true'
        timeout-minutes: 45
        run: |
          echo "📥 Downloading log contents..."
          
          # Extract log IDs
          log_ids=$(jq -r '.result.records[].Id' logs_query.json)
          total_logs=$(echo "$log_ids" | wc -l)
          current=0
          
          echo "Starting download of $total_logs logs..."

          # Download logs with progress tracking using `sf apex log get`
          downloaded_count=0
          for logid in $log_ids; do
            current=$((current + 1))
            echo "[$current/$total_logs] Attempting to download log: $logid"
          
            log_file="$LOG_DIR/$logid.log"
            tmp_error=$(mktemp)
          
            # Run and redirect stdout to log, stderr to temp error
            if sf apex log get --log-id "$logid" --target-org "$ORG_ALIAS" > "$log_file" 2> "$tmp_error"; then
              if [ -s "$log_file" ]; then
                echo "✅ Successfully downloaded $logid"
                downloaded_count=$((downloaded_count + 1))
              else
                echo "⚠️ Log $logid is empty (0 bytes)"
                echo "$logid (empty content)" >> "$LOG_DIR/failed_downloads.txt"
                rm -f "$log_file"  # Optional: delete empty log files
              fi
            else
              echo "❌ Failed to download log $logid"
              echo "$logid" >> "$LOG_DIR/failed_downloads.txt"
              if [ -s "$tmp_error" ]; then
                echo "🔍 Error details for $logid:"
                cat "$tmp_error"
                # Optional: Save the error only if non-empty
                cp "$tmp_error" "$LOG_DIR/errors_$logid.txt"
              else
                echo "⚠️ No error output for $logid"
              fi
            fi
          
            rm -f "$tmp_error"
            sleep 0.5
          done
          
          # Count successful downloads
          downloaded_count=$(find "$LOG_DIR" -name "*.log" | wc -l)
          echo "✅ Successfully downloaded $downloaded_count out of $total_logs logs"
          
      - name: Upload Log Artifacts
        uses: actions/upload-artifact@v4
        with:
          name: salesforce-debug-logs
          path: ${{ env.LOG_DIR }}
          retention-days: 7
          if-no-files-found: warn

      - name: 📄 Summary of Collected Logs
        run: |
          {
            echo "## 📊 Collect Logs Summary" 
            echo "- Org Alias: **$ORG_ALIAS**" 
            echo "- Days Back: **$DAYS_BACK**"
            echo "- Logs Found: **${{ steps.query.outputs.log_count }}**" 
            if [ "${{ steps.query.outputs.has_logs }}" = "true" ]; then
              downloaded=$(find "$LOG_DIR" -name "*.log" | wc -l)
              echo "- Logs Downloaded: **$downloaded**" 
              echo "- Artifact: \`salesforce-debug-logs\`"
            else
              echo "- No logs found in specified period"
            fi
          } >> $GITHUB_STEP_SUMMARY
          
  analyze-logs:
    name: Analyze Logs with Ollama
    runs-on: ubuntu-latest
    needs: collect-logs
    if: needs.collect-logs.outputs.has_logs == 'true'
    steps:
      - name: Download Log Artifacts
        uses: actions/download-artifact@v4
        with:
          name: salesforce-debug-logs
          path: logs

      - name: Install Ollama
        run: |
          echo "🚀 Installing Ollama..."
          curl -fsSL https://ollama.com/install.sh | sh
          ollama serve &
          for i in {1..30}; do
            if curl -s http://localhost:11434/api/version > /dev/null; then
              break
            fi
            sleep 2
          done

      - name: Download Model
        timeout-minutes: 30
        run: |
          MODEL="${{ inputs.MODEL_NAME }}"
          echo "Downloading Ollama model: $MODEL"
          ollama pull "$MODEL"
          
          echo "🔍 Verifying model installation..."
          ollama list


      - name: Analyze logs in batches
        run: |
          REPORT_FILE="analysis_report.md"
          BATCH_SIZE=5
          MODEL="${{ inputs.model_name }}"
      
          # Initialize report
          echo "# AI Log Analysis Report" > "$REPORT_FILE"
          echo "Model used: $MODEL" >> "$REPORT_FILE"
          echo "" >> "$REPORT_FILE"
      
          # Function to generate AI prompt
          generate_prompt() {
            local combined_logs="$1"
            cat <<EOF
            You are a Salesforce Debug Log analyzer. 
            Read the following Apex debug logs (multiple) and produce a **short structured report** in markdown.
            
            **Rules:**
            - Use ONLY the information present in the logs. Do not speculate.
            - NO Java, pseudo-code, or unrelated language examples.
            - NO generic Salesforce explanations.
            - Output must be under 500 words.
            
            **Output Format (Markdown table):**
            | Category | Findings |
            |----------|----------|
            | Problems & Errors | Bullet points of exceptions, failed DML, missing data, etc. |
            | Latency Sources | Bullet points with operation names and durations in ms (if available) |
            | Governor Limit Usage | Bullet points showing limit name, used/total, and % |
            | Code Anti-Patterns | Bullet points identifying inefficient patterns like SOQL/DML in loops, inefficient queries, etc. |
            
            **Additional:**
              - Sort each section by severity/impact.
              - For governor limits, only list limits above 50% usage or breached.
              - For anti-patterns, flag:
                - SOQL queries inside 'for' loops
                - DML operations inside loops
                - Large collections without bulk handling
                - Use of deprecated methods
              - Indicate which log file each finding came from in parentheses.

            Logs:
            $combined_logs
          EOF
          }      
          # Process logs in batches
          batch=()
          for file in logs/*.log; do
            batch+=("$file")
      
            # If batch is full, process it
            if [[ ${#batch[@]} -eq $BATCH_SIZE ]]; then
              combined=$(cat "${batch[@]}")
              echo "## Batch starting with ${batch[0]}" >> "$REPORT_FILE"
      
              prompt="$(generate_prompt "$combined")"
              ollama run "$MODEL" "$prompt" >> "$REPORT_FILE"
              echo -e "\n---\n" >> "$REPORT_FILE"
      
              # Reset batch
              batch=()
            fi
          done
      
          # Process final incomplete batch
          if [[ ${#batch[@]} -gt 0 ]]; then
            combined=$(cat "${batch[@]}")
            echo "## Final Batch starting with ${batch[0]}" >> "$REPORT_FILE"
      
            prompt="$(generate_prompt "$combined")"
            ollama run "$MODEL" "$prompt" >> "$REPORT_FILE"
            echo -e "\n---\n" >> "$REPORT_FILE"
          fi
          
      - name: Upload Analysis Report
        uses: actions/upload-artifact@v4
        with:
          name: salesforce-log-analysis
          path: analysis_report.md
          retention-days: 7

      - name: 📄 Summary of Analysis
        env:
          MODEL: ${{ inputs.model_name }}
        run: |
          {
            echo "## 🧠 Analysis Summary"
            total_logs=$(ls logs/*.log 2>/dev/null | wc -l || echo 0)
            echo "- Logs Analyzed: **$total_logs**" 
            echo "- Model: \`$MODEL\`"
            echo "- Report File: \`analysis_report.md\` (saved as artifact \`salesforce-log-analysis\`)" 
          } >> $GITHUB_STEP_SUMMARY

Executing and Monitoring the Pipeline

To run and track your workflow:

  1. Access GitHub Actions:
    • Open your repository → click the Actions tab in the top navigation bar.
  2. Trigger the Workflow:
    • In the left sidebar → locate the AI Powered Log Analyzer workflow.
    • Click the Run Workflow Dropdown → Verify Inputs → Execute.
  1. Monitor Execution: Expand individual job instances to inspect logs and outputs
  1. Review Results: Download the salesforce-log-analysis artifact and inspect the generated markdown report to uncover critical insights:

Business Impact

Organizations implementing this AI-powered debug log analysis pipeline will typically see significant improvements across multiple dimensions:

  • Early detection of performance regressions during development cycles.
  • Proactive identification of governor limit risks.
  • Systematic discovery of code anti-patterns across the entire codebase.
  • Consistent application of best practices through automated analysis.
  • Reduced production incidents through proactive issue identification.
  • Lower support overhead by catching issues in development environments.
  • Optimized resource usage through performance bottleneck identification.

Final Thoughts

This AI-powered debug log analysis pipeline embodies a fundamental shift in how organizations approach Salesforce development and operations.

This pipeline democratizes expert-level debugging knowledge, making sophisticated analysis capabilities available to developers at all skill levels. Junior developers gain access to insights that previously required senior expertise, while senior developers can focus on architectural decisions and complex problem-solving rather than routine log analysis.

The Author

Bassem Marji

Bassem is a certified Salesforce Administrator, a Project Implementation Manager at BLOM Bank Lebanon, and a Technical Author at Educative. He is a big fan of the Salesforce ecosystem.

Leave a Reply