PSLogs.psm1
#Region './prefix.ps1' -1 # The content of this file will be prepended to the top of the psm1 module file. This is useful for custom module setup is needed on import. $ScriptPath = Split-Path $MyInvocation.MyCommand.Path $PSModule = $ExecutionContext.SessionState.Module $PSModuleRoot = $PSModule.ModuleBase #EndRegion './prefix.ps1' 5 #Region './Private/Format-Pattern.ps1' -1 <# .DESCRIPTION Replaces the tokens present in the pattern with the values given inside the source (log) object. .PARAMETER Pattern Parameter The pattern that defines tokens and possible operations onto them. .PARAMETER Source Parameter Log object providing values, if wildcard parameter is not given .PARAMETER Wildcard Parameter If this parameter is given, all tokens are replaced by the wildcard character. .EXAMPLE Format-Pattern -Pattern %{timestamp} -Wildcard #> function Format-Pattern { [CmdletBinding()] [OutputType([String])] param( [AllowEmptyString()] [Parameter(Mandatory)] [string] $Pattern, [object] $Source, [switch] $Wildcard ) [string] $result = $Pattern [regex] $tokenMatcher = '%{(?<token>\w+?)?(?::?\+(?<datefmtU>(?:%[ABCDGHIMRSTUVWXYZabcdeghjklmnprstuwxy].*?)+))?(?::?\+(?<datefmt>(?:.*?)+))?(?::(?<padding>-?\d+))?}' $tokenMatches = @() $tokenMatches += $tokenMatcher.Matches($Pattern) [array]::Reverse($tokenMatches) foreach ($match in $tokenMatches) { $formattedEntry = [string]::Empty $tokenContent = [string]::Empty $token = $match.Groups['token'].value $datefmt = $match.Groups['datefmt'].value $datefmtU = $match.Groups['datefmtU'].value $padding = $match.Groups['padding'].value if ($Wildcard.IsPresent) { $formattedEntry = '*' } else { [hashtable] $dateParam = @{ } if (-not [string]::IsNullOrWhiteSpace($token)) { $tokenContent = $Source.$token $dateParam['Date'] = $tokenContent } if (-not [string]::IsNullOrWhiteSpace($datefmtU)) { $formattedEntry = Get-Date @dateParam -UFormat $datefmtU } elseif (-not [string]::IsNullOrWhiteSpace($datefmt)) { $formattedEntry = Get-Date @dateParam -Format $datefmt } else { $formattedEntry = $tokenContent } if ($padding) { $formattedEntry = "{0,$padding}" -f $formattedEntry } } $result = $result.Substring(0, $match.Index) + $formattedEntry + $result.Substring($match.Index + $match.Length) } return $result } #EndRegion './Private/Format-Pattern.ps1' 85 #Region './Private/Get-LevelName.ps1' -1 function Get-LevelName { [CmdletBinding()] param( [int] $Level ) $l = $Script:LevelNames[$Level] if ($l) { return $l } else { return ('Level {0}' -f $Level) } } #EndRegion './Private/Get-LevelName.ps1' 18 #Region './Private/Get-LevelNumber.ps1' -1 function Get-LevelNumber { [CmdletBinding()] param( $Level ) if ($Level -is [int] -and $Level -in $Script:LevelNames.Keys) { return $Level } elseif ([string] $Level -eq $Level -and $Level -in $Script:LevelNames.Keys) { return $Script:LevelNames[$Level] } else { throw ('Level not a valid integer or a valid string: {0}' -f $Level) } } #EndRegion './Private/Get-LevelNumber.ps1' 20 #Region './Private/Get-LevelsName.ps1' -1 Function Get-LevelsName { [CmdletBinding()] param() return $Script:LevelNames.Keys | Where-Object {$_ -isnot [int]} | Sort-Object } #EndRegion './Private/Get-LevelsName.ps1' 7 #Region './Private/Initialize-LoggingTarget.ps1' -1 function Initialize-LoggingTarget { param() $targets = @() $targets += Get-ChildItem "$ScriptRoot\include" -Filter '*.ps1' if ((![String]::IsNullOrWhiteSpace($Script:Logging.CustomTargets)) -and (Test-Path -Path $Script:Logging.CustomTargets -PathType Container)) { $targets += Get-ChildItem -Path $Script:Logging.CustomTargets -Filter '*.ps1' } foreach ($target in $targets) { $module = . $target.FullName $Script:Logging.Targets[$module.Name] = @{ Init = $module.Init Logger = $module.Logger Description = $module.Description Defaults = $module.Configuration ParamsRequired = $module.Configuration.GetEnumerator() | Where-Object { $_.Value.Required -eq $true } | Select-Object -ExpandProperty Name | Sort-Object } } } #EndRegion './Private/Initialize-LoggingTarget.ps1' 25 #Region './Private/Merge-DefaultConfig.ps1' -1 function Merge-DefaultConfig { param( [string] $Target, [hashtable] $Configuration ) $DefaultConfiguration = $Script:Logging.Targets[$Target].Defaults $ParamsRequired = $Script:Logging.Targets[$Target].ParamsRequired $result = @{} foreach ($Param in $DefaultConfiguration.Keys) { if ($Param -in $ParamsRequired -and $Param -notin $Configuration.Keys) { throw ('Configuration {0} is required for target {1}; please provide one of type {2}' -f $Param, $Target, $DefaultConfiguration[$Param].Type) } if ($Configuration.ContainsKey($Param)) { if ($Configuration[$Param] -is $DefaultConfiguration[$Param].Type) { $result[$Param] = $Configuration[$Param] } else { throw ('Configuration {0} has to be of type {1} for target {2}' -f $Param, $DefaultConfiguration[$Param].Type, $Target) } } else { $result[$Param] = $DefaultConfiguration[$Param].Default } } return $result } #EndRegion './Private/Merge-DefaultConfig.ps1' 30 #Region './Private/New-LoggingDynamicParam.ps1' -1 <# .SYNOPSIS Creates the param used inside the DynamicParam{}-Block .DESCRIPTION New-LoggingDynamicParam creates (or appends) a RuntimeDefinedParameterDictionary with a parameter whos value is validated through a dynamic validate set. .PARAMETER Name displayed parameter name .PARAMETER Level Constructs the validate set out of the currently configured logging level names. .PARAMETER Target Constructs the validate set out of the currently configured logging targets. .PARAMETER DynamicParams Dictionary to be appended. (Useful for multiple dynamic params) .PARAMETER Mandatory Controls if parameter is mandatory for call. Defaults to $true .EXAMPLE DynamicParam{ New-LoggingDynamicParam -Name "Level" -Level -DefaultValue 'Verbose' } DynamicParam{ $dictionary = New-LoggingDynamicParam -Name "Level" -Level New-LoggingDynamicParam -Name "Target" -Target -DynamicParams $dictionary } #> function New-LoggingDynamicParam { [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', '', Justification = 'FP')] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSReviewUnusedParameter', '', Justification = 'FP')] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification = 'Does not alter system state')] [OutputType([System.Management.Automation.RuntimeDefinedParameterDictionary])] [CmdletBinding(DefaultParameterSetName = 'DynamicTarget')] param( [Parameter(Mandatory = $true, ParameterSetName = 'DynamicLevel')] [Parameter(Mandatory = $true, ParameterSetName = 'DynamicTarget')] [String] $Name, [Parameter(Mandatory = $true, ParameterSetName = 'DynamicLevel')] [switch] $Level, [Parameter(Mandatory = $true, ParameterSetName = 'DynamicTarget')] [switch] $Target, [boolean] $Mandatory = $true, [System.Management.Automation.RuntimeDefinedParameterDictionary] $DynamicParams ) if (!$DynamicParams) { $DynamicParams = [System.Management.Automation.RuntimeDefinedParameterDictionary]::new() } $attributeCollection = [System.Collections.ObjectModel.Collection[System.Attribute]]::new() $attribute = [System.Management.Automation.ParameterAttribute]::new() $attribute.ParameterSetName = '__AllParameterSets' $attribute.Mandatory = $Mandatory $attribute.Position = 1 $attributeCollection.Add($attribute) [String[]] $allowedValues = @() switch ($PSCmdlet.ParameterSetName) { 'DynamicTarget' { $allowedValues += $Script:Logging.Targets.Keys } 'DynamicLevel' { $allowedValues += Get-LevelsName } } $validateSetAttribute = [System.Management.Automation.ValidateSetAttribute]::new($allowedValues) $attributeCollection.Add($validateSetAttribute) $dynamicParam = [System.Management.Automation.RuntimeDefinedParameter]::new($Name, [string], $attributeCollection) $DynamicParams.Add($Name, $dynamicParam) return $DynamicParams } #EndRegion './Private/New-LoggingDynamicParam.ps1' 97 #Region './Private/Set-LoggingVariables.ps1' -1 function Set-LoggingVariables { [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseSingularNouns', '', Justification = 'Ignored as of now, this is inherited from the original module. This is a internal module cmdlet so the user is not impacted by this.')] [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification = 'Does not alter system state')] param() #Already setup if ($Script:Logging -and $Script:LevelNames) { return } Write-Verbose -Message 'Setting up vars' $Script:NOTSET = 0 $Script:DEBUG = 10 $Script:VERBOSE = 14 $Script:INFO = 20 $Script:NOTICE = 24 $Script:SUCCESS = 26 $Script:WARNING = 30 $Script:ERROR_ = 40 $Script:CRITICAL = 50 $Script:ALERT = 60 $Script:EMERGENCY = 70 New-Variable -Name LevelNames -Scope Script -Option ReadOnly -Value ([hashtable]::Synchronized(@{ $NOTSET = 'NOTSET' $ERROR_ = 'ERROR' $WARNING = 'WARNING' $INFO = 'INFO' $DEBUG = 'DEBUG' $VERBOSE = 'VERBOSE' $NOTICE = 'NOTICE' $SUCCESS = 'SUCCESS' $CRITICAL = 'CRITICAL' $ALERT = 'ALERT' $EMERGENCY = 'EMERGENCY' 'NOTSET' = $NOTSET 'ERROR' = $ERROR_ 'WARNING' = $WARNING 'INFO' = $INFO 'DEBUG' = $DEBUG 'VERBOSE' = $VERBOSE 'NOTICE' = $NOTICE 'SUCCESS' = $SUCCESS 'CRITICAL' = $CRITICAL 'ALERT' = $ALERT 'EMERGENCY' = $EMERGENCY })) New-Variable -Name ScriptRoot -Scope Script -Option ReadOnly -Value ([System.IO.Path]::GetDirectoryName($MyInvocation.MyCommand.Module.Path)) New-Variable -Name Defaults -Scope Script -Option ReadOnly -Value @{ Level = $LevelNames[$LevelNames['NOTSET']] LevelNo = $LevelNames['NOTSET'] Format = '[%{timestamp:+%Y-%m-%d %T%Z}] [%{level:-7}] %{message}' Timestamp = '%Y-%m-%d %T%Z' CallerScope = 1 } New-Variable -Name Logging -Scope Script -Option ReadOnly -Value ([hashtable]::Synchronized(@{ Level = $Defaults.Level LevelNo = $Defaults.LevelNo Format = $Defaults.Format CallerScope = $Defaults.CallerScope CustomTargets = [String]::Empty Targets = ([System.Collections.Concurrent.ConcurrentDictionary[string, hashtable]]::new([System.StringComparer]::OrdinalIgnoreCase)) EnabledTargets = ([System.Collections.Concurrent.ConcurrentDictionary[string, hashtable]]::new([System.StringComparer]::OrdinalIgnoreCase)) })) } #EndRegion './Private/Set-LoggingVariables.ps1' 70 #Region './Private/Start-LoggingManager.ps1' -1 function Start-LoggingManager { [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification = 'Does not alter system state')] [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 'Format-Pattern', '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 PSLogs 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' } } #EndRegion './Private/Start-LoggingManager.ps1' 117 #Region './Private/Stop-LoggingManager.ps1' -1 function Stop-LoggingManager { [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification = 'Does not alter system state')] param () $Script:LoggingEventQueue.CompleteAdding() $Script:LoggingEventQueue.Dispose() [void] $Script:LoggingRunspace.Powershell.EndInvoke($Script:LoggingRunspace.Handle) [void] $Script:LoggingRunspace.Powershell.Dispose() $ExecutionContext.SessionState.Module.OnRemove = $null Get-EventSubscriber | Where-Object { $_.Action.Id -eq $Script:LoggingRunspace.EngineEventJob.Id } | Unregister-Event Remove-Variable -Scope Script -Force -Name LoggingEventQueue Remove-Variable -Scope Script -Force -Name LoggingRunspace Remove-Variable -Scope Script -Force -Name TargetsInitSync } #EndRegion './Private/Stop-LoggingManager.ps1' 19 #Region './Public/Add-LoggingLevel.ps1' -1 <# .SYNOPSIS Define a new severity level .DESCRIPTION This function add a new severity level to the ones already defined .PARAMETER Level An integer that identify the severity of the level, higher the value higher the severity of the level By default the module defines this levels: NOTSET 0 DEBUG 10 INFO 20 WARNING 30 ERROR 40 .PARAMETER LevelName The human redable name to assign to the level .EXAMPLE PS C:\> Add-LoggingLevel -Level 41 -LevelName CRITICAL .EXAMPLE PS C:\> Add-LoggingLevel -Level 15 -LevelName VERBOSE .LINK https://logging.readthedocs.io/en/latest/functions/Add-LoggingLevel.md .LINK https://logging.readthedocs.io/en/latest/functions/Write-Log.md .LINK https://github.com/EsOsO/Logging/blob/master/Logging/public/Add-LoggingLevel.ps1 #> function Add-LoggingLevel { [CmdletBinding(HelpUri='https://logging.readthedocs.io/en/latest/functions/Add-LoggingLevel.md')] param( [Parameter(Mandatory)] [int] $Level, [Parameter(Mandatory)] [string] $LevelName ) if ($Level -notin $LevelNames.Keys -and $LevelName -notin $LevelNames.Keys) { $LevelNames[$Level] = $LevelName.ToUpper() $LevelNames[$LevelName] = $Level } elseif ($Level -in $LevelNames.Keys -and $LevelName -notin $LevelNames.Keys) { $LevelNames.Remove($LevelNames[$Level]) | Out-Null $LevelNames[$Level] = $LevelName.ToUpper() $LevelNames[$LevelNames[$Level]] = $Level } elseif ($Level -notin $LevelNames.Keys -and $LevelName -in $LevelNames.Keys) { $LevelNames.Remove($LevelNames[$LevelName]) | Out-Null $LevelNames[$LevelName] = $Level } } #EndRegion './Public/Add-LoggingLevel.ps1' 56 #Region './Public/Add-LoggingTarget.ps1' -1 <# .SYNOPSIS Enable a logging target .DESCRIPTION This function configure and enable a logging target .PARAMETER Name The name of the target to enable and configure .PARAMETER Configuration An hashtable containing the configurations for the target .EXAMPLE PS C:\> Add-LoggingTarget -Name Console -Configuration @{Level = 'DEBUG'} .EXAMPLE PS C:\> Add-LoggingTarget -Name File -Configuration @{Level = 'INFO'; Path = 'C:\Temp\script.log'} .LINK https://logging.readthedocs.io/en/latest/functions/Add-LoggingTarget.md .LINK https://logging.readthedocs.io/en/latest/functions/Write-Log.md .LINK https://logging.readthedocs.io/en/latest/AvailableTargets.md .LINK https://github.com/EsOsO/Logging/blob/master/Logging/public/Add-LoggingTarget.ps1 #> function Add-LoggingTarget { [CmdletBinding(HelpUri='https://logging.readthedocs.io/en/latest/functions/Add-LoggingTarget.md')] param( [Parameter(Position = 2)] [hashtable] $Configuration = @{} ) DynamicParam { New-LoggingDynamicParam -Name 'Name' -Target } End { $Script:Logging.EnabledTargets[$PSBoundParameters.Name] = Merge-DefaultConfig -Target $PSBoundParameters.Name -Configuration $Configuration # Special case hack - resolve target file path if it's a relative path # This can't be done in the Init scriptblock of the logging target because that scriptblock gets created in the # log consumer runspace and doesn't inherit the current SessionState. That means that the scriptblock doesn't know the # current working directory at the time when `Add-LoggingTarget` is being called and can't accurately resolve the relative path. if($PSBoundParameters.Name -eq 'File'){ $Script:Logging.EnabledTargets[$PSBoundParameters.Name].Path = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($Configuration.Path) } if ($Script:Logging.Targets[$PSBoundParameters.Name].Init -is [scriptblock]) { & $Script:Logging.Targets[$PSBoundParameters.Name].Init $Script:Logging.EnabledTargets[$PSBoundParameters.Name] } } } #EndRegion './Public/Add-LoggingTarget.ps1' 50 #Region './Public/Get-LoggingAvailableTarget.ps1' -1 <# .SYNOPSIS Returns available logging targets .DESCRIPTION This function returns available logging targtes .EXAMPLE PS C:\> Get-LoggingAvailableTarget .LINK https://logging.readthedocs.io/en/latest/functions/Get-LoggingAvailableTarget.md .LINK https://logging.readthedocs.io/en/latest/functions/Write-Log.md .LINK https://github.com/EsOsO/Logging/blob/master/Logging/public/Get-LoggingAvailableTarget.ps1 #> function Get-LoggingAvailableTarget { [CmdletBinding(HelpUri='https://logging.readthedocs.io/en/latest/functions/Get-LoggingAvailableTarget.md')] param() return $Script:Logging.Targets } #EndRegion './Public/Get-LoggingAvailableTarget.ps1' 21 #Region './Public/Get-LoggingCallerScope.ps1' -1 <# .SYNOPSIS Returns the default caller scope .DESCRIPTION This function returns an int representing the scope where the invocation scope for the caller should be obtained from .EXAMPLE PS C:\> Get-LoggingCallerScope .LINK https://logging.readthedocs.io/en/latest/functions/Get-LoggingCallerScope.md .LINK https://logging.readthedocs.io/en/latest/functions/Write-Log.md .LINK https://logging.readthedocs.io/en/latest/LoggingFormat.md .LINK https://github.com/EsOsO/Logging/blob/master/Logging/public/Get-LoggingCallerScope.ps1 #> function Get-LoggingCallerScope { [CmdletBinding()] param() return $Script:Logging.CallerScope } #EndRegion './Public/Get-LoggingCallerScope.ps1' 23 #Region './Public/Get-LoggingDefaultFormat.ps1' -1 <# .SYNOPSIS Returns the default message format .DESCRIPTION This function returns a string representing the default message format used by enabled targets that don't override it .EXAMPLE PS C:\> Get-LoggingDefaultFormat .LINK https://logging.readthedocs.io/en/latest/functions/Get-LoggingDefaultFormat.md .LINK https://logging.readthedocs.io/en/latest/functions/Write-Log.md .LINK https://logging.readthedocs.io/en/latest/LoggingFormat.md .LINK https://github.com/EsOsO/Logging/blob/master/Logging/public/Get-LoggingDefaultFormat.ps1 #> function Get-LoggingDefaultFormat { [CmdletBinding()] param() return $Script:Logging.Format } #EndRegion './Public/Get-LoggingDefaultFormat.ps1' 23 #Region './Public/Get-LoggingDefaultLevel.ps1' -1 <# .SYNOPSIS Returns the default message level .DESCRIPTION This function returns a string representing the default message level used by enabled targets that don't override it .EXAMPLE PS C:\> Get-LoggingDefaultLevel .LINK https://logging.readthedocs.io/en/latest/functions/Get-LoggingDefaultLevel.md .LINK https://logging.readthedocs.io/en/latest/functions/Write-Log.md .LINK https://github.com/EsOsO/Logging/blob/master/Logging/public/Get-LoggingDefaultLevel.ps1 #> function Get-LoggingDefaultLevel { [CmdletBinding(HelpUri = 'https://logging.readthedocs.io/en/latest/functions/Get-LoggingDefaultLevel.md')] param() return Get-LevelName -Level $Script:Logging.LevelNo } #EndRegion './Public/Get-LoggingDefaultLevel.ps1' 26 #Region './Public/Get-LoggingTarget.ps1' -1 <# .SYNOPSIS Returns enabled logging targets .DESCRIPTION This function returns enabled logging targtes .PARAMETER Name The Name of the target to retrieve, if not passed all configured targets will be returned .EXAMPLE PS C:\> Get-LoggingTarget .EXAMPLE PS C:\> Get-LoggingTarget -Name Console .LINK https://logging.readthedocs.io/en/latest/functions/Get-LoggingTarget.md .LINK https://logging.readthedocs.io/en/latest/functions/Write-Log.md .LINK https://github.com/EsOsO/Logging/blob/master/Logging/public/Get-LoggingTarget.ps1 #> function Get-LoggingTarget { [CmdletBinding(HelpUri = 'https://logging.readthedocs.io/en/latest/functions/Get-LoggingTarget.md')] param( [string] $Name = $null ) if ($PSBoundParameters.Name) { return $Script:Logging.EnabledTargets[$Name] } return $Script:Logging.EnabledTargets } #EndRegion './Public/Get-LoggingTarget.ps1' 31 #Region './Public/Set-LoggingCallerScope.ps1' -1 <# .SYNOPSIS Sets the scope from which to get the caller scope .DESCRIPTION This function sets the scope to obtain information from the caller .PARAMETER CallerScope Integer representing the scope to use to find the caller information. Defaults to 1 which represent the scope of the function where Write-Log is being called from .EXAMPLE PS C:\> Set-LoggingCallerScope -CallerScope 2 .EXAMPLE PS C:\> Set-LoggingCallerScope It sets the caller scope to 1 .LINK https://logging.readthedocs.io/en/latest/functions/Set-LoggingCallerScope.md .LINK https://logging.readthedocs.io/en/latest/functions/Write-Log.md .LINK https://github.com/EsOsO/Logging/blob/master/Logging/public/Set-LoggingCallerScope.ps1 #> function Set-LoggingCallerScope { [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification = 'Does not alter system state')] [CmdletBinding(HelpUri = 'https://logging.readthedocs.io/en/latest/functions/Set-LoggingCallerScope.md')] param( [int]$CallerScope = $Defaults.CallerScope ) Wait-Logging $Script:Logging.CallerScope = $CallerScope } #EndRegion './Public/Set-LoggingCallerScope.ps1' 39 #Region './Public/Set-LoggingCustomTarget.ps1' -1 <# .SYNOPSIS Sets a folder as custom target repository .DESCRIPTION This function sets a folder as a custom target repository. Every *.ps1 file will be loaded as a custom target and available to be enabled for logging to. .PARAMETER Path A valid path containing *.ps1 files that defines new loggin targets .EXAMPLE PS C:\> Set-LoggingCustomTarget -Path C:\Logging\CustomTargets .LINK https://logging.readthedocs.io/en/latest/functions/Set-LoggingCustomTarget.md .LINK https://logging.readthedocs.io/en/latest/functions/CustomTargets.md .LINK https://logging.readthedocs.io/en/latest/functions/Write-Log.md .LINK https://github.com/EsOsO/Logging/blob/master/Logging/public/Set-LoggingCustomTarget.ps1 #> function Set-LoggingCustomTarget { [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification = 'Does not alter system state')] [CmdletBinding(HelpUri = 'https://logging.readthedocs.io/en/latest/functions/Set-LoggingCustomTarget.md')] param( [Parameter(Mandatory)] [ValidateScript({ Test-Path -Path $_ -PathType Container })] [string] $Path ) Write-Verbose 'Stopping Logging Manager' Stop-LoggingManager $Script:Logging.CustomTargets = $Path Write-Verbose 'Starting Logging Manager' Start-LoggingManager } #EndRegion './Public/Set-LoggingCustomTarget.ps1' 45 #Region './Public/Set-LoggingDefaultFormat.ps1' -1 <# .SYNOPSIS Sets a global logging message format .DESCRIPTION This function sets a global logging message format .PARAMETER Format The string used to format the message to log .EXAMPLE PS C:\> Set-LoggingDefaultFormat -Format '[%{level:-7}] %{message}' .EXAMPLE PS C:\> Set-LoggingDefaultFormat It sets the default format as [%{timestamp:+%Y-%m-%d %T%Z}] [%{level:-7}] %{message} .LINK https://logging.readthedocs.io/en/latest/functions/Set-LoggingDefaultFormat.md .LINK https://logging.readthedocs.io/en/latest/functions/LoggingFormat.md .LINK https://logging.readthedocs.io/en/latest/functions/Write-Log.md .LINK https://github.com/EsOsO/Logging/blob/master/Logging/public/Set-LoggingDefaultFormat.ps1 #> function Set-LoggingDefaultFormat { [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification = 'Does not alter system state')] [CmdletBinding(HelpUri = 'https://logging.readthedocs.io/en/latest/functions/Set-LoggingDefaultFormat.md')] param( [string] $Format = $Defaults.Format ) Wait-Logging $Script:Logging.Format = $Format # Setting format on already configured targets foreach ($Target in $Script:Logging.EnabledTargets.Values) { if ($Target.ContainsKey('Format')) { $Target['Format'] = $Script:Logging.Format } } # Setting format on available targets foreach ($Target in $Script:Logging.Targets.Values) { if ($Target.Defaults.ContainsKey('Format')) { $Target.Defaults.Format.Default = $Script:Logging.Format } } } #EndRegion './Public/Set-LoggingDefaultFormat.ps1' 60 #Region './Public/Set-LoggingDefaultLevel.ps1' -1 <# .SYNOPSIS Sets a global logging severity level. .DESCRIPTION This function sets a global logging severity level. Log messages written with a lower logging level will be discarded. .PARAMETER Level The level severity name to set as default for enabled targets .EXAMPLE PS C:\> Set-LoggingDefaultLevel -Level ERROR PS C:\> Write-Log -Level INFO -Message "Test" => Discarded. .LINK https://logging.readthedocs.io/en/latest/functions/Set-LoggingDefaultLevel.md .LINK https://logging.readthedocs.io/en/latest/functions/Write-Log.md .LINK https://github.com/EsOsO/Logging/blob/master/Logging/public/Set-LoggingDefaultLevel.ps1 #> function Set-LoggingDefaultLevel { [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSUseShouldProcessForStateChangingFunctions', '', Justification = 'Does not alter system state')] [CmdletBinding(HelpUri = 'https://logging.readthedocs.io/en/latest/functions/Set-LoggingDefaultLevel.md')] param() DynamicParam { New-LoggingDynamicParam -Name 'Level' -Level } End { $Script:Logging.Level = $PSBoundParameters.Level $Script:Logging.LevelNo = Get-LevelNumber -Level $PSBoundParameters.Level # Setting level on already configured targets foreach ($Target in $Script:Logging.EnabledTargets.Values) { if ($Target.ContainsKey('Level')) { $Target['Level'] = $Script:Logging.Level } } # Setting level on available targets foreach ($Target in $Script:Logging.Targets.Values) { if ($Target.Defaults.ContainsKey('Level')) { $Target.Defaults.Level.Default = $Script:Logging.Level } } } } #EndRegion './Public/Set-LoggingDefaultLevel.ps1' 62 #Region './Public/Wait-Logging.ps1' -1 <# .SYNOPSIS Wait for the message queue to be emptied .DESCRIPTION This function can be used to block the execution of a script waiting for the message queue to be emptied .EXAMPLE PS C:\> Wait-Logging .LINK https://logging.readthedocs.io/en/latest/functions/Wait-Logging.md .LINK https://github.com/EsOsO/Logging/blob/master/Logging/public/Wait-Logging.ps1 #> function Wait-Logging { [CmdletBinding(HelpUri='https://logging.readthedocs.io/en/latest/functions/Wait-Logging.md')] param() #This variable is initiated inside Start-LoggingManager if (!(Get-Variable -Name "LoggingEventQueue" -ErrorAction Ignore)) { return } $start = [datetime]::Now Start-Sleep -Milliseconds 10 while ($Script:LoggingEventQueue.Count -gt 0) { Start-Sleep -Milliseconds 20 <# If errors occure in the consumption of the logging requests, forcefully shutdown function after some time. #> $difference = [datetime]::Now - $start if ($difference.seconds -gt 30) { Write-Error -Message ("{0} :: Wait timeout." -f $MyInvocation.MyCommand) -ErrorAction SilentlyContinue break; } } } #EndRegion './Public/Wait-Logging.ps1' 44 #Region './Public/Write-Log.ps1' -1 <# .SYNOPSIS Emits a log record .DESCRIPTION This function write a log record to configured targets with the matching level .PARAMETER Level The log level of the message. Valid values are DEBUG, INFO, WARNING, ERROR, NOTSET Other custom levels can be added and are a valid value for the parameter INFO is the default .PARAMETER Message The text message to write. .PARAMETER Arguments An array of objects used to format <Message> .PARAMETER Body An object that can contain additional log metadata (used in target like ElasticSearch) .PARAMETER ExceptionInfo Provide an optional ErrorRecord .EXAMPLE PS C:\> Write-Log 'Hello, World!' .EXAMPLE PS C:\> Write-Log -Level ERROR -Message 'Hello, World!' .EXAMPLE PS C:\> Write-Log -Level ERROR -Message 'Hello, {0}!' -Arguments 'World' .EXAMPLE PS C:\> Write-Log -Level ERROR -Message 'Hello, {0}!' -Arguments 'World' -Body @{Server='srv01.contoso.com'} .LINK https://logging.readthedocs.io/en/latest/functions/Write-Log.md .LINK https://logging.readthedocs.io/en/latest/functions/Add-LoggingLevel.md .LINK https://github.com/EsOsO/Logging/blob/master/Logging/public/Write-Log.ps1 #> Function Write-Log { [Diagnostics.CodeAnalysis.SuppressMessageAttribute('PSAvoidOverwritingBuiltInCmdlets', '', Justification = 'This is a judgement call. The argument is that if this module is loaded the user should be considered aware that this is the main cmdlet of the module.')] [CmdletBinding()] param( [Parameter(Position = 2, Mandatory = $true)] [string] $Message, [Parameter(Position = 3, Mandatory = $false)] [array] $Arguments, [Parameter(Position = 4, Mandatory = $false)] [object] $Body = $null, [Parameter(Position = 5, Mandatory = $false)] [System.Management.Automation.ErrorRecord] $ExceptionInfo = $null ) DynamicParam { New-LoggingDynamicParam -Level -Mandatory $false -Name 'Level' $PSBoundParameters['Level'] = 'INFO' } End { $levelNumber = Get-LevelNumber -Level $PSBoundParameters.Level $invocationInfo = (Get-PSCallStack)[$Script:Logging.CallerScope] # Split-Path throws an exception if called with a -Path that is null or empty. [string] $fileName = [string]::Empty if (-not [string]::IsNullOrEmpty($invocationInfo.ScriptName)) { $fileName = Split-Path -Path $invocationInfo.ScriptName -Leaf } $logMessage = [hashtable] @{ timestamp = [datetime]::now timestamputc = [datetime]::UtcNow level = Get-LevelName -Level $levelNumber levelno = $levelNumber lineno = $invocationInfo.ScriptLineNumber pathname = $invocationInfo.ScriptName filename = $fileName caller = $invocationInfo.Command message = [string] $Message rawmessage = [string] $Message body = $Body execinfo = $ExceptionInfo pid = $PID } if ($PSBoundParameters.ContainsKey('Arguments')) { $logMessage['message'] = [string] $Message -f $Arguments $logMessage['args'] = $Arguments } #This variable is initiated via Start-LoggingManager $Script:LoggingEventQueue.Add($logMessage) } } #EndRegion './Public/Write-Log.ps1' 109 #Region './suffix.ps1' -1 # The content of this file will be appended to the end of the psm1 module file. This is useful for custom procesedures after all module functions are loaded. Set-LoggingVariables Start-LoggingManager #Trigger buil #EndRegion './suffix.ps1' 7 |