Public/Monitoring/Get-VergeLog.ps1

function Get-VergeLog {
    <#
    .SYNOPSIS
        Retrieves system logs from VergeOS.

    .DESCRIPTION
        Get-VergeLog retrieves log entries from a VergeOS system. Logs include
        audit events, messages, warnings, errors, and critical events from
        various system components.

    .PARAMETER Level
        Filter logs by level: Critical, Error, Warning, Message, Audit, Summary, Debug.
        Accepts multiple values.

    .PARAMETER ObjectType
        Filter logs by object type such as VM, Network, Tenant, User, System, etc.

    .PARAMETER User
        Filter logs by the user who performed the action.

    .PARAMETER Text
        Filter logs containing this text (case-insensitive search).

    .PARAMETER Since
        Return logs since this date/time.

    .PARAMETER Before
        Return logs before this date/time.

    .PARAMETER Limit
        Maximum number of log entries to return. Default is 100.

    .PARAMETER ErrorsOnly
        Shortcut to filter for Error and Critical level logs only.

    .PARAMETER Server
        The VergeOS connection to use. Defaults to the current default connection.

    .EXAMPLE
        Get-VergeLog

        Retrieves the most recent 100 log entries.

    .EXAMPLE
        Get-VergeLog -Level Error, Critical

        Retrieves error and critical log entries.

    .EXAMPLE
        Get-VergeLog -ErrorsOnly

        Shortcut to retrieve only error and critical logs.

    .EXAMPLE
        Get-VergeLog -ObjectType VM -Limit 50

        Retrieves the last 50 VM-related log entries.

    .EXAMPLE
        Get-VergeLog -Since (Get-Date).AddHours(-1)

        Retrieves logs from the last hour.

    .EXAMPLE
        Get-VergeLog -User "admin" -Text "powered on"

        Retrieves logs for actions by admin user containing "powered on".

    .OUTPUTS
        PSCustomObject with PSTypeName 'Verge.Log'

    .NOTES
        Logs are retained for approximately 31 days by default.
    #>

    [CmdletBinding()]
    [OutputType([PSCustomObject])]
    param(
        [Parameter()]
        [ValidateSet('Critical', 'Error', 'Warning', 'Message', 'Audit', 'Summary', 'Debug')]
        [string[]]$Level,

        [Parameter()]
        [ValidateSet(
            'VM', 'Network', 'Tenant', 'User', 'System', 'Node', 'Cluster',
            'File', 'Group', 'Permission', 'SMTP', 'Task', 'Site',
            'SystemSnapshot', 'CatalogRepository', 'OIDCApplication',
            'ServiceContainer', 'NASService', 'VMImport', 'VMwareBackup',
            'SnapshotProfile', 'ImportExport', 'Update', 'Other'
        )]
        [string]$ObjectType,

        [Parameter()]
        [string]$User,

        [Parameter()]
        [string]$Text,

        [Parameter()]
        [datetime]$Since,

        [Parameter()]
        [datetime]$Before,

        [Parameter()]
        [ValidateRange(1, 10000)]
        [int]$Limit = 100,

        [Parameter()]
        [switch]$ErrorsOnly,

        [Parameter()]
        [object]$Server
    )

    begin {
        # Resolve connection
        if (-not $Server) {
            $Server = $script:DefaultConnection
        }
        if (-not $Server) {
            throw [System.InvalidOperationException]::new(
                'Not connected to VergeOS. Use Connect-VergeOS to establish a connection.'
            )
        }

        # Map object types to API values
        $objectTypeMap = @{
            'VM'                = 'vm'
            'Network'           = 'vnet'
            'Tenant'            = 'tenant'
            'User'              = 'user'
            'System'            = 'system'
            'Node'              = 'node'
            'Cluster'           = 'cluster'
            'File'              = 'file'
            'Group'             = 'group'
            'Permission'        = 'permission'
            'SMTP'              = 'smtp'
            'Task'              = 'task'
            'Site'              = 'site'
            'SystemSnapshot'    = 'cloud_snapshots'
            'CatalogRepository' = 'catalog_repository'
            'OIDCApplication'   = 'oidc_application'
            'ServiceContainer'  = 'service_container'
            'NASService'        = 'vm_service'
            'VMImport'          = 'vm_import'
            'VMwareBackup'      = 'vmware_container'
            'SnapshotProfile'   = 'snapshot_profile'
            'ImportExport'      = 'import_export'
            'Update'            = 'updates'
            'Other'             = 'other'
        }

        # Reverse map for display
        $objectTypeDisplayMap = @{
            'vm'                  = 'VM'
            'vnet'                = 'Network'
            'tenant'              = 'Tenant'
            'user'                = 'User'
            'system'              = 'System'
            'node'                = 'Node'
            'cluster'             = 'Cluster'
            'file'                = 'File'
            'group'               = 'Group'
            'permission'          = 'Permission'
            'smtp'                = 'SMTP'
            'task'                = 'Task'
            'site'                = 'Site'
            'cloud_snapshots'     = 'SystemSnapshot'
            'catalog_repository'  = 'CatalogRepository'
            'oidc_application'    = 'OIDCApplication'
            'service_container'   = 'ServiceContainer'
            'vm_service'          = 'NASService'
            'vm_import'           = 'VMImport'
            'vmware_container'    = 'VMwareBackup'
            'snapshot_profile'    = 'SnapshotProfile'
            'import_export'       = 'ImportExport'
            'updates'             = 'Update'
            'other'               = 'Other'
        }
    }

    process {
        # Build query parameters
        $queryParams = @{}
        $filters = [System.Collections.Generic.List[string]]::new()

        # Handle ErrorsOnly shortcut
        if ($ErrorsOnly) {
            $Level = @('Error', 'Critical')
        }

        # Filter by level
        if ($Level -and $Level.Count -gt 0) {
            # Force array output to avoid string indexing issues
            $levelFilters = @($Level | ForEach-Object { "level eq '$($_.ToLower())'" })
            if ($levelFilters.Count -eq 1) {
                $filters.Add($levelFilters[0])
            }
            else {
                $filters.Add("($($levelFilters -join ' or '))")
            }
        }

        # Filter by object type
        if ($ObjectType) {
            $apiObjectType = $objectTypeMap[$ObjectType]
            $filters.Add("object_type eq '$apiObjectType'")
        }

        # Filter by user
        if ($User) {
            $filters.Add("user ct '$User'")
        }

        # Filter by text
        if ($Text) {
            $filters.Add("text ct '$Text'")
        }

        # Filter by timestamp (logs use microseconds)
        if ($Since) {
            $sinceUs = [DateTimeOffset]::new($Since).ToUnixTimeMilliseconds() * 1000
            $filters.Add("timestamp ge $sinceUs")
        }

        if ($Before) {
            $beforeUs = [DateTimeOffset]::new($Before).ToUnixTimeMilliseconds() * 1000
            $filters.Add("timestamp lt $beforeUs")
        }

        # Apply filters
        if ($filters.Count -gt 0) {
            $queryParams['filter'] = $filters -join ' and '
        }

        # Request fields
        $queryParams['fields'] = @(
            '$key'
            'level'
            'text'
            'timestamp'
            'user'
            'object_type'
            'object_name'
        ) -join ','

        $queryParams['sort'] = '-timestamp'
        $queryParams['limit'] = $Limit

        try {
            Write-Verbose "Querying logs from $($Server.Server)"
            $response = Invoke-VergeAPI -Method GET -Endpoint 'logs' -Query $queryParams -Connection $Server

            $logs = if ($response -is [array]) { $response } else { @($response) }

            foreach ($log in $logs) {
                if (-not $log -or $null -eq $log.'$key') {
                    continue
                }

                # Map level to display name
                $levelDisplay = switch ($log.level) {
                    'critical' { 'Critical' }
                    'error'    { 'Error' }
                    'warning'  { 'Warning' }
                    'message'  { 'Message' }
                    'audit'    { 'Audit' }
                    'summary'  { 'Summary' }
                    'debug'    { 'Debug' }
                    default    { $log.level }
                }

                # Map object type to display name
                $objectTypeDisplay = if ($objectTypeDisplayMap.ContainsKey($log.object_type)) {
                    $objectTypeDisplayMap[$log.object_type]
                }
                else {
                    $log.object_type
                }

                # Convert timestamp from microseconds
                $timestamp = if ($log.timestamp) {
                    [DateTimeOffset]::FromUnixTimeMilliseconds($log.timestamp / 1000).LocalDateTime
                }
                else {
                    $null
                }

                $output = [PSCustomObject]@{
                    PSTypeName = 'Verge.Log'
                    Key        = [long]$log.'$key'
                    Level      = $levelDisplay
                    Text       = $log.text
                    Timestamp  = $timestamp
                    User       = $log.user
                    ObjectType = $objectTypeDisplay
                    ObjectName = $log.object_name
                }

                $output | Add-Member -MemberType NoteProperty -Name '_Connection' -Value $Server -Force
                Write-Output $output
            }
        }
        catch {
            $PSCmdlet.ThrowTerminatingError($_)
        }
    }
}