private/Start-LoggingManager.ps1

function Start-LoggingManager {
    [CmdletBinding()]
    param(
        [TimeSpan]$ConsumerStartupTimeout = "00:00:10"
    )

    New-Variable -Name LoggingEventQueue    -Scope Script -Value ([System.Collections.Concurrent.BlockingCollection[hashtable]]::new(100))
    New-Variable -Name LoggingRunspace      -Scope Script -Option ReadOnly -Value ([hashtable]::Synchronized(@{ }))
    New-Variable -Name TargetsInitSync      -Scope Script -Option ReadOnly -Value ([System.Threading.ManualResetEventSlim]::new($false))

    $Script:InitialSessionState = [initialsessionstate]::CreateDefault()

    if ($Script:InitialSessionState.psobject.Properties['ApartmentState']) {
        $Script:InitialSessionState.ApartmentState = [System.Threading.ApartmentState]::MTA
    }

    # Importing variables into runspace
    foreach ($sessionVariable in 'ScriptRoot', 'LevelNames', 'Logging', 'LoggingEventQueue', 'TargetsInitSync') {
        $Value = Get-Variable -Name $sessionVariable -ErrorAction Continue -ValueOnly
        Write-Verbose "Importing variable $sessionVariable`: $Value into runspace"
        $v = New-Object System.Management.Automation.Runspaces.SessionStateVariableEntry -ArgumentList $sessionVariable, $Value, '', ([System.Management.Automation.ScopedItemOptions]::AllScope)
        $Script:InitialSessionState.Variables.Add($v)
    }

    # Importing functions into runspace
    foreach ($Function in 'Replace-Token', 'Initialize-LoggingTarget', 'Get-LevelNumber') {
        Write-Verbose "Importing function $($Function) into runspace"
        $Body = Get-Content Function:\$Function
        $f = New-Object System.Management.Automation.Runspaces.SessionStateFunctionEntry -ArgumentList $Function, $Body
        $Script:InitialSessionState.Commands.Add($f)
    }

    #Setup runspace
    $Script:LoggingRunspace.Runspace = [runspacefactory]::CreateRunspace($Script:InitialSessionState)
    $Script:LoggingRunspace.Runspace.Name = 'LoggingQueueConsumer'
    $Script:LoggingRunspace.Runspace.Open()
    $Script:LoggingRunspace.Runspace.SessionStateProxy.SetVariable('ParentHost', $Host)
    $Script:LoggingRunspace.Runspace.SessionStateProxy.SetVariable('VerbosePreference', $VerbosePreference)

    # Spawn Logging Consumer
    $Consumer = {
        Initialize-LoggingTarget

        $TargetsInitSync.Set(); # Signal to the parent runspace that logging targets have been loaded

        foreach ($Log in $Script:LoggingEventQueue.GetConsumingEnumerable()) {
            if ($Script:Logging.EnabledTargets) {
                $ParentHost.NotifyBeginApplication()

                try {
                    #Enumerating through a collection is intrinsically not a thread-safe procedure
                    for ($targetEnum = $Script:Logging.EnabledTargets.GetEnumerator(); $targetEnum.MoveNext(); ) {
                        [string] $LoggingTarget = $targetEnum.Current.key
                        [hashtable] $TargetConfiguration = $targetEnum.Current.Value
                        $Logger = [scriptblock] $Script:Logging.Targets[$LoggingTarget].Logger

                        $targetLevelNo = Get-LevelNumber -Level $TargetConfiguration.Level

                        if ($Log.LevelNo -ge $targetLevelNo) {
                            Invoke-Command -ScriptBlock $Logger -ArgumentList @($Log.PSObject.Copy(), $TargetConfiguration)
                        }
                    }
                }
                catch {
                    $ParentHost.UI.WriteErrorLine($_)
                }
                finally {
                    $ParentHost.NotifyEndApplication()
                }
            }
        }
    }

    $Script:LoggingRunspace.Powershell = [Powershell]::Create().AddScript($Consumer, $true)
    $Script:LoggingRunspace.Powershell.Runspace = $Script:LoggingRunspace.Runspace
    $Script:LoggingRunspace.Handle = $Script:LoggingRunspace.Powershell.BeginInvoke()

    #region Handle Module Removal
    $OnRemoval = {
        $Module = Get-Module Logging

        if ($Module) {
            $Module.Invoke({
                Wait-Logging
                Stop-LoggingManager
            })
        }

        [System.GC]::Collect()
    }

    # This scriptblock would be called within the module scope
    $ExecutionContext.SessionState.Module.OnRemove += $OnRemoval

    # This scriptblock would be called within the global scope and wouldn't have access to internal module variables and functions that we need
    $Script:LoggingRunspace.EngineEventJob = Register-EngineEvent -SourceIdentifier ([System.Management.Automation.PsEngineEvent]::Exiting) -Action $OnRemoval
    #endregion Handle Module Removal

    if(-not $TargetsInitSync.Wait($ConsumerStartupTimeout)){
        throw 'Timed out while waiting for logging consumer to start up'
    }
}