private/Start-LoggingManager.ps1
function Start-LoggingManager { [CmdletBinding()] param() 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(@{ })) $InitialSessionState = [initialsessionstate]::CreateDefault() if ($InitialSessionState.psobject.Properties['ApartmentState']) { $InitialSessionState.ApartmentState = [System.Threading.ApartmentState]::MTA } # Importing variables into runspace foreach ($sessionVariable in 'ScriptRoot', 'LevelNames', 'Logging', 'LoggingEventQueue') { $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) $InitialSessionState.Variables.Add($v) } # Importing functions into runspace foreach ($Function in 'Replace-Token', 'Initialize-LoggingTarget') { Write-Verbose "Importing function $($Function) into runspace" $Body = Get-Content Function:\$Function $f = New-Object System.Management.Automation.Runspaces.SessionStateFunctionEntry -ArgumentList $Function, $Body $InitialSessionState.Commands.Add($f) } #Setup runspace $LoggingRunspace.Runspace = [runspacefactory]::CreateRunspace($InitialSessionState) $LoggingRunspace.Runspace.Name = 'LoggingQueueConsumer' $LoggingRunspace.Runspace.Open() $LoggingRunspace.Runspace.SessionStateProxy.SetVariable('ParentHost', $Host) $LoggingRunspace.Runspace.SessionStateProxy.SetVariable('VerbosePreference', $VerbosePreference) # Spawn Logging Consumer $Consumer = { Initialize-LoggingTarget Write-Verbose 'Spinning up consumer runspace' foreach ($Log in $LoggingEventQueue.GetConsumingEnumerable()) { if ($Logging.EnabledTargets) { $ParentHost.NotifyBeginApplication() try { #Enumerating through a collection is intrinsically not a thread-safe procedure for ($targetEnum = $Logging.EnabledTargets.GetEnumerator(); $targetEnum.MoveNext(); ) { [string] $LoggingTarget = $targetEnum.Current.key [hashtable] $TargetConfiguration = $targetEnum.Current.Value $Logger = [scriptblock] $Script:Logging.Targets[$LoggingTarget].Logger if ($Log.LevelNo -ge $TargetConfiguration.LevelNo) { Invoke-Command -ScriptBlock $Logger -ArgumentList @($Log, $TargetConfiguration) } } } catch { $ParentHost.UI.WriteErrorLine($_) } finally { $ParentHost.NotifyEndApplication() } } } Write-Verbose 'Killing consumer runspace' } $LoggingRunspace.Powershell = [Powershell]::Create().AddScript($Consumer, $true) $LoggingRunspace.Powershell.Runspace = $LoggingRunspace.Runspace $LoggingRunspace.Handle = $LoggingRunspace.Powershell.BeginInvoke() #region Handle Module Removal $OnRemoval = { $Script:LoggingEventQueue.CompleteAdding() $Script:LoggingEventQueue.Dispose() Write-Verbose -Message ('Stopping: {0}' -f $LoggingRunspace.Powershell.InstanceId) [void] $LoggingRunspace.Powershell.EndInvoke($LoggingRunspace.Handle) [void] $LoggingRunspace.Powershell.Dispose() Remove-Variable Logging -Scope Script -Force Remove-Variable Defaults -Scope Script -Force Remove-Variable LevelNames -Scope Script -Force Remove-Variable LoggingRunspace -Scope Script -Force Remove-Variable LoggingEventQueue -Scope Script -Force [System.GC]::Collect() } $ExecutionContext.SessionState.Module.OnRemove += $OnRemoval $Script:LoggingRunspace.EngineEvent = Register-EngineEvent -SourceIdentifier ([System.Management.Automation.PsEngineEvent]::Exiting) -Action $OnRemoval #endregion Handle Module Removal } |