#!/usr/bin/env pwsh <# .SYNOPSIS Debug logs tail wrapper for Salesforce CLI with real-time monitoring .DESCRIPTION A user-friendly wrapper around 'sf apex tail log' that provides real-time debug log monitoring with filtering, formatting, and intelligent output colorization. .PARAMETER to Target org username or alias .PARAMETER ui Specific user ID to monitor (default: current user) .PARAMETER lv Log level: ERROR, WARN, INFO, DEBUG, FINE, FINER, FINEST .PARAMETER dr How long to tail logs in minutes (default: 30) .PARAMETER ft Filter log entries containing pattern .PARAMETER ax Show only Apex-related log entries .PARAMETER nc Disable colored output .PARAMETER ve Enable verbose output with timestamps .PARAMETER hp Show this help message .EXAMPLE .\sf-logs-tail.ps1 -to MYORG .\sf-logs-tail.ps1 -to MYORG -lv DEBUG -dr 60 .\sf-logs-tail.ps1 -to MYORG -ft "MyClass" -ax .\sf-logs-tail.ps1 -to sandbox -ui USER123 .NOTES This script automatically checks for Salesforce CLI installation and runs diagnostics if the CLI is not found. Use Ctrl+C to stop tailing logs and exit. #> param( [string]$to, [string]$ui, [ValidateSet("ERROR", "WARN", "INFO", "DEBUG", "FINE", "FINER", "FINEST")] [string]$lv, [int]$dr = 30, [string]$ft, [switch]$ax, [switch]$nc, [switch]$ve, [switch]$hp ) # Show help if no parameters provided if (-not ($to -or $ui -or $lv -or $dr -ne 30 -or $ft -or $ax -or $nc -or $ve -or $hp)) { Get-Help $MyInvocation.MyCommand.Path -Detailed exit 0 } # Show help if requested if ($hp) { Get-Help $MyInvocation.MyCommand.Path -Detailed exit 0 } # Function to check if Salesforce CLI is installed function Test-SalesforceCLI { try { $null = Get-Command sf -ErrorAction Stop return $true } catch { return $false } } # Function to run sf-check diagnostics function Invoke-SalesforceCheck { $checkScript = if (Test-Path "sf-check.ps1") { ".\sf-check.ps1" } elseif (Test-Path "sf-check.sh") { "bash sf-check.sh" } else { $null } if ($checkScript) { Write-Host "Running Salesforce CLI diagnostics..." -ForegroundColor Yellow Invoke-Expression $checkScript } else { Write-Host "Salesforce CLI not found and no diagnostic script available." -ForegroundColor Red Write-Host "Please install the Salesforce CLI: https://developer.salesforce.com/tools/salesforcecli" -ForegroundColor Red } } # Function to colorize log level function Write-ColorizedLogLevel { param([string]$Level) switch -Regex ($Level) { "ERROR" { return Write-Host $Level -ForegroundColor Red -NoNewline; "" } "WARN" { return Write-Host $Level -ForegroundColor Yellow -NoNewline; "" } "INFO" { return Write-Host $Level -ForegroundColor Green -NoNewline; "" } "DEBUG" { return Write-Host $Level -ForegroundColor Cyan -NoNewline; "" } "FINE" { return Write-Host $Level -ForegroundColor Blue -NoNewline; "" } "APEX" { return Write-Host $Level -ForegroundColor Magenta -NoNewline; "" } default { return $Level } } } # Function to format log entry function Write-FormattedLogEntry { param([string]$Line, [bool]$ShowColors, [bool]$ShowTimestamp) if ($ShowColors) { if ($ShowTimestamp) { $timestamp = Get-Date -Format "HH:mm:ss" Write-Host "[$timestamp] " -ForegroundColor Gray -NoNewline } # Colorize based on content switch -Regex ($Line) { "(ERROR|EXCEPTION|FATAL)" { Write-Host $Line -ForegroundColor Red } "(WARN|WARNING)" { Write-Host $Line -ForegroundColor Yellow } "(DEBUG|FINE)" { Write-Host $Line -ForegroundColor Cyan } "(APEX|USER_DEBUG)" { Write-Host $Line -ForegroundColor Magenta } default { Write-Host $Line } } } else { Write-Host $Line } } # Function to test if log entry should be shown function Test-ShowLogEntry { param([string]$Line, [string]$FilterPattern, [bool]$ApexOnlyMode) # Apply apex-only filter if ($ApexOnlyMode) { if ($Line -notmatch "(APEX|USER_DEBUG|EXCEPTION|METHOD_|CONSTRUCTOR_|DML_|SOQL_|VALIDATION_|FLOW_)") { return $false } } # Apply custom filter if ($FilterPattern) { if ($Line -notmatch $FilterPattern) { return $false } } return $true } # Function to setup signal handlers function Set-SignalHandlers { # PowerShell equivalent of trap - handle Ctrl+C gracefully $null = Register-EngineEvent PowerShell.Exiting -Action { Write-Host "" Write-Host "Stopping log tail..." -ForegroundColor Yellow } } # Silently check for Salesforce CLI if (-not (Test-SalesforceCLI)) { Invoke-SalesforceCheck exit 1 } # Validate duration if ($dr -lt 1) { Write-Host "Error: Duration must be at least 1 minute" -ForegroundColor Red exit 1 } # Build the sf command $sfArgs = @("apex", "tail", "log") # Add optional parameters if ($to) { $sfArgs += "--target-org" $sfArgs += $to } if ($ui) { $sfArgs += "--user-id" $sfArgs += $ui } if ($lv) { $sfArgs += "--debug-level" $sfArgs += $lv } # Set up signal handlers Set-SignalHandlers # Display log tail information Write-Host "📡 Starting Debug Log Tail" -ForegroundColor Blue Write-Host "===========================" -ForegroundColor Blue if ($to) { Write-Host "Target org: $to" -ForegroundColor Cyan } if ($ui) { Write-Host "User ID: $ui" -ForegroundColor Cyan } else { Write-Host "User: Current user" -ForegroundColor Cyan } if ($lv) { Write-Host "Log level: " -ForegroundColor Cyan -NoNewline Write-ColorizedLogLevel $lv Write-Host "" } Write-Host "Duration: $dr minutes" -ForegroundColor Cyan if ($ft) { Write-Host "Filter: $ft" -ForegroundColor Cyan } if ($ax) { Write-Host "Mode: Apex-only logs" -ForegroundColor Yellow } if ($ve) { Write-Host "Verbose: Enabled (with timestamps)" -ForegroundColor Yellow } # Color settings $showColors = -not $nc if ($nc) { Write-Host "Colors: Disabled" -ForegroundColor Gray } Write-Host "" Write-Host "Press Ctrl+C to stop tailing logs" -ForegroundColor Yellow Write-Host "" # Display the command being run if ($ve) { Write-Host "Executing: sf $($sfArgs -join ' ')" -ForegroundColor Gray Write-Host "" } # Start the log tailing with timeout try { $job = Start-Job -ScriptBlock { param($sfArgs) & sf @sfArgs 2>$null } -ArgumentList $sfArgs $timeoutTime = (Get-Date).AddMinutes($dr) while ((Get-Date) -lt $timeoutTime -and $job.State -eq "Running") { $output = Receive-Job $job foreach ($line in $output) { if (Test-ShowLogEntry $line $ft $ax) { Write-FormattedLogEntry $line $showColors $ve } } Start-Sleep -Milliseconds 100 } # Get any remaining output $finalOutput = Receive-Job $job foreach ($line in $finalOutput) { if (Test-ShowLogEntry $line $ft $ax) { Write-FormattedLogEntry $line $showColors $ve } } $exitCode = 0 if ($job.State -eq "Running") { Stop-Job $job $exitCode = 124 # Timeout exit code } elseif ($job.State -eq "Failed") { $exitCode = 1 } Remove-Job $job Write-Host "" if ($exitCode -eq 124) { Write-Host "⏰ Log tail timed out after $dr minutes" -ForegroundColor Yellow } elseif ($exitCode -eq 0) { Write-Host "✅ Log tail completed successfully" -ForegroundColor Green } else { Write-Host "❌ Log tail failed with exit code: $exitCode" -ForegroundColor Red Write-Host "💡 Check org connectivity and user permissions" -ForegroundColor Yellow } Write-Host "Tip: Use different filters to focus on specific log types" -ForegroundColor Gray } catch { Write-Host "Error during log tail execution: $($_.Exception.Message)" -ForegroundColor Red exit 1 }