Public/Write-FDALog.ps1

function Write-FDALog {
    <#
    .SYNOPSIS
        Emit a structured log event at the specified level (built-in or custom).
    .DESCRIPTION
        Honors the configured minimum log level (global and per-category).
        Critical-level entries flush synchronously; the rest batch.

    .PARAMETER Level
        Name (e.g. 'Information', 'Audit') or numeric weight.

    .PARAMETER Message
        Log message text.

    .PARAMETER Category
        Logical grouping. Per-category MinLevel overrides apply.

    .PARAMETER Source
        Originating subsystem / cmdlet name.

    .PARAMETER Properties
        Hashtable of structured properties.

    .PARAMETER Exception
        Exception object (or error record) for error/critical events.

    .PARAMETER CorrelationId
        Optional correlation id for cross-event linkage.

    .EXAMPLE
        Write-FDALog -Level Warning -Message 'Slow query' -Category 'Performance' `
                     -Properties @{ DurationMs = 8200 }

    .EXAMPLE
        Write-FDALog -Level 'Audit' -Message 'PII payload preserved' `
                     -Category 'Compliance' -Properties @{ ConsentClaim = $claim }
    #>

    [CmdletBinding()]
    param(
        [Parameter(Mandatory)]
        [object] $Level,

        [Parameter(Mandatory)]
        [string] $Message,

        [string] $Category = 'General',
        [string] $Source = 'Write-FDALog',
        [hashtable] $Properties,
        [object] $Exception,
        [string] $CorrelationId
    )
    if (-not $script:FDAState.Connected) {
        throw 'Not connected. Call Connect-FDAObservability first.'
    }
    $resolved = Resolve-LogLevel -Level $Level

    # Apply min-level filter (global + per-category).
    $config = Get-FDAObservabilityConfig
    $minNumeric = 0
    if ($config -and $config.PSObject.Properties['MinLevelNumeric']) {
        $minNumeric = [int]$config.MinLevelNumeric
    }
    $catKey = 'MinLevelByCategory.' + $Category
    if ($config -and $config.PSObject.Properties[$catKey]) {
        $catCfg = $config.$catKey
        if ($null -ne $catCfg.Numeric) { $minNumeric = [int]$catCfg.Numeric }
    }
    if ($resolved.Numeric -lt $minNumeric) {
        Write-Verbose "Suppressed: level $($resolved.Name)($($resolved.Numeric)) below threshold $minNumeric"
        return
    }

    $caller = Get-FDACallerIdentity
    if (-not $CorrelationId) { $CorrelationId = [guid]::NewGuid().ToString() }

    $exceptionObj = $null
    if ($Exception) {
        $exceptionObj = if ($Exception -is [System.Management.Automation.ErrorRecord]) {
            @{
                Type        = $Exception.Exception.GetType().FullName
                Message     = $Exception.Exception.Message
                StackTrace  = $Exception.ScriptStackTrace
                CategoryInfo= [string]$Exception.CategoryInfo
            }
        } elseif ($Exception -is [Exception]) {
            @{
                Type       = $Exception.GetType().FullName
                Message    = $Exception.Message
                StackTrace = $Exception.StackTrace
            }
        } else { $Exception }
    }

    $record = [pscustomobject]@{
        Timestamp         = (Get-Date).ToUniversalTime().ToString('o')
        EventId           = [guid]::NewGuid().ToString()
        CorrelationId     = $CorrelationId
        SessionId         = $script:FDAState.SessionId
        TenantId          = $caller.TenantId
        UserPrincipalName = $caller.UserPrincipalName
        Source            = $Source
        Category          = $Category
        LevelName         = $resolved.Name
        LevelNumeric      = $resolved.Numeric
        Message           = $Message
        Properties        = $Properties
        Exception         = $exceptionObj
    }
    Add-FDAFlushEntry -TableName 'FDALogEventsRaw' -MappingName 'FDALogEventsRawMapping' -Record $record -LevelNumeric $resolved.Numeric
}