internal/functions/Write-AzOpsMessage.ps1

function Write-AzOpsMessage {
    <#
        .SYNOPSIS
            Wrapper function to emit logs with Write-PSFMessage and ApplicationInsights.
        .PARAMETER ApplicationInsights
            Boolean to indicate if function should emit logs to ApplicationInsights.
        .PARAMETER Data
            Additional data points.
        .PARAMETER ErrorRecord
            Additional exception information from catch.
        .PARAMETER LogLevel
            Set level of message severity.
        .PARAMETER LogString
            String used to construct message.
        .PARAMETER LogStringValues
            String array used to enrich to message.
        .PARAMETER Metric
            Used to output metric.
        .PARAMETER MetricName
            Override FunctionName as MetricName.
        .PARAMETER FunctionName
            Static set FunctionName in log, otherwise Write-AzOpsMessage collects this from callStack.
        .PARAMETER ModuleName
            Static set ModuleName in log, otherwise Write-AzOpsMessage collects this from callStack.
        .PARAMETER Target
            The object that was processed when invoked.
        .EXAMPLE
            Write-AzOpsMessage -LogLevel Verbose
    #>


    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $false)]
        [bool]
        $ApplicationInsights = (Get-PSFConfigValue -FullName 'AzOps.Core.ApplicationInsights'),
        [Parameter(Mandatory = $false)]
        [hashtable]
        $Data,
        [Parameter(Mandatory = $false)]
        [System.Management.Automation.ErrorRecord]
        $ErrorRecord,
        [Parameter(Mandatory = $true)]
        [ValidateSet("Critical", "Debug", "Error", "Host", "Important", "InternalComment", "Output", "Significant", "SomewhatVerbose", "System", "Verbose", "VeryVerbose", "Warning")]
        [string]
        $LogLevel,
        [Parameter(Mandatory = $true)]
        [string]
        $LogString,
        [Parameter(Mandatory = $false)]
        [string[]]
        $LogStringValues,
        [Parameter(Mandatory = $false)]
        [int]
        $Metric,
        [Parameter(Mandatory = $false)]
        [string]
        $MetricName,
        [Parameter(Mandatory = $false)]
        [string]
        $FunctionName,
        [Parameter(Mandatory = $false)]
        [string]
        $ModuleName,
        [Parameter(Mandatory = $false)]
        [string]
        $Target
    )
    begin {
        # Collect callStack information to enrich logs with accurate caller information
        $callStack = (Get-PSCallStack)[1]
        if (-not $FunctionName) { $FunctionName = $callStack.Command }
        if (-not $ModuleName) { $ModuleName = $callstack.InvocationInfo.MyCommand.ModuleName }
        if (-not $ModuleName) { $ModuleName = "<Unknown>" }
        $File = $callStack.Position.File
        $Line = $callStack.Position.StartLineNumber
    }
    process {
        # Evaluate message verbosity
        $logLevels = @{
            "Critical" = 1
            "Debug" = 8
            "Error" = 667
            "Host" = 2
            "Important" = 2
            "InternalComment" = 9
            "Output" = 2
            "Significant" = 3
            "SomewhatVerbose" = 6
            "System" = 7
            "Verbose" = 5
            "VeryVerbose" = 4
            "Warning" = 666
        }
        $intLevel = $logLevels[$LogLevel]
        [int]$messageInfo = Get-PSFConfigValue -FullName 'PSFramework.Message.Info.Maximum'
        if (($messageInfo -lt $intLevel) -or ([PSFramework.Message.MessageHost]::MinimumInformation -gt $intLevel) -and ($intLevel -notin 666..667)) {
            # Message is below desired log verbosity, skip
            return
        }
        # Generate unique logTag information, used to identify each log entry
        $logTag = (New-Guid).Guid + '-' + (Get-Date).TimeOfDay.TotalMilliseconds
        # Pass information to Write-PSFMessage, to emit local log
        $params = @{
            Level = $LogLevel
            String = $LogString
            StringValues = $LogStringValues
            Tag = $logTag
            ModuleName = $ModuleName
            FunctionName = $FunctionName
            File = $File
            Line = $Line
            Target = $Target
            ErrorRecord = $ErrorRecord
            Data = $Data
        }
        Write-PSFMessage @params
        if ($env:APPLICATIONINSIGHTS_CONNECTION_STRING -ne '' -and $ApplicationInsights -eq $true) {
            # Initiate export of log to ApplicationInsights
            try {
                # Gather log generated by Write-PSFMessage with retry/backoff logic
                $logMessage = Invoke-AzOpsScriptBlock -ArgumentList $FunctionName, $LogTag -ScriptBlock {
                    Get-PSFMessage -FunctionName $FunctionName | Where-Object { $_.Tags -eq $LogTag } -ErrorAction Stop
                } -RetryCount 5 -RetryWait 1 -RetryType Exponential -ErrorAction Stop
            }
            catch {
                Write-PSFMessage -Level Warning -Message 'Get-PSFMessage failing: {0}' -StringValues $_
            }
            if (-not $logMessage) {
                # No log message, return
                return
            }
            if ($logMessage.Count -gt 1) {
                # Log message has duplicate, return
                Write-PSFMessage -Level Warning -Message 'Get-PSFMessage has duplicate entires for {0} with tag {1}' -StringValues $LogString, $logTag
                return
            }
            # Construct ApplicationInsights object
            $azOpsMessage = [Microsoft.ApplicationInsights.TelemetryClient]::new()
            # Set ApplicationInsights connectionstring
            $azOpsMessage.TelemetryConfiguration.ConnectionString = $env:APPLICATIONINSIGHTS_CONNECTIONSTRING
            $azOpsMessage.Context.Session.Id = $PID
            # Adjust logMessage.Level to align with ApplicationInsights
            switch ($logMessage.Level) {
                { ($_ -eq "SomewhatVerbose") -or ($_ -eq "System") -or ($_ -eq "Debug") -or ($_ -eq "InternalComment") } {
                    $level = "Verbose"
                    break
                }
                { ($_ -eq "Important") -or ($_ -eq "Output") -or ($_ -eq "Host") -or ($_ -eq "Significant") -or ($_ -eq "VeryVerbose") } {
                    $level = "Information"
                    break
                }
                default {
                    $level = ($logMessage.Level).ToString()
                    break
                }
            }
            # Create ApplicationInsights customDimensions
            $logProperties = [System.Collections.Generic.Dictionary[string, string]]::new()
            $logProperties.Add("Timestamp", $logMessage.Timestamp)
            $logProperties.Add("ModuleName", $logMessage.ModuleName)
            $logProperties.Add("FunctionName", $logMessage.FunctionName)
            $logProperties.Add("TargetObject", $logMessage.TargetObject)
            $logProperties.Add("Data", $logmessage.Data)
            $logProperties.Add("Runspace", $logMessage.Runspace)
            $logProperties.Add("ComputerName", $logMessage.ComputerName)
            $logProperties.Add("File", $logMessage.File)
            $logProperties.Add("Line", $logMessage.Line)
            $logProperties.Add("CallStack", $logMessage.CallStack)
            $logProperties.Add("ErrorRecord", $logMessage.ErrorRecord)
            $logProperties.Add("String", $logMessage.String)
            # Create TrackTrace
            $azOpsMessage.TrackTrace($logMessage.Message, $level, $logProperties)
            # Create TrackEvent
            $azOpsMessage.TrackEvent($logMessage.FunctionName)
            if ($Metric) {
                # Create TrackMetric
                if (-not $MetricName) {
                    $azOpsMessage.TrackMetric($logMessage.FunctionName, $Metric)
                }
                else {
                    $azOpsMessage.TrackMetric($MetricName, $Metric)
                }
            }
            if ($level -eq "Critical" -or $level -eq "Error" -or $level -eq "Warning") {
                # Create TrackException
                $azOpsMessage.TrackException($logMessage.Message, $logProperties)
            }
            # Immediately emit log to ApplicationInsights
            $azOpsMessage.Flush()
        }
    }
}