#!/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 TargetOrg Target org username or alias .PARAMETER UserId Specific user ID to monitor (default: current user) .PARAMETER Level Log level: ERROR, WARN, INFO, DEBUG, FINE, FINER, FINEST .PARAMETER Duration How long to tail logs in minutes (default: 30) .PARAMETER Filter Filter log entries containing pattern .PARAMETER ApexOnly Show only Apex-related log entries .PARAMETER NoColors Disable colored output .PARAMETER Verbose Enable verbose output with timestamps .PARAMETER Help Show this help message .EXAMPLE .\sf-logs-tail.ps1 .\sf-logs-tail.ps1 -Level DEBUG -Duration 60 .\sf-logs-tail.ps1 -Filter "MyClass" -ApexOnly .\sf-logs-tail.ps1 -TargetOrg sandbox -UserId 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]$TargetOrg, [string]$UserId, [ValidateSet("ERROR", "WARN", "INFO", "DEBUG", "FINE", "FINER", "FINEST")] [string]$Level, [int]$Duration = 30, [string]$Filter, [switch]$ApexOnly, [switch]$NoColors, [switch]$Verbose, [switch]$Help ) # Show help if requested if ($Help) { 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 ($Duration -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 ($TargetOrg) { $sfArgs += "--target-org" $sfArgs += $TargetOrg } if ($UserId) { $sfArgs += "--user-id" $sfArgs += $UserId } if ($Level) { $sfArgs += "--debug-level" $sfArgs += $Level } # Set up signal handlers Set-SignalHandlers # Display log tail information Write-Host "📡 Starting Debug Log Tail" -ForegroundColor Blue Write-Host "===========================" -ForegroundColor Blue if ($TargetOrg) { Write-Host "Target org: $TargetOrg" -ForegroundColor Cyan } if ($UserId) { Write-Host "User ID: $UserId" -ForegroundColor Cyan } else { Write-Host "User: Current user" -ForegroundColor Cyan } if ($Level) { Write-Host "Log level: " -ForegroundColor Cyan -NoNewline Write-ColorizedLogLevel $Level Write-Host "" } Write-Host "Duration: $Duration minutes" -ForegroundColor Cyan if ($Filter) { Write-Host "Filter: $Filter" -ForegroundColor Cyan } if ($ApexOnly) { Write-Host "Mode: Apex-only logs" -ForegroundColor Yellow } if ($Verbose) { Write-Host "Verbose: Enabled (with timestamps)" -ForegroundColor Yellow } # Color settings $showColors = -not $NoColors if ($NoColors) { 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 ($Verbose) { 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($Duration) while ((Get-Date) -lt $timeoutTime -and $job.State -eq "Running") { $output = Receive-Job $job foreach ($line in $output) { if (Test-ShowLogEntry $line $Filter $ApexOnly) { Write-FormattedLogEntry $line $showColors $Verbose } } Start-Sleep -Milliseconds 100 } # Get any remaining output $finalOutput = Receive-Job $job foreach ($line in $finalOutput) { if (Test-ShowLogEntry $line $Filter $ApexOnly) { Write-FormattedLogEntry $line $showColors $Verbose } } $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 $Duration 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 }