├── .gitattributes ├── .gitignore ├── .vscode ├── ScriptAnalyzerSettings.psd1 ├── launch.json └── settings.json ├── GitVersion.yml ├── Logging ├── Logging.psd1 ├── Logging.psm1 ├── private │ ├── Format-Pattern.ps1 │ ├── Get-LevelName.ps1 │ ├── Get-LevelNumber.ps1 │ ├── Get-LevelsName.ps1 │ ├── Initialize-LoggingTarget.ps1 │ ├── Merge-DefaultConfig.ps1 │ ├── New-LoggingDynamicParam.ps1 │ ├── Set-LoggingVariables.ps1 │ ├── Start-LoggingManager.ps1 │ └── Stop-LoggingManager.ps1 ├── public │ ├── Add-LoggingLevel.ps1 │ ├── Add-LoggingTarget.ps1 │ ├── Get-LoggingAvailableTarget.ps1 │ ├── Get-LoggingCallerScope.ps1 │ ├── Get-LoggingDefaultFormat.ps1 │ ├── Get-LoggingDefaultLevel.ps1 │ ├── Get-LoggingTarget.ps1 │ ├── Set-LoggingCallerScope.ps1 │ ├── Set-LoggingCustomTarget.ps1 │ ├── Set-LoggingDefaultFormat.ps1 │ ├── Set-LoggingDefaultLevel.ps1 │ ├── Wait-Logging.ps1 │ └── Write-Log.ps1 └── targets │ ├── AzureLogAnalytics.ps1 │ ├── Console.ps1 │ ├── ElasticSearch.ps1 │ ├── Email.ps1 │ ├── File.ps1 │ ├── Seq.ps1 │ ├── Slack.ps1 │ ├── Teams.ps1 │ ├── WebexTeams.ps1 │ └── WinEventLog.ps1 ├── README.md ├── Tests ├── AzureLogAnalytics.Tests.ps1 ├── Console.Tests.ps1 ├── FileTarget.Tests.ps1 ├── Help.Tests.ps1 ├── Logging.Tests.ps1 ├── Teams.Tests.ps1 ├── Test.ps1 ├── WebexTeams.Tests.ps1 ├── WinEventLog.Tests.ps1 └── WriteLogCallstackTokens.Tests.ps1 ├── appveyor.yml ├── build.ps1 ├── build.psake.ps1 ├── docs ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE.md ├── RELEASE.md ├── SUPPORT.md ├── Usage.md ├── functions │ ├── Add-LoggingLevel.md │ ├── Add-LoggingTarget.md │ ├── Get-LoggingAvailableTarget.md │ ├── Get-LoggingCallerScope.md │ ├── Get-LoggingDefaultFormat.md │ ├── Get-LoggingDefaultLevel.md │ ├── Get-LoggingMessageCount.md │ ├── Get-LoggingTarget.md │ ├── Get-LoggingTargetAvailable.md │ ├── Set-LoggingCallerScope.md │ ├── Set-LoggingCustomTarget.md │ ├── Set-LoggingDefaultFormat.md │ ├── Set-LoggingDefaultLevel.md │ ├── Use-LogMessage.md │ ├── Wait-Logging.md │ └── Write-Log.md └── index.md ├── header-mkdocs.yml └── mkdocs.yml /.gitattributes: -------------------------------------------------------------------------------- 1 | # Set the default behavior, in case people don't have core.autocrlf set. 2 | * text=auto 3 | 4 | *.md text eol=crlf 5 | *.yml text eol=crlf -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gitconfig 2 | BuildOutput/ 3 | *.xml 4 | debug.log -------------------------------------------------------------------------------- /.vscode/ScriptAnalyzerSettings.psd1: -------------------------------------------------------------------------------- 1 | @{ 2 | Severity = @('Error','Warning') 3 | 4 | ExcludeRules = @( 5 | 'PSAvoidUsingWriteHost', 6 | 'PSMissingModuleManifestField', 7 | 'PSUseDeclaredVarsMoreThanAssignments', 8 | 'PSUseShouldProcessForStateChangingFunctions', 9 | 'PSAvoidOverwritingBuiltInCmdlets' 10 | ) 11 | } 12 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "type": "PowerShell", 6 | "request": "launch", 7 | "name": "PowerShell Launch (current file)", 8 | "script": "${file}", 9 | "args": [], 10 | "cwd": "${file}" 11 | }, 12 | { 13 | "type": "PowerShell", 14 | "request": "attach", 15 | "name": "PowerShell Attach to Host Process", 16 | "processId": "${command:PickPSHostProcess}", 17 | "runspaceId": 1 18 | }, 19 | { 20 | "type": "PowerShell", 21 | "request": "launch", 22 | "name": "PowerShell Interactive Session", 23 | "cwd": "${workspaceRoot}" 24 | } 25 | ] 26 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.trimTrailingWhitespace": true, 3 | "search.exclude": { 4 | "Release": true 5 | }, 6 | "powershell.scriptAnalysis.settingsPath": ".vscode/ScriptAnalyzerSettings.psd1", 7 | "markdownlint.config": { 8 | "MD013": false, 9 | "MD041": false, 10 | "MD033": false 11 | } 12 | } -------------------------------------------------------------------------------- /GitVersion.yml: -------------------------------------------------------------------------------- 1 | mode: Mainline 2 | branches: {} 3 | ignore: 4 | sha: [] 5 | -------------------------------------------------------------------------------- /Logging/Logging.psd1: -------------------------------------------------------------------------------- 1 | # 2 | # Module manifest for module 'PSGet_Logging' 3 | # 4 | # Generated by: Massimo Bonvicini 5 | # 6 | # Generated on: 10/01/2017 7 | # 8 | 9 | @{ 10 | 11 | # Script module or binary module file associated with this manifest. 12 | RootModule = 'Logging.psm1' 13 | 14 | # Version number of this module. 15 | ModuleVersion = '4.8.5' 16 | 17 | # Supported PSEditions 18 | # CompatiblePSEditions = @() 19 | 20 | # ID used to uniquely identify this module 21 | GUID = '25a60f1d-85dd-4ad6-9efc-35fd3894f6c1' 22 | 23 | # Author of this module 24 | Author = 'Massimo Bonvicini' 25 | 26 | # Company or vendor of this module 27 | CompanyName = 'Unknown' 28 | 29 | # Copyright statement for this module 30 | Copyright = '(c) 2015 Massimo Bonvicini. All rights reserved.' 31 | 32 | # Description of the functionality provided by this module 33 | Description = 'Powershell Logging Module' 34 | 35 | # Minimum version of the Windows PowerShell engine required by this module 36 | # PowerShellVersion = '' 37 | 38 | # Name of the Windows PowerShell host required by this module 39 | # PowerShellHostName = '' 40 | 41 | # Minimum version of the Windows PowerShell host required by this module 42 | # PowerShellHostVersion = '' 43 | 44 | # Minimum version of Microsoft .NET Framework required by this module. This prerequisite is valid for the PowerShell Desktop edition only. 45 | # DotNetFrameworkVersion = '' 46 | 47 | # Minimum version of the common language runtime (CLR) required by this module. This prerequisite is valid for the PowerShell Desktop edition only. 48 | # CLRVersion = '' 49 | 50 | # Processor architecture (None, X86, Amd64) required by this module 51 | # ProcessorArchitecture = '' 52 | 53 | # Modules that must be imported into the global environment prior to importing this module 54 | # RequiredModules = @() 55 | 56 | # Assemblies that must be loaded prior to importing this module 57 | # RequiredAssemblies = @() 58 | 59 | # Script files (.ps1) that are run in the caller's environment prior to importing this module. 60 | # ScriptsToProcess = @() 61 | 62 | # Type files (.ps1xml) to be loaded when importing this module 63 | # TypesToProcess = @() 64 | 65 | # Format files (.ps1xml) to be loaded when importing this module 66 | # FormatsToProcess = @() 67 | 68 | # Modules to import as nested modules of the module specified in RootModule/ModuleToProcess 69 | # NestedModules = @() 70 | 71 | # Functions to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no functions to export. 72 | FunctionsToExport = @('Add-LoggingLevel','Add-LoggingTarget','Get-LoggingAvailableTarget','Get-LoggingCallerScope','Get-LoggingDefaultFormat','Get-LoggingDefaultLevel','Get-LoggingTarget','Set-LoggingCallerScope','Set-LoggingCustomTarget','Set-LoggingDefaultFormat','Set-LoggingDefaultLevel','Wait-Logging','Write-Log') 73 | 74 | # Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export. 75 | CmdletsToExport = @() 76 | 77 | # Variables to export from this module 78 | # VariablesToExport = @() 79 | 80 | # Aliases to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no aliases to export. 81 | AliasesToExport = @() 82 | 83 | # DSC resources to export from this module 84 | # DscResourcesToExport = @() 85 | 86 | # List of all modules packaged with this module 87 | # ModuleList = @() 88 | 89 | # List of all files packaged with this module 90 | # FileList = @() 91 | 92 | # Private data to pass to the module specified in RootModule/ModuleToProcess. This may also contain a PSData hashtable with additional module metadata used by PowerShell. 93 | PrivateData = @{ 94 | 95 | PSData = @{ 96 | 97 | # Tags applied to this module. These help with module discovery in online galleries. 98 | Tags = 'Logging','Log','Console','File','ElasticSearch','Slack','Email', 'Windows' 99 | 100 | # A URL to the license for this module. 101 | LicenseUri = 'https://github.com/EsOsO/Logging/blob/master/docs/LICENSE' 102 | 103 | # A URL to the main website for this project. 104 | ProjectUri = 'https://github.com/EsOsO/Logging' 105 | 106 | # A URL to an icon representing this module. 107 | IconUri = 'https://github.com/EsOsO/Logging/blob/static/img/logo.svg' 108 | 109 | # ReleaseNotes of this module 110 | ReleaseNotes = '' 111 | 112 | # External dependent modules of this module 113 | # ExternalModuleDependencies = '' 114 | 115 | } # End of PSData hashtable 116 | 117 | } # End of PrivateData hashtable 118 | 119 | # HelpInfo URI of this module 120 | HelpInfoURI = 'https://logging.readthedocs.io' 121 | 122 | # Default prefix for commands exported from this module. Override the default prefix using Import-Module -Prefix. 123 | # DefaultCommandPrefix = '' 124 | 125 | } 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | -------------------------------------------------------------------------------- /Logging/Logging.psm1: -------------------------------------------------------------------------------- 1 | $ScriptPath = Split-Path $MyInvocation.MyCommand.Path 2 | $PSModule = $ExecutionContext.SessionState.Module 3 | $PSModuleRoot = $PSModule.ModuleBase 4 | 5 | # Dot source public/private functions 6 | $PublicFunctions = @(Get-ChildItem -Path "$ScriptPath\public" -Filter *.ps1 -Recurse -ErrorAction SilentlyContinue) 7 | $PrivateFunctions = @(Get-ChildItem -Path "$ScriptPath\private" -Filter *.ps1 -Recurse -ErrorAction SilentlyContinue) 8 | 9 | $AllFunctions = $PublicFunctions + $PrivateFunctions 10 | foreach ($Function in $AllFunctions) { 11 | try { 12 | . $Function.FullName 13 | } catch { 14 | throw ('Unable to dot source {0}' -f $Function.FullName) 15 | } 16 | } 17 | 18 | Export-ModuleMember -Function $PublicFunctions.BaseName 19 | 20 | Set-LoggingVariables 21 | 22 | Start-LoggingManager 23 | -------------------------------------------------------------------------------- /Logging/private/Format-Pattern.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Replaces the tokens present in the pattern with the values given inside the source (log) object. 4 | 5 | .PARAMETER Pattern 6 | Parameter The pattern that defines tokens and possible operations onto them. 7 | 8 | .PARAMETER Source 9 | Parameter Log object providing values, if wildcard parameter is not given 10 | 11 | .PARAMETER Wildcard 12 | Parameter If this parameter is given, all tokens are replaced by the wildcard character. 13 | 14 | .EXAMPLE 15 | Format-Pattern -Pattern %{timestamp} -Wildcard 16 | #> 17 | function Format-Pattern { 18 | [CmdletBinding()] 19 | [OutputType([String])] 20 | param( 21 | [AllowEmptyString()] 22 | [Parameter(Mandatory)] 23 | [string] 24 | $Pattern, 25 | [object] 26 | $Source, 27 | [switch] 28 | $Wildcard 29 | ) 30 | 31 | [string] $result = $Pattern 32 | [regex] $tokenMatcher = '%{(?\w+?)?(?::?\+(?(?:%[ABCDGHIMRSTUVWXYZabcdeghjklmnprstuwxy].*?)+))?(?::?\+(?(?:.*?)+))?(?::(?-?\d+))?}' 33 | $tokenMatches = @() 34 | $tokenMatches += $tokenMatcher.Matches($Pattern) 35 | [array]::Reverse($tokenMatches) 36 | 37 | foreach ($match in $tokenMatches) { 38 | $formattedEntry = [string]::Empty 39 | $tokenContent = [string]::Empty 40 | 41 | $token = $match.Groups["token"].value 42 | $datefmt = $match.Groups["datefmt"].value 43 | $datefmtU = $match.Groups["datefmtU"].value 44 | $padding = $match.Groups["padding"].value 45 | 46 | if ($Wildcard.IsPresent){ 47 | $formattedEntry = "*" 48 | } 49 | else{ 50 | [hashtable] $dateParam = @{ } 51 | if (-not [string]::IsNullOrWhiteSpace($token)) { 52 | $tokenContent = $Source.$token 53 | $dateParam["Date"] = $tokenContent 54 | } 55 | 56 | if (-not [string]::IsNullOrWhiteSpace($datefmtU)) { 57 | $formattedEntry = Get-Date @dateParam -UFormat $datefmtU 58 | } 59 | elseif (-not [string]::IsNullOrWhiteSpace($datefmt)) { 60 | $formattedEntry = Get-Date @dateParam -Format $datefmt 61 | } 62 | else { 63 | $formattedEntry = $tokenContent 64 | } 65 | 66 | if ($padding) { 67 | $formattedEntry = "{0,$padding}" -f $formattedEntry 68 | } 69 | } 70 | 71 | $result = $result.Substring(0, $match.Index) + $formattedEntry + $result.Substring($match.Index + $match.Length) 72 | } 73 | 74 | return $result 75 | } -------------------------------------------------------------------------------- /Logging/private/Get-LevelName.ps1: -------------------------------------------------------------------------------- 1 | function Get-LevelName { 2 | [CmdletBinding()] 3 | param( 4 | [int] $Level 5 | ) 6 | 7 | $l = $Script:LevelNames[$Level] 8 | if ($l) {return $l} 9 | else {return ('Level {0}' -f $Level)} 10 | } 11 | -------------------------------------------------------------------------------- /Logging/private/Get-LevelNumber.ps1: -------------------------------------------------------------------------------- 1 | function Get-LevelNumber { 2 | [CmdletBinding()] 3 | param( 4 | $Level 5 | ) 6 | if ($Level -is [int] -and $Level -in $Script:LevelNames.Keys) {return $Level} 7 | elseif ([string] $Level -eq $Level -and $Level -in $Script:LevelNames.Keys) {return $Script:LevelNames[$Level]} 8 | else {throw ('Level not a valid integer or a valid string: {0}' -f $Level)} 9 | } 10 | -------------------------------------------------------------------------------- /Logging/private/Get-LevelsName.ps1: -------------------------------------------------------------------------------- 1 | Function Get-LevelsName { 2 | [CmdletBinding()] 3 | param() 4 | 5 | return $Script:LevelNames.Keys | Where-Object {$_ -isnot [int]} | Sort-Object 6 | } 7 | -------------------------------------------------------------------------------- /Logging/private/Initialize-LoggingTarget.ps1: -------------------------------------------------------------------------------- 1 | function Initialize-LoggingTarget { 2 | param() 3 | 4 | $targets = @() 5 | $targets += Get-ChildItem "$ScriptRoot\targets" -Filter '*.ps1' 6 | 7 | if ((![String]::IsNullOrWhiteSpace($Script:Logging.CustomTargets)) -and (Test-Path -Path $Script:Logging.CustomTargets -PathType Container)) { 8 | $targets += Get-ChildItem -Path $Script:Logging.CustomTargets -Filter '*.ps1' 9 | } 10 | 11 | foreach ($target in $targets) { 12 | $module = . $target.FullName 13 | $Script:Logging.Targets[$module.Name] = @{ 14 | Init = $module.Init 15 | Logger = $module.Logger 16 | Description = $module.Description 17 | Defaults = $module.Configuration 18 | ParamsRequired = $module.Configuration.GetEnumerator() | Where-Object {$_.Value.Required -eq $true} | Select-Object -ExpandProperty Name | Sort-Object 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Logging/private/Merge-DefaultConfig.ps1: -------------------------------------------------------------------------------- 1 | function Merge-DefaultConfig { 2 | param( 3 | [string] $Target, 4 | [hashtable] $Configuration 5 | ) 6 | 7 | $DefaultConfiguration = $Script:Logging.Targets[$Target].Defaults 8 | $ParamsRequired = $Script:Logging.Targets[$Target].ParamsRequired 9 | 10 | $result = @{} 11 | 12 | foreach ($Param in $DefaultConfiguration.Keys) { 13 | if ($Param -in $ParamsRequired -and $Param -notin $Configuration.Keys) { 14 | throw ('Configuration {0} is required for target {1}; please provide one of type {2}' -f $Param, $Target, $DefaultConfiguration[$Param].Type) 15 | } 16 | 17 | if ($Configuration.ContainsKey($Param)) { 18 | if ($Configuration[$Param] -is $DefaultConfiguration[$Param].Type) { 19 | $result[$Param] = $Configuration[$Param] 20 | } else { 21 | throw ('Configuration {0} has to be of type {1} for target {2}' -f $Param, $DefaultConfiguration[$Param].Type, $Target) 22 | } 23 | } else { 24 | $result[$Param] = $DefaultConfiguration[$Param].Default 25 | } 26 | } 27 | 28 | return $result 29 | } 30 | -------------------------------------------------------------------------------- /Logging/private/New-LoggingDynamicParam.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Creates the param used inside the DynamicParam{}-Block 4 | 5 | .DESCRIPTION 6 | New-LoggingDynamicParam creates (or appends) a RuntimeDefinedParameterDictionary 7 | with a parameter whos value is validated through a dynamic validate set. 8 | 9 | .PARAMETER Name 10 | displayed parameter name 11 | 12 | .PARAMETER Level 13 | Constructs the validate set out of the currently configured logging level names. 14 | 15 | .PARAMETER Target 16 | Constructs the validate set out of the currently configured logging targets. 17 | 18 | .PARAMETER DynamicParams 19 | Dictionary to be appended. (Useful for multiple dynamic params) 20 | 21 | .PARAMETER Mandatory 22 | Controls if parameter is mandatory for call. Defaults to $true 23 | 24 | .EXAMPLE 25 | DynamicParam{ 26 | New-LoggingDynamicParam -Name "Level" -Level -DefaultValue 'Verbose' 27 | } 28 | 29 | DynamicParam{ 30 | $dictionary = New-LoggingDynamicParam -Name "Level" -Level 31 | New-LoggingDynamicParam -Name "Target" -Target -DynamicParams $dictionary 32 | } 33 | #> 34 | 35 | function New-LoggingDynamicParam { 36 | [OutputType([System.Management.Automation.RuntimeDefinedParameterDictionary])] 37 | [CmdletBinding(DefaultParameterSetName = "DynamicTarget")] 38 | param( 39 | [Parameter(Mandatory = $true, ParameterSetName = "DynamicLevel")] 40 | [Parameter(Mandatory = $true, ParameterSetName = "DynamicTarget")] 41 | [String] 42 | $Name, 43 | [Parameter(Mandatory = $true, ParameterSetName = "DynamicLevel")] 44 | [switch] 45 | $Level, 46 | [Parameter(Mandatory = $true, ParameterSetName = "DynamicTarget")] 47 | [switch] 48 | $Target, 49 | [boolean] 50 | $Mandatory = $true, 51 | [System.Management.Automation.RuntimeDefinedParameterDictionary] 52 | $DynamicParams 53 | ) 54 | 55 | if (!$DynamicParams) { 56 | $DynamicParams = [System.Management.Automation.RuntimeDefinedParameterDictionary]::new() 57 | } 58 | 59 | $attributeCollection = [System.Collections.ObjectModel.Collection[System.Attribute]]::new() 60 | $attribute = [System.Management.Automation.ParameterAttribute]::new() 61 | 62 | $attribute.ParameterSetName = '__AllParameterSets' 63 | $attribute.Mandatory = $Mandatory 64 | $attribute.Position = 1 65 | 66 | $attributeCollection.Add($attribute) 67 | 68 | 69 | [String[]] $allowedValues = @() 70 | 71 | switch ($PSCmdlet.ParameterSetName) { 72 | "DynamicTarget" { 73 | $allowedValues += $Script:Logging.Targets.Keys 74 | } 75 | "DynamicLevel" { 76 | $allowedValues += Get-LevelsName 77 | } 78 | } 79 | 80 | $validateSetAttribute = [System.Management.Automation.ValidateSetAttribute]::new($allowedValues) 81 | $attributeCollection.Add($validateSetAttribute) 82 | 83 | $dynamicParam = [System.Management.Automation.RuntimeDefinedParameter]::new($Name, [string], $attributeCollection) 84 | 85 | $DynamicParams.Add($Name, $dynamicParam) 86 | 87 | return $DynamicParams 88 | } -------------------------------------------------------------------------------- /Logging/private/Set-LoggingVariables.ps1: -------------------------------------------------------------------------------- 1 | function Set-LoggingVariables { 2 | 3 | #Already setup 4 | if ($Script:Logging -and $Script:LevelNames) { 5 | return 6 | } 7 | 8 | Write-Verbose -Message 'Setting up vars' 9 | 10 | $Script:NOTSET = 0 11 | $Script:DEBUG = 10 12 | $Script:INFO = 20 13 | $Script:WARNING = 30 14 | $Script:ERROR_ = 40 15 | 16 | New-Variable -Name LevelNames -Scope Script -Option ReadOnly -Value ([hashtable]::Synchronized(@{ 17 | $NOTSET = 'NOTSET' 18 | $ERROR_ = 'ERROR' 19 | $WARNING = 'WARNING' 20 | $INFO = 'INFO' 21 | $DEBUG = 'DEBUG' 22 | 'NOTSET' = $NOTSET 23 | 'ERROR' = $ERROR_ 24 | 'WARNING' = $WARNING 25 | 'INFO' = $INFO 26 | 'DEBUG' = $DEBUG 27 | })) 28 | 29 | New-Variable -Name ScriptRoot -Scope Script -Option ReadOnly -Value ([System.IO.Path]::GetDirectoryName($MyInvocation.MyCommand.Module.Path)) 30 | New-Variable -Name Defaults -Scope Script -Option ReadOnly -Value @{ 31 | Level = $LevelNames[$LevelNames['NOTSET']] 32 | LevelNo = $LevelNames['NOTSET'] 33 | Format = '[%{timestamp:+%Y-%m-%d %T%Z}] [%{level:-7}] %{message}' 34 | Timestamp = '%Y-%m-%d %T%Z' 35 | CallerScope = 1 36 | } 37 | 38 | New-Variable -Name Logging -Scope Script -Option ReadOnly -Value ([hashtable]::Synchronized(@{ 39 | Level = $Defaults.Level 40 | LevelNo = $Defaults.LevelNo 41 | Format = $Defaults.Format 42 | CallerScope = $Defaults.CallerScope 43 | CustomTargets = [String]::Empty 44 | Targets = ([System.Collections.Concurrent.ConcurrentDictionary[string, hashtable]]::new([System.StringComparer]::OrdinalIgnoreCase)) 45 | EnabledTargets = ([System.Collections.Concurrent.ConcurrentDictionary[string, hashtable]]::new([System.StringComparer]::OrdinalIgnoreCase)) 46 | })) 47 | } -------------------------------------------------------------------------------- /Logging/private/Start-LoggingManager.ps1: -------------------------------------------------------------------------------- 1 | function Start-LoggingManager { 2 | [CmdletBinding()] 3 | param( 4 | [TimeSpan]$ConsumerStartupTimeout = "00:00:10" 5 | ) 6 | 7 | New-Variable -Name LoggingEventQueue -Scope Script -Value ([System.Collections.Concurrent.BlockingCollection[hashtable]]::new(100)) 8 | New-Variable -Name LoggingRunspace -Scope Script -Option ReadOnly -Value ([hashtable]::Synchronized(@{ })) 9 | New-Variable -Name TargetsInitSync -Scope Script -Option ReadOnly -Value ([System.Threading.ManualResetEventSlim]::new($false)) 10 | 11 | $Script:InitialSessionState = [initialsessionstate]::CreateDefault() 12 | 13 | if ($Script:InitialSessionState.psobject.Properties['ApartmentState']) { 14 | $Script:InitialSessionState.ApartmentState = [System.Threading.ApartmentState]::MTA 15 | } 16 | 17 | # Importing variables into runspace 18 | foreach ($sessionVariable in 'ScriptRoot', 'LevelNames', 'Logging', 'LoggingEventQueue', 'TargetsInitSync') { 19 | $Value = Get-Variable -Name $sessionVariable -ErrorAction Continue -ValueOnly 20 | Write-Verbose "Importing variable $sessionVariable`: $Value into runspace" 21 | $v = New-Object System.Management.Automation.Runspaces.SessionStateVariableEntry -ArgumentList $sessionVariable, $Value, '', ([System.Management.Automation.ScopedItemOptions]::AllScope) 22 | $Script:InitialSessionState.Variables.Add($v) 23 | } 24 | 25 | # Importing functions into runspace 26 | foreach ($Function in 'Format-Pattern', 'Initialize-LoggingTarget', 'Get-LevelNumber') { 27 | Write-Verbose "Importing function $($Function) into runspace" 28 | $Body = Get-Content Function:\$Function 29 | $f = New-Object System.Management.Automation.Runspaces.SessionStateFunctionEntry -ArgumentList $Function, $Body 30 | $Script:InitialSessionState.Commands.Add($f) 31 | } 32 | 33 | #Setup runspace 34 | $Script:LoggingRunspace.Runspace = [runspacefactory]::CreateRunspace($Script:InitialSessionState) 35 | $Script:LoggingRunspace.Runspace.Name = 'LoggingQueueConsumer' 36 | $Script:LoggingRunspace.Runspace.Open() 37 | $Script:LoggingRunspace.Runspace.SessionStateProxy.SetVariable('ParentHost', $Host) 38 | $Script:LoggingRunspace.Runspace.SessionStateProxy.SetVariable('VerbosePreference', $VerbosePreference) 39 | 40 | # Spawn Logging Consumer 41 | $Consumer = { 42 | Initialize-LoggingTarget 43 | 44 | $TargetsInitSync.Set(); # Signal to the parent runspace that logging targets have been loaded 45 | 46 | foreach ($Log in $Script:LoggingEventQueue.GetConsumingEnumerable()) { 47 | if ($Script:Logging.EnabledTargets) { 48 | $ParentHost.NotifyBeginApplication() 49 | 50 | try { 51 | #Enumerating through a collection is intrinsically not a thread-safe procedure 52 | for ($targetEnum = $Script:Logging.EnabledTargets.GetEnumerator(); $targetEnum.MoveNext(); ) { 53 | [string] $LoggingTarget = $targetEnum.Current.key 54 | [hashtable] $TargetConfiguration = $targetEnum.Current.Value 55 | $Logger = [scriptblock] $Script:Logging.Targets[$LoggingTarget].Logger 56 | 57 | $targetLevelNo = Get-LevelNumber -Level $TargetConfiguration.Level 58 | 59 | if ($Log.LevelNo -ge $targetLevelNo) { 60 | Invoke-Command -ScriptBlock $Logger -ArgumentList @($Log.PSObject.Copy(), $TargetConfiguration) 61 | } 62 | } 63 | } 64 | catch { 65 | $ParentHost.UI.WriteErrorLine($_) 66 | } 67 | finally { 68 | $ParentHost.NotifyEndApplication() 69 | } 70 | } 71 | } 72 | } 73 | 74 | $Script:LoggingRunspace.Powershell = [Powershell]::Create().AddScript($Consumer, $true) 75 | $Script:LoggingRunspace.Powershell.Runspace = $Script:LoggingRunspace.Runspace 76 | $Script:LoggingRunspace.Handle = $Script:LoggingRunspace.Powershell.BeginInvoke() 77 | 78 | #region Handle Module Removal 79 | $OnRemoval = { 80 | $Module = Get-Module Logging 81 | 82 | if ($Module) { 83 | $Module.Invoke({ 84 | Wait-Logging 85 | Stop-LoggingManager 86 | }) 87 | } 88 | 89 | [System.GC]::Collect() 90 | } 91 | 92 | # This scriptblock would be called within the module scope 93 | $ExecutionContext.SessionState.Module.OnRemove += $OnRemoval 94 | 95 | # This scriptblock would be called within the global scope and wouldn't have access to internal module variables and functions that we need 96 | $Script:LoggingRunspace.EngineEventJob = Register-EngineEvent -SourceIdentifier ([System.Management.Automation.PsEngineEvent]::Exiting) -Action $OnRemoval 97 | #endregion Handle Module Removal 98 | 99 | if(-not $TargetsInitSync.Wait($ConsumerStartupTimeout)){ 100 | throw 'Timed out while waiting for logging consumer to start up' 101 | } 102 | } -------------------------------------------------------------------------------- /Logging/private/Stop-LoggingManager.ps1: -------------------------------------------------------------------------------- 1 | function Stop-LoggingManager { 2 | param () 3 | 4 | $Script:LoggingEventQueue.CompleteAdding() 5 | $Script:LoggingEventQueue.Dispose() 6 | 7 | [void] $Script:LoggingRunspace.Powershell.EndInvoke($Script:LoggingRunspace.Handle) 8 | [void] $Script:LoggingRunspace.Powershell.Dispose() 9 | 10 | $ExecutionContext.SessionState.Module.OnRemove = $null 11 | Get-EventSubscriber | Where-Object { $_.Action.Id -eq $Script:LoggingRunspace.EngineEventJob.Id } | Unregister-Event 12 | 13 | Remove-Variable -Scope Script -Force -Name LoggingEventQueue 14 | Remove-Variable -Scope Script -Force -Name LoggingRunspace 15 | Remove-Variable -Scope Script -Force -Name TargetsInitSync 16 | } -------------------------------------------------------------------------------- /Logging/public/Add-LoggingLevel.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Define a new severity level 4 | 5 | .DESCRIPTION 6 | This function add a new severity level to the ones already defined 7 | 8 | .PARAMETER Level 9 | An integer that identify the severity of the level, higher the value higher the severity of the level 10 | By default the module defines this levels: 11 | NOTSET 0 12 | DEBUG 10 13 | INFO 20 14 | WARNING 30 15 | ERROR 40 16 | 17 | .PARAMETER LevelName 18 | The human redable name to assign to the level 19 | 20 | .EXAMPLE 21 | PS C:\> Add-LoggingLevel -Level 41 -LevelName CRITICAL 22 | 23 | .EXAMPLE 24 | PS C:\> Add-LoggingLevel -Level 15 -LevelName VERBOSE 25 | 26 | .LINK 27 | https://logging.readthedocs.io/en/latest/functions/Add-LoggingLevel.md 28 | 29 | .LINK 30 | https://logging.readthedocs.io/en/latest/functions/Write-Log.md 31 | 32 | .LINK 33 | https://github.com/EsOsO/Logging/blob/master/Logging/public/Add-LoggingLevel.ps1 34 | #> 35 | function Add-LoggingLevel { 36 | [CmdletBinding(HelpUri='https://logging.readthedocs.io/en/latest/functions/Add-LoggingLevel.md')] 37 | param( 38 | [Parameter(Mandatory)] 39 | [int] $Level, 40 | [Parameter(Mandatory)] 41 | [string] $LevelName 42 | ) 43 | 44 | if ($Level -notin $LevelNames.Keys -and $LevelName -notin $LevelNames.Keys) { 45 | $LevelNames[$Level] = $LevelName.ToUpper() 46 | $LevelNames[$LevelName] = $Level 47 | } elseif ($Level -in $LevelNames.Keys -and $LevelName -notin $LevelNames.Keys) { 48 | $LevelNames.Remove($LevelNames[$Level]) | Out-Null 49 | $LevelNames[$Level] = $LevelName.ToUpper() 50 | $LevelNames[$LevelNames[$Level]] = $Level 51 | } elseif ($Level -notin $LevelNames.Keys -and $LevelName -in $LevelNames.Keys) { 52 | $LevelNames.Remove($LevelNames[$LevelName]) | Out-Null 53 | $LevelNames[$LevelName] = $Level 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Logging/public/Add-LoggingTarget.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Enable a logging target 4 | .DESCRIPTION 5 | This function configure and enable a logging target 6 | .PARAMETER Name 7 | The name of the target to enable and configure 8 | .PARAMETER Configuration 9 | An hashtable containing the configurations for the target 10 | .EXAMPLE 11 | PS C:\> Add-LoggingTarget -Name Console -Configuration @{Level = 'DEBUG'} 12 | .EXAMPLE 13 | PS C:\> Add-LoggingTarget -Name File -Configuration @{Level = 'INFO'; Path = 'C:\Temp\script.log'} 14 | .LINK 15 | https://logging.readthedocs.io/en/latest/functions/Add-LoggingTarget.md 16 | .LINK 17 | https://logging.readthedocs.io/en/latest/functions/Write-Log.md 18 | .LINK 19 | https://logging.readthedocs.io/en/latest/AvailableTargets.md 20 | .LINK 21 | https://github.com/EsOsO/Logging/blob/master/Logging/public/Add-LoggingTarget.ps1 22 | #> 23 | function Add-LoggingTarget { 24 | [CmdletBinding(HelpUri='https://logging.readthedocs.io/en/latest/functions/Add-LoggingTarget.md')] 25 | param( 26 | [Parameter(Position = 2)] 27 | [hashtable] $Configuration = @{} 28 | ) 29 | 30 | DynamicParam { 31 | New-LoggingDynamicParam -Name 'Name' -Target 32 | } 33 | 34 | End { 35 | $Script:Logging.EnabledTargets[$PSBoundParameters.Name] = Merge-DefaultConfig -Target $PSBoundParameters.Name -Configuration $Configuration 36 | 37 | # Special case hack - resolve target file path if it's a relative path 38 | # This can't be done in the Init scriptblock of the logging target because that scriptblock gets created in the 39 | # log consumer runspace and doesn't inherit the current SessionState. That means that the scriptblock doesn't know the 40 | # current working directory at the time when `Add-LoggingTarget` is being called and can't accurately resolve the relative path. 41 | if($PSBoundParameters.Name -eq 'File'){ 42 | $Script:Logging.EnabledTargets[$PSBoundParameters.Name].Path = $ExecutionContext.SessionState.Path.GetUnresolvedProviderPathFromPSPath($Configuration.Path) 43 | } 44 | 45 | if ($Script:Logging.Targets[$PSBoundParameters.Name].Init -is [scriptblock]) { 46 | & $Script:Logging.Targets[$PSBoundParameters.Name].Init $Script:Logging.EnabledTargets[$PSBoundParameters.Name] 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /Logging/public/Get-LoggingAvailableTarget.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Returns available logging targets 4 | .DESCRIPTION 5 | This function returns available logging targtes 6 | .EXAMPLE 7 | PS C:\> Get-LoggingAvailableTarget 8 | .LINK 9 | https://logging.readthedocs.io/en/latest/functions/Get-LoggingAvailableTarget.md 10 | .LINK 11 | https://logging.readthedocs.io/en/latest/functions/Write-Log.md 12 | .LINK 13 | https://github.com/EsOsO/Logging/blob/master/Logging/public/Get-LoggingAvailableTarget.ps1 14 | #> 15 | function Get-LoggingAvailableTarget { 16 | [CmdletBinding(HelpUri='https://logging.readthedocs.io/en/latest/functions/Get-LoggingAvailableTarget.md')] 17 | param() 18 | 19 | return $Script:Logging.Targets 20 | } -------------------------------------------------------------------------------- /Logging/public/Get-LoggingCallerScope.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Returns the default caller scope 4 | .DESCRIPTION 5 | This function returns an int representing the scope where the invocation scope for the caller should be obtained from 6 | .EXAMPLE 7 | PS C:\> Get-LoggingCallerScope 8 | .LINK 9 | https://logging.readthedocs.io/en/latest/functions/Get-LoggingCallerScope.md 10 | .LINK 11 | https://logging.readthedocs.io/en/latest/functions/Write-Log.md 12 | .LINK 13 | https://logging.readthedocs.io/en/latest/LoggingFormat.md 14 | .LINK 15 | https://github.com/EsOsO/Logging/blob/master/Logging/public/Get-LoggingCallerScope.ps1 16 | #> 17 | function Get-LoggingCallerScope { 18 | [CmdletBinding()] 19 | param() 20 | 21 | return $Script:Logging.CallerScope 22 | } -------------------------------------------------------------------------------- /Logging/public/Get-LoggingDefaultFormat.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Returns the default message format 4 | .DESCRIPTION 5 | This function returns a string representing the default message format used by enabled targets that don't override it 6 | .EXAMPLE 7 | PS C:\> Get-LoggingDefaultFormat 8 | .LINK 9 | https://logging.readthedocs.io/en/latest/functions/Get-LoggingDefaultFormat.md 10 | .LINK 11 | https://logging.readthedocs.io/en/latest/functions/Write-Log.md 12 | .LINK 13 | https://logging.readthedocs.io/en/latest/LoggingFormat.md 14 | .LINK 15 | https://github.com/EsOsO/Logging/blob/master/Logging/public/Get-LoggingDefaultFormat.ps1 16 | #> 17 | function Get-LoggingDefaultFormat { 18 | [CmdletBinding()] 19 | param() 20 | 21 | return $Script:Logging.Format 22 | } -------------------------------------------------------------------------------- /Logging/public/Get-LoggingDefaultLevel.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Returns the default message level 4 | 5 | .DESCRIPTION 6 | This function returns a string representing the default message level used by enabled targets that don't override it 7 | 8 | .EXAMPLE 9 | PS C:\> Get-LoggingDefaultLevel 10 | 11 | .LINK 12 | https://logging.readthedocs.io/en/latest/functions/Get-LoggingDefaultLevel.md 13 | 14 | .LINK 15 | https://logging.readthedocs.io/en/latest/functions/Write-Log.md 16 | 17 | .LINK 18 | https://github.com/EsOsO/Logging/blob/master/Logging/public/Get-LoggingDefaultLevel.ps1 19 | #> 20 | function Get-LoggingDefaultLevel { 21 | [CmdletBinding(HelpUri = 'https://logging.readthedocs.io/en/latest/functions/Get-LoggingDefaultLevel.md')] 22 | param() 23 | 24 | return Get-LevelName -Level $Script:Logging.LevelNo 25 | } 26 | -------------------------------------------------------------------------------- /Logging/public/Get-LoggingTarget.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Returns enabled logging targets 4 | .DESCRIPTION 5 | This function returns enabled logging targtes 6 | .PARAMETER Name 7 | The Name of the target to retrieve, if not passed all configured targets will be returned 8 | .EXAMPLE 9 | PS C:\> Get-LoggingTarget 10 | .EXAMPLE 11 | PS C:\> Get-LoggingTarget -Name Console 12 | .LINK 13 | https://logging.readthedocs.io/en/latest/functions/Get-LoggingTarget.md 14 | .LINK 15 | https://logging.readthedocs.io/en/latest/functions/Write-Log.md 16 | .LINK 17 | https://github.com/EsOsO/Logging/blob/master/Logging/public/Get-LoggingTarget.ps1 18 | #> 19 | function Get-LoggingTarget { 20 | [CmdletBinding(HelpUri = 'https://logging.readthedocs.io/en/latest/functions/Get-LoggingTarget.md')] 21 | param( 22 | [string] $Name = $null 23 | ) 24 | 25 | if ($PSBoundParameters.Name) { 26 | return $Script:Logging.EnabledTargets[$Name] 27 | } 28 | 29 | return $Script:Logging.EnabledTargets 30 | } 31 | -------------------------------------------------------------------------------- /Logging/public/Set-LoggingCallerScope.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Sets the scope from which to get the caller scope 4 | 5 | .DESCRIPTION 6 | This function sets the scope to obtain information from the caller 7 | 8 | .PARAMETER CallerScope 9 | 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 10 | 11 | .EXAMPLE 12 | PS C:\> Set-LoggingCallerScope -CallerScope 2 13 | 14 | .EXAMPLE 15 | PS C:\> Set-LoggingCallerScope 16 | 17 | It sets the caller scope to 1 18 | 19 | .LINK 20 | https://logging.readthedocs.io/en/latest/functions/Set-LoggingCallerScope.md 21 | 22 | .LINK 23 | https://logging.readthedocs.io/en/latest/functions/Write-Log.md 24 | 25 | .LINK 26 | https://github.com/EsOsO/Logging/blob/master/Logging/public/Set-LoggingCallerScope.ps1 27 | #> 28 | function Set-LoggingCallerScope { 29 | [CmdletBinding(HelpUri='https://logging.readthedocs.io/en/latest/functions/Set-LoggingCallerScope.md')] 30 | param( 31 | [int]$CallerScope = $Defaults.CallerScope 32 | ) 33 | 34 | Wait-Logging 35 | $Script:Logging.CallerScope = $CallerScope 36 | } 37 | -------------------------------------------------------------------------------- /Logging/public/Set-LoggingCustomTarget.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Sets a folder as custom target repository 4 | 5 | .DESCRIPTION 6 | This function sets a folder as a custom target repository. 7 | Every *.ps1 file will be loaded as a custom target and available to be enabled for logging to. 8 | 9 | .PARAMETER Path 10 | A valid path containing *.ps1 files that defines new loggin targets 11 | 12 | .EXAMPLE 13 | PS C:\> Set-LoggingCustomTarget -Path C:\Logging\CustomTargets 14 | 15 | .LINK 16 | https://logging.readthedocs.io/en/latest/functions/Set-LoggingCustomTarget.md 17 | 18 | .LINK 19 | https://logging.readthedocs.io/en/latest/functions/CustomTargets.md 20 | 21 | .LINK 22 | https://logging.readthedocs.io/en/latest/functions/Write-Log.md 23 | 24 | .LINK 25 | https://github.com/EsOsO/Logging/blob/master/Logging/public/Set-LoggingCustomTarget.ps1 26 | #> 27 | function Set-LoggingCustomTarget { 28 | [CmdletBinding(HelpUri='https://logging.readthedocs.io/en/latest/functions/Set-LoggingCustomTarget.md')] 29 | param( 30 | [Parameter(Mandatory)] 31 | [ValidateScript({Test-Path -Path $_ -PathType Container})] 32 | [string] $Path 33 | ) 34 | 35 | Write-Verbose 'Stopping Logging Manager' 36 | Stop-LoggingManager 37 | 38 | $Script:Logging.CustomTargets = $Path 39 | 40 | Write-Verbose 'Starting Logging Manager' 41 | Start-LoggingManager 42 | } 43 | -------------------------------------------------------------------------------- /Logging/public/Set-LoggingDefaultFormat.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Sets a global logging message format 4 | 5 | .DESCRIPTION 6 | This function sets a global logging message format 7 | 8 | .PARAMETER Format 9 | The string used to format the message to log 10 | 11 | .EXAMPLE 12 | PS C:\> Set-LoggingDefaultFormat -Format '[%{level:-7}] %{message}' 13 | 14 | .EXAMPLE 15 | PS C:\> Set-LoggingDefaultFormat 16 | 17 | It sets the default format as [%{timestamp:+%Y-%m-%d %T%Z}] [%{level:-7}] %{message} 18 | 19 | .LINK 20 | https://logging.readthedocs.io/en/latest/functions/Set-LoggingDefaultFormat.md 21 | 22 | .LINK 23 | https://logging.readthedocs.io/en/latest/functions/LoggingFormat.md 24 | 25 | .LINK 26 | https://logging.readthedocs.io/en/latest/functions/Write-Log.md 27 | 28 | .LINK 29 | https://github.com/EsOsO/Logging/blob/master/Logging/public/Set-LoggingDefaultFormat.ps1 30 | #> 31 | function Set-LoggingDefaultFormat { 32 | [CmdletBinding(HelpUri='https://logging.readthedocs.io/en/latest/functions/Set-LoggingDefaultFormat.md')] 33 | param( 34 | [string] $Format = $Defaults.Format 35 | ) 36 | 37 | Wait-Logging 38 | $Script:Logging.Format = $Format 39 | 40 | # Setting format on already configured targets 41 | foreach ($Target in $Script:Logging.EnabledTargets.Values) { 42 | if ($Target.ContainsKey('Format')) { 43 | $Target['Format'] = $Script:Logging.Format 44 | } 45 | } 46 | 47 | # Setting format on available targets 48 | foreach ($Target in $Script:Logging.Targets.Values) { 49 | if ($Target.Defaults.ContainsKey('Format')) { 50 | $Target.Defaults.Format.Default = $Script:Logging.Format 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Logging/public/Set-LoggingDefaultLevel.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Sets a global logging severity level. 4 | 5 | .DESCRIPTION 6 | This function sets a global logging severity level. 7 | Log messages written with a lower logging level will be discarded. 8 | 9 | .PARAMETER Level 10 | The level severity name to set as default for enabled targets 11 | 12 | .EXAMPLE 13 | PS C:\> Set-LoggingDefaultLevel -Level ERROR 14 | 15 | PS C:\> Write-Log -Level INFO -Message "Test" 16 | => Discarded. 17 | 18 | .LINK 19 | https://logging.readthedocs.io/en/latest/functions/Set-LoggingDefaultLevel.md 20 | 21 | .LINK 22 | https://logging.readthedocs.io/en/latest/functions/Write-Log.md 23 | 24 | .LINK 25 | https://github.com/EsOsO/Logging/blob/master/Logging/public/Set-LoggingDefaultLevel.ps1 26 | #> 27 | function Set-LoggingDefaultLevel { 28 | [CmdletBinding(HelpUri = 'https://logging.readthedocs.io/en/latest/functions/Set-LoggingDefaultLevel.md')] 29 | param() 30 | 31 | DynamicParam { 32 | New-LoggingDynamicParam -Name "Level" -Level 33 | } 34 | 35 | End { 36 | $Script:Logging.Level = $PSBoundParameters.Level 37 | $Script:Logging.LevelNo = Get-LevelNumber -Level $PSBoundParameters.Level 38 | 39 | # Setting level on already configured targets 40 | foreach ($Target in $Script:Logging.EnabledTargets.Values) { 41 | if ($Target.ContainsKey('Level')) { 42 | $Target['Level'] = $Script:Logging.Level 43 | } 44 | } 45 | 46 | # Setting level on available targets 47 | foreach ($Target in $Script:Logging.Targets.Values) { 48 | if ($Target.Defaults.ContainsKey('Level')) { 49 | $Target.Defaults.Level.Default = $Script:Logging.Level 50 | } 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Logging/public/Wait-Logging.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Wait for the message queue to be emptied 4 | 5 | .DESCRIPTION 6 | This function can be used to block the execution of a script waiting for the message queue to be emptied 7 | 8 | .EXAMPLE 9 | PS C:\> Wait-Logging 10 | 11 | .LINK 12 | https://logging.readthedocs.io/en/latest/functions/Wait-Logging.md 13 | 14 | .LINK 15 | https://github.com/EsOsO/Logging/blob/master/Logging/public/Wait-Logging.ps1 16 | #> 17 | function Wait-Logging { 18 | [CmdletBinding(HelpUri='https://logging.readthedocs.io/en/latest/functions/Wait-Logging.md')] 19 | param() 20 | 21 | #This variable is initiated inside Start-LoggingManager 22 | if (!(Get-Variable -Name "LoggingEventQueue" -ErrorAction Ignore)) { 23 | return 24 | } 25 | 26 | $start = [datetime]::Now 27 | 28 | Start-Sleep -Milliseconds 10 29 | 30 | while ($Script:LoggingEventQueue.Count -gt 0) { 31 | Start-Sleep -Milliseconds 20 32 | 33 | <# 34 | If errors occure in the consumption of the logging requests, 35 | forcefully shutdown function after some time. 36 | #> 37 | $difference = [datetime]::Now - $start 38 | if ($difference.seconds -gt 30) { 39 | Write-Error -Message ("{0} :: Wait timeout." -f $MyInvocation.MyCommand) -ErrorAction SilentlyContinue 40 | break; 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Logging/public/Write-Log.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .SYNOPSIS 3 | Emits a log record 4 | 5 | .DESCRIPTION 6 | This function write a log record to configured targets with the matching level 7 | 8 | .PARAMETER Level 9 | The log level of the message. Valid values are DEBUG, INFO, WARNING, ERROR, NOTSET 10 | Other custom levels can be added and are a valid value for the parameter 11 | INFO is the default 12 | 13 | .PARAMETER Message 14 | The text message to write 15 | 16 | .PARAMETER Arguments 17 | An array of objects used to format 18 | 19 | .PARAMETER Body 20 | An object that can contain additional log metadata (used in target like ElasticSearch) 21 | 22 | .PARAMETER ExceptionInfo 23 | An optional ErrorRecord 24 | 25 | .EXAMPLE 26 | PS C:\> Write-Log 'Hello, World!' 27 | 28 | .EXAMPLE 29 | PS C:\> Write-Log -Level ERROR -Message 'Hello, World!' 30 | 31 | .EXAMPLE 32 | PS C:\> Write-Log -Level ERROR -Message 'Hello, {0}!' -Arguments 'World' 33 | 34 | .EXAMPLE 35 | PS C:\> Write-Log -Level ERROR -Message 'Hello, {0}!' -Arguments 'World' -Body @{Server='srv01.contoso.com'} 36 | 37 | .LINK 38 | https://logging.readthedocs.io/en/latest/functions/Write-Log.md 39 | 40 | .LINK 41 | https://logging.readthedocs.io/en/latest/functions/Add-LoggingLevel.md 42 | 43 | .LINK 44 | https://github.com/EsOsO/Logging/blob/master/Logging/public/Write-Log.ps1 45 | #> 46 | Function Write-Log { 47 | [CmdletBinding()] 48 | param( 49 | [Parameter(Position = 2, 50 | Mandatory = $true)] 51 | [string] $Message, 52 | [Parameter(Position = 3, 53 | Mandatory = $false)] 54 | [array] $Arguments, 55 | [Parameter(Position = 4, 56 | Mandatory = $false)] 57 | [object] $Body = $null, 58 | [Parameter(Position = 5, 59 | Mandatory = $false)] 60 | [System.Management.Automation.ErrorRecord] $ExceptionInfo = $null 61 | ) 62 | 63 | DynamicParam { 64 | New-LoggingDynamicParam -Level -Mandatory $false -Name "Level" 65 | $PSBoundParameters["Level"] = "INFO" 66 | } 67 | 68 | End { 69 | $levelNumber = Get-LevelNumber -Level $PSBoundParameters.Level 70 | $invocationInfo = (Get-PSCallStack)[$Script:Logging.CallerScope] 71 | 72 | # Split-Path throws an exception if called with a -Path that is null or empty. 73 | [string] $fileName = [string]::Empty 74 | if (-not [string]::IsNullOrEmpty($invocationInfo.ScriptName)) { 75 | $fileName = Split-Path -Path $invocationInfo.ScriptName -Leaf 76 | } 77 | 78 | $logMessage = [hashtable] @{ 79 | timestamp = [datetime]::now 80 | timestamputc = [datetime]::UtcNow 81 | level = Get-LevelName -Level $levelNumber 82 | levelno = $levelNumber 83 | lineno = $invocationInfo.ScriptLineNumber 84 | pathname = $invocationInfo.ScriptName 85 | filename = $fileName 86 | caller = $invocationInfo.Command 87 | message = [string] $Message 88 | rawmessage = [string] $Message 89 | body = $Body 90 | execinfo = $ExceptionInfo 91 | pid = $PID 92 | } 93 | 94 | if ($PSBoundParameters.ContainsKey('Arguments')) { 95 | $logMessage["message"] = [string] $Message -f $Arguments 96 | $logMessage["args"] = $Arguments 97 | } 98 | 99 | #This variable is initiated via Start-LoggingManager 100 | $Script:LoggingEventQueue.Add($logMessage) 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /Logging/targets/AzureLogAnalytics.ps1: -------------------------------------------------------------------------------- 1 | @{ 2 | Name = 'AzureLogAnalytics' 3 | Description = 'Sends log data to a Azure Log Analytics Workspace' 4 | Configuration = @{ 5 | WorkspaceId = @{Required = $true; Type = [string]; Default = $null } 6 | SharedKey = @{Required = $true; Type = [string]; Default = $null } 7 | LogType = @{Required = $false; Type = [string]; Default = "Logging" } 8 | Level = @{Required = $false; Type = [string]; Default = $Logging.Level } 9 | } 10 | Logger = { 11 | param( 12 | [hashtable] $Log, 13 | [hashtable] $Configuration 14 | ) 15 | 16 | Function GetAuthorizationSignature { 17 | [CmdletBinding()] 18 | param ( 19 | $WorkspaceId, 20 | $SharedKey, 21 | $Hashable 22 | ) 23 | 24 | $hashableBytes = [Text.Encoding]::UTF8.GetBytes($Hashable) 25 | $key = [Convert]::FromBase64String($SharedKey) 26 | $sha256 = New-Object System.Security.Cryptography.HMACSHA256 27 | $sha256.Key = $key 28 | $hash = $sha256.ComputeHash($hashableBytes) 29 | $authorization = 'SharedKey {0}:{1}' -f $WorkspaceId, 30 | [Convert]::ToBase64String($hash) 31 | 32 | return $authorization 33 | } 34 | 35 | Function WriteLogAnalyticsData { 36 | param ( 37 | $WorkspaceId, 38 | $SharedKey, 39 | $LogType, 40 | $Body 41 | ) 42 | $method = "POST" 43 | $contentType = "application/json" 44 | $resource = "/api/logs" 45 | $contentLength = $Body.Length 46 | $rfc1123date = [DateTime]::UtcNow.ToString("r") 47 | $xHeaders = "x-ms-date:" + $rfc1123date 48 | 49 | $hashable = $method, $contentLength, $contentType, $xHeaders, $resource -join "`n" 50 | $getAuthorizationSignatureSplat = @{ 51 | 52 | WorkspaceId = $WorkspaceId 53 | SharedKey = $SharedKey 54 | Hashable = $hashable 55 | } 56 | $authorization = GetAuthorizationSignature @getAuthorizationSignatureSplat 57 | $uri = "https://${WorkspaceId}.ods.opinsights.azure.com${resource}?api-version=2016-04-01" 58 | 59 | $headers = @{ 60 | 61 | Authorization = $authorization 62 | 'Log-Type' = $LogType 63 | 'x-ms-date' = $rfc1123date 64 | 'time-generated-field' = 'timestamputc' 65 | } 66 | 67 | $invokeWebRequestSplat = @{ 68 | 69 | Uri = $uri 70 | Method = $method 71 | ContentType = $contentType 72 | Headers = $headers 73 | Body = $Body 74 | UseBasicParsing = $true 75 | } 76 | Invoke-WebRequest @invokeWebRequestSplat 77 | } 78 | 79 | # Convert timestamp from utc to ISO 8601 80 | $Log.timestamputc = $Log.timestamputc | Get-Date -UFormat '+%Y-%m-%dT%H:%M:%S.000Z' 81 | 82 | # See if a Body was provided, that needs to be expanded. 83 | if ($Log.Body) { 84 | $Log = $Log + $Log.Body 85 | $Log.Remove('Body') 86 | } 87 | 88 | # Submit the data to the API endpoint 89 | $json = $Log | ConvertTo-Json 90 | $encodedJson = [System.Text.Encoding]::UTF8.GetBytes($json) 91 | $writeLogAnalyticsDataSplat = @{ 92 | 93 | WorkspaceId = $Configuration.'WorkspaceId' 94 | SharedKey = $Configuration.'SharedKey' 95 | LogType = $Configuration.'LogType' 96 | Body = $encodedJson 97 | } 98 | $null = WriteLogAnalyticsData @writeLogAnalyticsDataSplat 99 | } 100 | } -------------------------------------------------------------------------------- /Logging/targets/Console.ps1: -------------------------------------------------------------------------------- 1 | @{ 2 | Name = 'Console' 3 | Description = 'Writes messages to console with different colors.' 4 | Configuration = @{ 5 | Level = @{Required = $false; Type = [string]; Default = $Logging.Level } 6 | Format = @{Required = $false; Type = [string]; Default = $Logging.Format } 7 | PrintException = @{Required = $false; Type = [bool]; Default = $true } 8 | ColorMapping = @{Required = $false; Type = [hashtable]; Default = @{ 9 | 'DEBUG' = 'Blue' 10 | 'INFO' = 'Green' 11 | 'WARNING' = 'Yellow' 12 | 'ERROR' = 'Red' 13 | } 14 | } 15 | } 16 | Init = { 17 | param( 18 | [hashtable] $Configuration 19 | ) 20 | 21 | foreach ($Level in $Configuration.ColorMapping.Keys) { 22 | $Color = $Configuration.ColorMapping[$Level] 23 | 24 | if ($Color -notin ([System.Enum]::GetNames([System.ConsoleColor]))) { 25 | $ParentHost.UI.WriteErrorLine("ERROR: Cannot use custom color '$Color': not a valid [System.ConsoleColor] value") 26 | continue 27 | } 28 | } 29 | } 30 | Logger = { 31 | param( 32 | [hashtable] $Log, 33 | [hashtable] $Configuration 34 | ) 35 | 36 | try { 37 | $logText = Format-Pattern -Pattern $Configuration.Format -Source $Log 38 | 39 | if (![String]::IsNullOrWhiteSpace($Log.ExecInfo) -and $Configuration.PrintException) { 40 | $logText += "`n{0}" -f $Log.ExecInfo.Exception.Message 41 | $logText += "`n{0}" -f (($Log.ExecInfo.ScriptStackTrace -split "`r`n" | %{"`t{0}" -f $_}) -join "`n") 42 | } 43 | 44 | $mtx = New-Object System.Threading.Mutex($false, 'ConsoleMtx') 45 | [void] $mtx.WaitOne() 46 | 47 | if ($Configuration.ColorMapping.ContainsKey($Log.Level)) { 48 | $ParentHost.UI.WriteLine($Configuration.ColorMapping[$Log.Level], $ParentHost.UI.RawUI.BackgroundColor, $logText) 49 | } 50 | else { 51 | $ParentHost.UI.WriteLine($logText) 52 | } 53 | 54 | [void] $mtx.ReleaseMutex() 55 | $mtx.Dispose() 56 | } 57 | catch { 58 | $ParentHost.UI.WriteErrorLine($_) 59 | } 60 | } 61 | } -------------------------------------------------------------------------------- /Logging/targets/ElasticSearch.ps1: -------------------------------------------------------------------------------- 1 | @{ 2 | Name = 'ElasticSearch' 3 | Configuration = @{ 4 | Index = @{Required = $true; Type = [string]; Default = $null} 5 | Type = @{Required = $true; Type = [string]; Default = $null} 6 | ServerName = @{Required = $true; Type = [string]; Default = $null} 7 | ServerPort = @{Required = $true; Type = [int]; Default = 9200} 8 | Flatten = @{Required = $false; Type = [bool]; Default = $false} 9 | Level = @{Required = $false; Type = [string]; Default = $Logging.Level} 10 | Authorization = @{Required = $false; Type = [string]; Default = $null} 11 | Https = @{Required = $false; Type = [bool]; Default = $false} 12 | } 13 | Logger = { 14 | param( 15 | [hashtable] $Log, 16 | [hashtable] $Configuration 17 | ) 18 | 19 | Function ConvertTo-FlatterHashTable { 20 | [CmdletBinding()] 21 | param( 22 | [hashtable] $Object 23 | ) 24 | 25 | $ht = [hashtable] @{} 26 | 27 | foreach ($key in $Object.Keys) { 28 | if ($Object[$key] -is [hashtable]) { 29 | $ht += ConvertTo-FlatterHashTable -Object $Object[$key] 30 | } else { 31 | $ht[$key] = $Object[$key] 32 | } 33 | } 34 | 35 | return $ht 36 | } 37 | 38 | if ($Configuration.Https) { 39 | $httpType = "https" 40 | } else { 41 | $httpType = "http" 42 | } 43 | 44 | $Index = Format-Pattern -Pattern $Configuration.Index -Source $Log 45 | $Uri = '{0}://{1}:{2}/{3}/{4}' -f $httpType, $Configuration.ServerName, $Configuration.ServerPort, $Index, $Configuration.Type 46 | 47 | if ($Configuration.Flatten) { 48 | $Message = ConvertTo-FlatterHashTable $Log | ConvertTo-Json -Compress 49 | } else { 50 | $Message = $Log | ConvertTo-Json -Compress 51 | } 52 | 53 | if ($Configuration.Authorization) { 54 | $base64Auth = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("$($Configuration.Authorization)"))) 55 | Invoke-RestMethod -Method Post -Uri $Uri -Body $Message -Headers @{"Content-Type"="application/json";Authorization="Basic $base64Auth"} 56 | } else { 57 | Invoke-RestMethod -Method Post -Uri $Uri -Body $Message -Headers @{"Content-Type"="application/json"} 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /Logging/targets/Email.ps1: -------------------------------------------------------------------------------- 1 | @{ 2 | Name = 'Email' 3 | Description = 'Send log message to email recipients' 4 | Configuration = @{ 5 | SMTPServer = @{Required = $true; Type = [string]; Default = $null } 6 | From = @{Required = $true; Type = [string]; Default = $null } 7 | To = @{Required = $true; Type = [string]; Default = $null } 8 | Subject = @{Required = $false; Type = [string]; Default = '[%{level:-7}] %{message}' } 9 | Credential = @{Required = $false; Type = [pscredential]; Default = $null } 10 | Level = @{Required = $false; Type = [string]; Default = $Logging.Level } 11 | Port = @{Required = $false; Type = [int]; Default = 25 } 12 | UseSsl = @{Required = $false; Type = [bool]; Default = $false } 13 | Format = @{Required = $false; Type = [string]; Default = $Logging.Format } 14 | PrintException = @{Required = $false; Type = [bool]; Default = $false } 15 | } 16 | Logger = { 17 | param( 18 | [hashtable] $Log, 19 | [hashtable] $Configuration 20 | ) 21 | 22 | $Body = '

{0}

' -f $Log.Message 23 | 24 | if (![String]::IsNullOrWhiteSpace($Log.ExecInfo)) { 25 | $Body += '
'
26 |             $Body += "`n{0}" -f $Log.ExecInfo.Exception.Message
27 |             $Body += "`n{0}" -f (($Log.ExecInfo.ScriptStackTrace -split "`r`n" | % { "`t{0}" -f $_ }) -join "`n")
28 |             $Body += '
' 29 | } 30 | 31 | $Params = @{ 32 | SmtpServer = $Configuration.SMTPServer 33 | From = $Configuration.From 34 | To = $Configuration.To.Split(',').Trim() 35 | Port = $Configuration.Port 36 | UseSsl = $Configuration.UseSsl 37 | Subject = Format-Pattern -Pattern $Configuration.Subject -Source $Log 38 | Body = $Body 39 | BodyAsHtml = $true 40 | } 41 | 42 | if ($Configuration.Credential) { 43 | $Params['Credential'] = $Configuration.Credential 44 | } 45 | 46 | if ($Log.Body) { 47 | $Params.Body += "`n`n{0}" -f ($Log.Body | ConvertTo-Json) 48 | } 49 | 50 | Send-MailMessage @Params 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Logging/targets/File.ps1: -------------------------------------------------------------------------------- 1 | @{ 2 | Name = 'File' 3 | Configuration = @{ 4 | Path = @{Required = $true; Type = [string]; Default = $null } 5 | PrintBody = @{Required = $false; Type = [bool]; Default = $false } 6 | PrintException = @{Required = $false; Type = [bool]; Default = $false } 7 | Append = @{Required = $false; Type = [bool]; Default = $true } 8 | Encoding = @{Required = $false; Type = [string]; Default = 'ascii' } 9 | Level = @{Required = $false; Type = [string]; Default = $Logging.Level } 10 | Format = @{Required = $false; Type = [string]; Default = $Logging.Format } 11 | # Rotation 12 | ## Rotate after the directory contains the given amount of files. A value that is less than or equal to 0 is treated as not configured. 13 | RotateAfterAmount = @{Required = $false; Type = [int]; Default = -1} 14 | ## Amount of files to be rotated, when RotateAfterAmount is used. 15 | ## In general max(|Files| - RotateAfterAmount, RotateAmount) files are rotated. 16 | RotateAmount = @{Required = $false; Type = [int]; Default = -1} 17 | ## Rotate after the difference between the current datetime and the datetime of the file(s) are greater then the given timespan. A value of 0 is treated as not configured. 18 | RotateAfterDate = @{Required = $false; Type = [timespan]; Default = [timespan]::Zero} 19 | ## Rotate after the file(s) are greater than the given size in BYTES. A value that is less than or equal to 0 is treated as not configured. 20 | RotateAfterSize = @{Required = $false; Type = [int]; Default = -1} 21 | ## Optionally all rotated files can be compressed. Uses patterns, however only datetimes are allows 22 | CompressionPath = @{Required = $false; Type = [string]; Default = [String]::Empty} 23 | } 24 | Init = { 25 | param( 26 | [hashtable] $Configuration 27 | ) 28 | 29 | [string] $directoryPath = [System.IO.Path]::GetDirectoryName($Configuration.Path) 30 | [string] $wildcardBasePath = Format-Pattern -Pattern ([System.IO.Path]::GetFileName($Configuration.Path)) -Wildcard 31 | 32 | # We (try to) create the directory if it is not yet given 33 | if (-not [System.IO.Directory]::Exists($directoryPath)){ 34 | # "Creates all directories and subdirectories in the specified path unless they already exist." 35 | # https://docs.microsoft.com/en-us/dotnet/api/system.io.directory.createdirectory?view=net-5.0#System_IO_Directory_CreateDirectory_System_String_ 36 | [System.IO.Directory]::CreateDirectory($directoryPath) | Out-Null 37 | } 38 | 39 | # Allow for the rolling of log files 40 | $mtx = New-Object System.Threading.Mutex($false, 'FileMtx') 41 | [void] $mtx.WaitOne() 42 | try{ 43 | # Get existing files 44 | if (-not [System.IO.Directory]::Exists($directoryPath)){ 45 | return 46 | } 47 | 48 | $rotationDate = $Configuration.RotateAfterDate.Duration() 49 | $currentDateUtc = [datetime]::UtcNow 50 | 51 | [string[]] $logFiles = [System.IO.Directory]::GetFiles($directoryPath, $wildcardBasePath) 52 | $toBeRolled = @() 53 | $givenFiles = [System.IO.FileInfo[]]::new($logFiles.Count) 54 | 55 | for ([int] $i = 0; $i -lt $logFiles.Count; $i++){ 56 | $fileInfo = [System.IO.FileInfo]::new($logFiles[$i]) 57 | 58 | # 1. Based on file size 59 | if ($Configuration.RotateAfterSize -gt 0 -and $fileInfo.Length -gt $Configuration.RotateAfterSize){ 60 | $toBeRolled += $fileInfo 61 | } 62 | # 2. Based on date 63 | elseif ($rotationDate.TotalSeconds -gt 0 -and ($currentDateUtc - $fileInfo.CreationTimeUtc).TotalSeconds -gt $rotationDate.TotalSeconds){ 64 | $toBeRolled += $fileInfo 65 | } 66 | # 3. Based on number 67 | else{ 68 | $givenFiles[$i] = $fileInfo 69 | } 70 | } 71 | 72 | # 3. Based on number 73 | if ($Configuration.RotateAfterAmount -gt 0 -and $givenFiles.Count -gt $Configuration.RotateAfterAmount){ 74 | if ($Configuration.RotateAmount -le 0){ 75 | $Configuration.RotateAmount = $Configuration.RotateAfterAmount / 2 76 | } 77 | 78 | $sortedFiles = $givenFiles | Sort-Object -Property CreationTimeUtc 79 | 80 | # Rotate 81 | # a) until sortedFiles = RotateAfterAmount 82 | # b) until RotateAmount files are rotated 83 | for ([int] $i = 0; ($i -lt ($sortedFiles.Count - $Configuration.RotateAfterAmount)) -or ($i -le $Configuration.RotateAmount); $i++){ 84 | $toBeRolled += $sortedFiles[$i] 85 | } 86 | } 87 | 88 | [string[]] $paths = @() 89 | foreach ($fileInfo in $toBeRolled){ 90 | $paths += $fileInfo.FullName 91 | } 92 | 93 | if ($paths.Count -eq 0){ 94 | return 95 | } 96 | 97 | # (opt) compress old files 98 | if (-not [string]::IsNullOrWhiteSpace($Configuration.CompressionPath)){ 99 | try{ 100 | Add-Type -As System.IO.Compression.FileSystem 101 | }catch{ 102 | $ParentHost.UI.WriteErrorLine("ERROR: You need atleast .Net 4.5 for the compression feature.") 103 | return 104 | } 105 | 106 | [string] $compressionDirectory = [System.IO.Path]::GetDirectoryName($Configuration.CompressionPath) 107 | [string] $compressionFile = Format-Pattern -Pattern $Configuration.CompressionPath -Source @{ 108 | timestamp = [datetime]::now 109 | timestamputc = [datetime]::UtcNow 110 | pid = $PID 111 | } 112 | 113 | # We (try to) create the directory if it is not yet given 114 | if (-not [System.IO.Directory]::Exists($compressionDirectory)){ 115 | [System.IO.Directory]::CreateDirectory($compressionDirectory) | Out-Null 116 | } 117 | 118 | # Compress-Archive not supported for PS < 5 119 | [string] $temporary = Join-Path -Path ([System.IO.Path]::GetTempPath()) -ChildPath ([guid]::NewGuid().ToString()) 120 | [System.IO.DirectoryInfo] $tempDir = [System.IO.Directory]::CreateDirectory($temporary) 121 | 122 | if ([System.IO.File]::Exists($compressionFile)){ 123 | [IO.Compression.ZipFile]::ExtractToDirectory($compressionFile, $tempDir.FullName) 124 | Remove-Item -Path $compressionFile -Force 125 | } 126 | 127 | Move-Item -Path $paths -Destination $tempDir.FullName -Force 128 | [IO.Compression.ZipFile]::CreateFromDirectory($tempDir.FullName, $compressionFile, [System.IO.Compression.CompressionLevel]::Fastest, $false) 129 | Remove-Item -Path $tempDir.FullName -Recurse -Force 130 | }else{ 131 | Remove-Item -Path $paths -Force 132 | } 133 | }finally{ 134 | [void] $mtx.ReleaseMutex() 135 | $mtx.Dispose() 136 | } 137 | } 138 | Logger = { 139 | param( 140 | [hashtable] $Log, 141 | [hashtable] $Configuration 142 | ) 143 | 144 | if ($Configuration.PrintBody -and $Log.Body) { 145 | $Log.Body = $Log.Body | ConvertTo-Json -Compress 146 | } 147 | elseif (-not $Configuration.PrintBody -and $Log.Body) { 148 | $Log.Remove('Body') 149 | } 150 | 151 | $Text = Format-Pattern -Pattern $Configuration.Format -Source $Log 152 | 153 | if (![String]::IsNullOrWhiteSpace($Log.ExecInfo) -and $Configuration.PrintException) { 154 | $Text += "`n{0}" -f $Log.ExecInfo.Exception.Message 155 | $Text += "`n{0}" -f (($Log.ExecInfo.ScriptStackTrace -split "`r`n" | % { "`t{0}" -f $_ }) -join "`n") 156 | } 157 | 158 | $Params = @{ 159 | Append = $Configuration.Append 160 | FilePath = Format-Pattern -Pattern $Configuration.Path -Source $Log 161 | Encoding = $Configuration.Encoding 162 | } 163 | 164 | $mtx = New-Object System.Threading.Mutex($false, 'FileMtx') 165 | [void] $mtx.WaitOne() 166 | try{ 167 | $Text | Out-File @Params 168 | }finally{ 169 | [void] $mtx.ReleaseMutex() 170 | $mtx.Dispose() 171 | } 172 | } 173 | } -------------------------------------------------------------------------------- /Logging/targets/Seq.ps1: -------------------------------------------------------------------------------- 1 | @{ 2 | Name = 'Seq' 3 | Description = 'Sends log data to the designated Seq server web service' 4 | Configuration = @{ 5 | Url = @{Required = $true; Type = [string]; Default = $null } 6 | ApiKey = @{Required = $false; Type = [string]; Default = $null } 7 | Properties = @{Required = $false; Type = [hashtable]; Default = $null } 8 | Level = @{Required = $false; Type = [string]; Default = $Logging.Level } 9 | } 10 | Logger = { 11 | param( 12 | [hashtable] $Log, 13 | [hashtable] $Configuration 14 | ) 15 | 16 | $Body = @{ 17 | "@t" = $Log.TimestampUtc 18 | "@l" = $Log.Level.substring(0,1).toupper()+$Log.Level.substring(1).tolower() 19 | "@m" = $Log.Message 20 | "@mt" = $Log.RawMessage 21 | } 22 | 23 | if ($Log.ExecInfo) { 24 | $Body["@x"] = $Log.ExecInfo.ScriptStackTrace 25 | } 26 | 27 | $Body += $Log 28 | 29 | if($null -ne $Configuration.Properties) 30 | { 31 | $Body += $Configuration.Properties 32 | } 33 | 34 | if ($Configuration.ApiKey) { 35 | $Url = '{0}/api/events/raw?clef&apiKey={1}' -f $Configuration.Url, $Configuration.ApiKey 36 | } 37 | else { 38 | $Url = '{0}/api/events/raw?clef' -f $Configuration.Url 39 | } 40 | 41 | Invoke-RestMethod -Uri $Url -Body ($Body | ConvertTo-Json -Compress) -Method POST | Out-Null 42 | } 43 | } -------------------------------------------------------------------------------- /Logging/targets/Slack.ps1: -------------------------------------------------------------------------------- 1 | @{ 2 | Name = 'Slack' 3 | Configuration = @{ 4 | WebHook = @{Required = $true; Type = [string]; Default = $null } 5 | BotName = @{Required = $false; Type = [string]; Default = $null } 6 | Channel = @{Required = $false; Type = [string]; Default = $null } 7 | Icons = @{Required = $false; Type = [hashtable]; Default = @{ 8 | 'ERROR' = ':fire:' 9 | 'WARNING' = ':warning:' 10 | 'INFO' = ':exclamation' 11 | 'DEBUG' = ':eyes:' 12 | } 13 | } 14 | Level = @{Required = $false; Type = [string]; Default = $Logging.Level} 15 | Format = @{Required = $false; Type = [string]; Default = $Logging.Format} 16 | } 17 | Logger = { 18 | param( 19 | [hashtable] $Log, 20 | [hashtable] $Configuration 21 | ) 22 | 23 | $Text = @{ 24 | text = Format-Pattern -Pattern $Configuration.Format -Source $Log 25 | } 26 | 27 | if ($Configuration.BotName) { $Text['username'] = $Configuration.BotName } 28 | 29 | if ($Configuration.Channel) { $Text['channel'] = $Configuration.Channel } 30 | 31 | $Text['icon_emoji'] = $Configuration.Icons[$Log.LevelNo] 32 | 33 | $payload = 'payload={0}' -f ($Text | ConvertTo-Json -Compress) 34 | 35 | Invoke-RestMethod -Method POST -Uri $Configuration.WebHook -Body $payload | Out-Null 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Logging/targets/Teams.ps1: -------------------------------------------------------------------------------- 1 | @{ 2 | Name = 'Teams' 3 | Configuration = @{ 4 | WebHook = @{Required = $true; Type = [string]; Default = $null } 5 | Details = @{Required = $false; Type = [bool]; Default = $true} 6 | Level = @{Required = $false; Type = [string]; Default = $Logging.Level} 7 | Colors = @{Required = $false; Type = [hashtable]; Default = @{ 8 | 'DEBUG' = 'blue' 9 | 'INFO' = 'brightgreen' 10 | 'WARNING' = 'orange' 11 | 'ERROR' = 'red' 12 | }} 13 | } 14 | Logger = { 15 | param( 16 | [hashtable] $Log, 17 | [hashtable] $Configuration 18 | ) 19 | 20 | $Payload = [ordered] @{ 21 | '@type' = 'MessageCard' 22 | '@context' = 'https://schema.org/extensions' 23 | summary = '[{0}] {1}' -f $Log.Level, $Log.Message 24 | themeColor = '#0078D7' 25 | title = $Log.Message 26 | text = '![{0}](https://raster.shields.io/static/v1?label=Logging&message={0}&color={1}&style=flat)' -f $Log.Level, $Configuration.Colors[$Log.Level] 27 | } 28 | 29 | $sections = @() 30 | 31 | if ($Log.Body) { 32 | $body = [ordered]@{}; 33 | 34 | if ($Log.Body.Activity) { 35 | $body.activityTitle = @('Body', $Log.Body.Activity.title)[[bool]$Log.Body.Activity.title] 36 | $body.activitySubTitle = $Log.Body.Activity.subtitle 37 | $body.text = $Log.Body.Activity.text 38 | } elseif ($Log.Body.Facts) { 39 | $body.title = 'Facts' 40 | if ($Log.Body.Facts -is [array]) { 41 | $body.facts = $Log.Body.Facts 42 | } elseif ($Log.Body.Facts -is [hashtable]) { 43 | $body.facts = $Log.Body.Facts.Keys | %{ 44 | @{ 45 | name = $_ 46 | value = $Log.Body.Facts[$_] 47 | } 48 | } 49 | } else { 50 | $body.facts = @{ 51 | name='fact' 52 | value = $($Log.Body.Facts | ConvertTo-Json -Depth 3 -Compress) 53 | } 54 | } 55 | } elseif ($Log.Body -is [string]) { 56 | $body.activityTitle = 'Body' 57 | $body.text = $Log.Body 58 | } else { 59 | $body.activityTitle = 'Body' 60 | $body.text = $Log.Body | ConvertTo-Json -Depth 3 -Compress 61 | } 62 | 63 | $sections += $body 64 | } 65 | 66 | if ($Configuration.Details) { 67 | $details = [ordered] @{} 68 | $details.activitySubtitle = 'Details' 69 | $details.facts = $Log.Keys | ?{$_ -notin 'message', 'body'} | sort | %{ 70 | [ordered] @{ 71 | name = $_ 72 | value = if ([string]::IsNullOrEmpty($Log[$_])) {'(none)'} else {[string] $Log[$_]} 73 | } 74 | } 75 | $sections += $details 76 | } 77 | 78 | if ($sections) { 79 | $Payload.sections = $sections 80 | } 81 | 82 | $Payload = $Payload | ConvertTo-Json -Depth 5 -Compress 83 | 84 | Invoke-RestMethod -Method POST -Uri $Configuration.WebHook -Body $Payload -ContentType 'application/json; charset=UTF-8' | Out-Null 85 | } 86 | } -------------------------------------------------------------------------------- /Logging/targets/WebexTeams.ps1: -------------------------------------------------------------------------------- 1 | @{ 2 | Name = 'WebexTeams' 3 | Configuration = @{ 4 | BotToken = @{Required = $true; Type = [string]; Default = $null } 5 | RoomID = @{Required = $true; Type = [string]; Default = $null } 6 | Icons = @{Required = $false; Type = [hashtable]; Default = @{ 7 | 'ERROR' = '🚨' 8 | 'WARNING' = '⚠️' 9 | 'INFO' = 'ℹ️' 10 | 'DEBUG' = '🔎' 11 | } 12 | } 13 | Level = @{Required = $false; Type = [string]; Default = $Logging.Level } 14 | Format = @{Required = $false; Type = [string]; Default = $Logging.Format } 15 | } 16 | Logger = { 17 | param( 18 | [hashtable] $Log, 19 | [hashtable] $Configuration 20 | ) 21 | # Build the Message body 22 | $body = @{ 23 | roomId = $Configuration.RoomId 24 | text = $Configuration.Icons[$Log.Level] + " " + $(Format-Pattern -Pattern $Configuration.Format -Source $Log) 25 | } 26 | 27 | # Convert to JSON 28 | $json = $body | ConvertTo-Json 29 | # Send Message to Cisco Webex API - UTF8 Handling for Emojiis 30 | Invoke-RestMethod -Method Post ` 31 | -Headers @{"Authorization" = "Bearer $($Configuration.BotToken)" } ` 32 | -ContentType "application/json" -Body ([System.Text.Encoding]::UTF8.GetBytes($json)) ` 33 | -Uri "https://api.ciscospark.com/v1/messages" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Logging/targets/WinEventLog.ps1: -------------------------------------------------------------------------------- 1 | @{ 2 | Name = 'WinEventLog' 3 | Configuration = @{ 4 | LogName = @{Required = $true; Type = [string]; Default = $null} 5 | Source = @{Required = $true; Type = [string]; Default = $null} 6 | Level = @{Required = $false; Type = [string]; Default = $Logging.Level} 7 | } 8 | Logger = { 9 | param( 10 | [hashtable] $Log, 11 | [hashtable] $Configuration 12 | ) 13 | 14 | $Params = @{ 15 | EventId = 0 16 | } 17 | 18 | if ($Configuration.LogName) { $Params['LogName'] = $Configuration.LogName } 19 | if ($Configuration.Source) { $Params['Source'] = $Configuration.Source } 20 | if ($Log.Body.EventId) { $Params['EventId'] = $Log.Body.EventId } 21 | 22 | switch ($Log.LevelNo) { 23 | {$_ -ge 40} { $Params['EntryType'] = 'Error' } 24 | {$_ -ge 30 -and $_ -lt 40} { $Params['EntryType'] = 'Warning' } 25 | {$_ -lt 30} { $Params['EntryType'] = 'Information' } 26 | } 27 | 28 | $Params['Message'] = $Log.Message 29 | 30 | if ($Log.ExecInfo) { 31 | $ExceptionFormat = "{0}`n" + 32 | "{1}`n" + 33 | "+ CategoryInfo : {2}`n" + 34 | "+ FullyQualifiedErrorId : {3}`n" 35 | 36 | $ExceptionFields = @($Log.ExecInfo.Exception.Message, 37 | $Log.ExecInfo.InvocationInfo.PositionMessage, 38 | $Log.ExecInfo.CategoryInfo.ToString(), 39 | $Log.ExecInfo.FullyQualifiedErrorId) 40 | 41 | if ( [string]::IsNullOrEmpty($Params['Message']) ){ 42 | $Params['Message'] = $ExceptionFormat -f $ExceptionFields 43 | } else { 44 | $Params['Message'] += "`n`n" + ($ExceptionFormat -f $ExceptionFields) 45 | } 46 | } 47 | 48 | Write-EventLog @Params 49 | } 50 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Powershell Logging Module

2 |

3 | 4 | Powershell Logging logo 5 | 6 |

7 | 8 |

9 | 10 | Master Build Status 11 | 12 | 13 | Develop Build Status 14 | 15 | 16 | Powershell gallery downloads 17 | 18 | Contributor Covenant 19 |

20 | 21 | ## Introduction 22 | 23 | - View the [module documentation][module-doc] 24 | - Project [changelog][changelog] 25 | - Project [homepage][github-logging] 26 | 27 | --- 28 | 29 | ## Contributing 30 | 31 | Please read [CONTRIBUTING.md][contribute] for details on how to contribute to the project. 32 | Take a look at the [CODE_OF_CONDUCT.md][coc] to be a good user/contributor of the project. 33 | 34 | ## Maintainers 35 | 36 | - [Torben Soennecken](https://github.com/tosoikea) 37 | - [Massimo Bonvicini][github] 38 | 39 | ## License 40 | 41 | This project is licensed under the [MIT License][license] 42 | 43 | --- 44 | 45 | Special thanks to: 46 | 47 | - Mark Kraus (@markekraus) for his work on [CI/CD pipeline][get-powershell-blog] 48 | - Boe Prox (@proxb) for his work on [runspaces][runspaces] 49 | 50 | [module-doc]: https://github.com/EsOsO/Logging/wiki 51 | [changelog]: https://github.com/EsOsO/Logging/blob/master/docs/CHANGELOG.md 52 | [github-logging]: https://github.com/EsOsO/Logging 53 | [contribute]: https://github.com/EsOsO/Logging/blob/master/docs/CONTRIBUTING.md 54 | [get-powershell-blog]: http://get-powershellblog.blogspot.com/2017/03/write-faq-n-manual-part1.html 55 | [runspaces]: https://learn-powershell.net/tag/runspace/ 56 | [github]: https://github.com/EsOsO 57 | [license]: https://github.com/EsOsO/Logging/blob/master/docs/LICENSE.md 58 | [coc]: https://github.com/EsOsO/Logging/blob/master/docs/CODE_OF_CONDUCT.md 59 | -------------------------------------------------------------------------------- /Tests/AzureLogAnalytics.Tests.ps1: -------------------------------------------------------------------------------- 1 | Get-Module Logging | Remove-Module Logging -Force -ErrorAction SilentlyContinue 2 | 3 | $ModuleManifestPath = '{0}\..\Logging\Logging.psd1' -f $PSScriptRoot 4 | Import-Module $ModuleManifestPath -Force 5 | 6 | $TargetFile = (Split-Path -Leaf $MyInvocation.MyCommand.Path) -replace '\.Tests\.ps1', '.ps1' 7 | $TargetImplementationPath = '{0}\..\Logging\targets\{1}' -f $PSScriptRoot, $TargetFile 8 | 9 | Describe -Tags Targets, TargetAzureLogAnalytics 'AzureLogAnalytics target' { 10 | It 'should be available in the module' { 11 | $Targets = Get-LoggingAvailableTarget 12 | $Targets.AzureLogAnalytics | Should Not BeNullOrEmpty 13 | } 14 | 15 | It 'should have two required parameters' { 16 | $Targets = Get-LoggingAvailableTarget 17 | $Targets.AzureLogAnalytics.ParamsRequired | Should Be @('SharedKey', 'WorkspaceId') 18 | } 19 | 20 | It 'should call Invoke-WebRequest' { 21 | 22 | Mock Invoke-WebRequest -Verifiable 23 | 24 | $Module = . $TargetImplementationPath 25 | 26 | $Log = [hashtable] @{ 27 | timestamp = Get-Date -UFormat '%Y-%m-%dT%T%Z' 28 | timestamputc = '2020-02-24T22:35:23.000Z' 29 | level = 'INFO' 30 | levelno = 20 31 | lineno = 1 32 | pathname = 'c:\Scripts\Script.ps1' 33 | filename = 'TestScript.ps1' 34 | caller = 'TestScript.ps1' 35 | message = 'Hello, Azure!' 36 | body = $null 37 | execinfo = $null 38 | pid = $PID 39 | } 40 | 41 | $Configuration = @{ 42 | WorkspaceId = '12345' 43 | SharedKey = 'Q3Vyc2UgeW91ciBzdWRkZW4gYnV0IGluZXZpdGFibGUgYmV0cmF5YWwh' 44 | LogType = 'TestLog' 45 | } 46 | 47 | { & $Module.Logger $Log $Configuration } | Should -Not -Throw 48 | 49 | Assert-MockCalled -CommandName 'Invoke-WebRequest' -Times 1 -Exactly 50 | } 51 | } -------------------------------------------------------------------------------- /Tests/Console.Tests.ps1: -------------------------------------------------------------------------------- 1 | if (Get-Module Logging) { 2 | Remove-Module Logging -Force -ErrorAction SilentlyContinue 3 | } 4 | 5 | $ModuleManifestPath = '{0}\..\Logging\Logging.psd1' -f $PSScriptRoot 6 | Import-Module $ModuleManifestPath -Force 7 | 8 | Describe -Tags Targets, TargetConsole 'Console target' { 9 | # Give time to the runspace to init the targets 10 | Start-Sleep -Milliseconds 100 11 | 12 | It 'should be available in the module' { 13 | $Targets = Get-LoggingAvailableTarget 14 | $Targets.Console | Should Not BeNullOrEmpty 15 | } 16 | 17 | It "shouldn't have required parameters" { 18 | $Targets = Get-LoggingAvailableTarget 19 | $Targets.Console.ParamsRequired | Should BeNullOrEmpty 20 | } 21 | } -------------------------------------------------------------------------------- /Tests/FileTarget.Tests.ps1: -------------------------------------------------------------------------------- 1 | if (Get-Module Logging) { 2 | Remove-Module Logging -Force -ErrorAction SilentlyContinue 3 | } 4 | 5 | $ManifestPath = '{0}\..\Logging\Logging.psd1' -f $PSScriptRoot 6 | 7 | Import-Module $ManifestPath -Force 8 | 9 | Describe -Tags Targets, TargetFile 'File target' { 10 | 11 | It 'should resolve relative paths' { 12 | 13 | Add-LoggingTarget -Name File -Configuration @{ 14 | Path = '..\Test.log' 15 | } 16 | 17 | $a = Get-LoggingTarget 18 | $a.Values.Path.Contains('..') | Should Be $false 19 | } 20 | 21 | } -------------------------------------------------------------------------------- /Tests/Help.Tests.ps1: -------------------------------------------------------------------------------- 1 | if (Get-Module Logging) { 2 | Remove-Module Logging -Force -ErrorAction SilentlyContinue 3 | } 4 | 5 | $ManifestPath = '{0}\..\Logging\Logging.psd1' -f $PSScriptRoot 6 | 7 | Import-Module $ManifestPath -Force 8 | 9 | Describe "Help tests for $moduleName" -Tags Build { 10 | 11 | $Functions = Get-Command -Module Logging -CommandType Function 12 | 13 | foreach ($Function in $Functions) { 14 | $help = Get-Help $Function.name 15 | Context $help.name { 16 | It "Has a HelpUri" { 17 | $Function.HelpUri | Should Not BeNullOrEmpty 18 | } 19 | 20 | It "Has related Links" { 21 | $help.relatedLinks.navigationLink.uri.count | Should BeGreaterThan 0 22 | } 23 | 24 | It "Has a description" { 25 | $help.description | Should Not BeNullOrEmpty 26 | } 27 | 28 | It "Has an example" { 29 | $help.examples | Should Not BeNullOrEmpty 30 | } 31 | 32 | foreach ($parameter in $help.parameters.parameter) { 33 | if ($parameter -notmatch 'whatif|confirm') { 34 | It "Has a Parameter description for '$($parameter.name)'" { 35 | $parameter.Description.text | Should Not BeNullOrEmpty 36 | } 37 | } 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /Tests/Logging.Tests.ps1: -------------------------------------------------------------------------------- 1 | if (Get-Module Logging) { 2 | Remove-Module Logging -Force -ErrorAction SilentlyContinue 3 | } 4 | 5 | $ManifestPath = '{0}\..\Logging\Logging.psd1' -f $PSScriptRoot 6 | 7 | Import-Module $ManifestPath -Force 8 | 9 | Describe -Tags Build 'Logging manifest' { 10 | $script:Manifest = $null 11 | It 'has a manifest' { 12 | $ManifestPath | Should Exist 13 | } 14 | 15 | It 'has a valid manifest' { 16 | { 17 | $script:Manifest = Test-ModuleManifest -Path $ManifestPath -ErrorAction Stop -WarningAction SilentlyContinue 18 | } | Should Not Throw 19 | } 20 | 21 | It 'has a valid name in the manifest' { 22 | $script:Manifest.Name | Should Be 'Logging' 23 | } 24 | 25 | It 'has a valid guid in the manifest' { 26 | $script:Manifest.Guid | Should Be '25a60f1d-85dd-4ad6-9efc-35fd3894f6c1' 27 | } 28 | 29 | It 'has a valid version in the manifest' { 30 | $script:Manifest.Version -as [Version] | Should Not BeNullOrEmpty 31 | } 32 | } 33 | 34 | InModuleScope Logging { 35 | Describe -Tags Build 'Internal Vars' { 36 | It 'sets up internal variables' { 37 | Test-Path Variable:Logging | Should Be $true 38 | Test-Path Variable:Defaults | Should Be $true 39 | Test-Path Variable:LevelNames | Should Be $true 40 | Test-Path Variable:LoggingRunspace | Should Be $true 41 | Test-Path Variable:LoggingEventQueue | Should Be $true 42 | } 43 | } 44 | 45 | Describe -Tags Build 'Token replacement' { 46 | $TimeStamp = Get-Date -UFormat '%Y-%m-%dT%T%Z' 47 | $Object = [PSCustomObject] @{ 48 | message = 'Test' 49 | timestamp = $TimeStamp 50 | level = 'INFO' 51 | } 52 | 53 | It 'should return a string with token replaced' { 54 | Format-Pattern -Pattern '%{message}' -Source $Object | Should Be 'Test' 55 | } 56 | 57 | It 'should return a string with token replaced and padded' { 58 | Format-Pattern -Pattern '%{message:7}' -Source $Object | Should Be ' Test' 59 | Format-Pattern -Pattern '%{message:-7}' -Source $Object | Should Be 'Test ' 60 | } 61 | 62 | It 'should return a string with a timestamp, no formatter' { 63 | Format-Pattern -Pattern '%{timestamp}' -Source $Object | Should Be $TimeStamp 64 | } 65 | 66 | It 'should return a string using a custom Unix format with token' { 67 | Format-Pattern -Pattern '%{timestamp:+%Y%m%d}' -Source $Object | Should Be $(Get-Date $TimeStamp -UFormat '%Y%m%d') 68 | } 69 | 70 | It 'should return a string using a custom Unix format without token' { 71 | Format-Pattern -Pattern '%{+%Y%m%d}' -Source $Object | Should Be $(Get-Date -UFormat '%Y%m%d') 72 | } 73 | 74 | It 'should return a string using a custom Unix format with a full day name, with token' { 75 | Format-Pattern -Pattern '%{timestamp:+%A, %B %d, %Y}' -Source $Object | Should Be $(Get-Date $TimeStamp -UFormat '%A, %B %d, %Y') 76 | } 77 | 78 | It 'should return a string using a custom Unix format with a full day name, without token' { 79 | Format-Pattern -Pattern '%{+%A, %B %d, %Y}' -Source $Object | Should Be $(Get-Date -UFormat '%A, %B %d, %Y') 80 | } 81 | 82 | It 'should return a string using a custom Unix format with token, with padding' { 83 | Format-Pattern -Pattern '%{timestamp:+%Y%m:12}' -Source $Object | Should Be $(" {0}" -f (Get-Date $TimeStamp -UFormat '%Y%m')) 84 | } 85 | 86 | It 'should return a string using a custom Unix format without token, with padding' { 87 | Format-Pattern -Pattern '%{+%Y%m:12}' -Source $Object | Should Be $(" {0}" -f (Get-Date -UFormat '%Y%m')) 88 | } 89 | 90 | It 'should return a string using a custom [DateTimeFormatInfo] string with token' { 91 | Format-Pattern -Pattern '%{timestamp:+yyyy/MM/dd HH:mm:ss.fff}' -Source $Object | Should Be $(Get-Date $TimeStamp -Format 'yyyy/MM/dd HH:mm:ss.fff') 92 | } 93 | 94 | It 'should return a string using a custom [DateTimeFormatInfo] string without token' { 95 | Format-Pattern -Pattern '%{+yyyy/MM/dd HH}' -Source $Object | Should Be $(Get-Date -Format 'yyyy/MM/dd HH') 96 | } 97 | 98 | It 'should return a string using a custom [DateTimeFormatInfo] string with token, with padding' { 99 | Format-Pattern -Pattern '%{timestamp:+HH:mm:ss.fff:15}' -Source $Object | Should Be $(" {0}" -f (Get-Date $TimeStamp -Format 'HH:mm:ss.fff')) 100 | } 101 | 102 | It 'should return a string using a custom [DateTimeFormatInfo] string without token, with padding' { 103 | Format-Pattern -Pattern '%{+yyyy/MM/dd HH:15}' -Source $Object | Should Be $(" {0}" -f (Get-Date -Format 'yyyy/MM/dd HH')) 104 | } 105 | } 106 | 107 | Describe -Tags Build 'Logging Levels' { 108 | It 'should return logging levels names' { 109 | Get-LevelsName | Should Be @('DEBUG', 'ERROR', 'INFO', 'NOTSET', 'WARNING') 110 | } 111 | 112 | It 'should return loggin level name' { 113 | Get-LevelName -Level 10 | Should Be 'DEBUG' 114 | { Get-LevelName -Level 'DEBUG' } | Should Throw 115 | } 116 | 117 | It 'should return logging levels number' { 118 | Get-LevelNumber -Level 0 | Should Be 0 119 | Get-LevelNumber -Level 'NOTSET' | Should Be 0 120 | } 121 | 122 | It 'should throw on invalid level' { 123 | { Get-LevelNumber -Level 11 } | Should Throw 124 | { Get-LevelNumber -Level 'LEVEL_UNKNOWN' } | Should Throw 125 | } 126 | 127 | It 'should add a new logging level' { 128 | Add-LoggingLevel -Level 11 -LevelName 'Test' 129 | Get-LevelsName | Should Be @('DEBUG', 'ERROR', 'INFO', 'NOTSET', 'TEST', 'WARNING') 130 | } 131 | 132 | It 'should change the level name if same level number' { 133 | Add-LoggingLevel -Level 11 -LevelName 'Foo' 134 | Get-LevelsName | Should Be @('DEBUG', 'ERROR', 'FOO', 'INFO', 'NOTSET', 'WARNING') 135 | } 136 | 137 | It 'should change the level number if same level name' { 138 | Add-LoggingLevel -Level 21 -LevelName 'Foo' 139 | Get-LevelsName | Should Be @('DEBUG', 'ERROR', 'FOO', 'INFO', 'NOTSET', 'WARNING') 140 | Get-LevelNumber -Level 'FOO' | Should Be 21 141 | } 142 | 143 | It 'return the default logging level' { 144 | Get-LoggingDefaultLevel | Should Be 'NOTSET' 145 | } 146 | 147 | It 'sets the default logging level' { 148 | Set-LoggingDefaultLevel -Level INFO 149 | Get-LoggingDefaultLevel | Should Be 'INFO' 150 | } 151 | 152 | It 'change the logging level of available targets' { 153 | Add-LoggingTarget -Name Console 154 | (Get-LoggingTarget -Name Console).Level | Should Be 'INFO' 155 | } 156 | It 'change the logging level of already configured targets' { 157 | Set-LoggingDefaultLevel -Level DEBUG 158 | (Get-LoggingTarget -Name Console).Level | Should Be 'DEBUG' 159 | } 160 | 161 | } 162 | 163 | Describe -Tags Build 'Logging Targets' { 164 | $TargetsPath = '{0}\..\Logging\targets' -f $PSScriptRoot 165 | $Targets = Get-ChildItem -Path $TargetsPath -Filter '*.ps1' 166 | 167 | It 'loads the logging targets' { 168 | $Logging.Targets.Count | Should Be $Targets.Count 169 | } 170 | 171 | It 'returns the loaded logging targets' { 172 | $AvailableTargets = Get-LoggingAvailableTarget 173 | # not sure how to test System.Collections.Concurrent.ConcurrentDictionary[string, hashtable] 174 | # $AvailableTargets | Should Be [] 175 | $AvailableTargets.Count | Should Be $Targets.Count 176 | } 177 | 178 | It 'is case-insensitive' { 179 | $AvailableTargets = Get-LoggingAvailableTarget 180 | $AvailableTargets[$AvailableTargets.Keys[0].ToLower()] | Should Not BeNullOrEmpty 181 | } 182 | } 183 | 184 | Describe -Tags Build 'Logging Format' { 185 | It 'gets the default format' { 186 | Get-LoggingDefaultFormat | Should Be $Defaults.Format 187 | } 188 | 189 | It 'sets the default logging format' { 190 | $NewFormat = '[%{level:-7}] %{message}' 191 | Get-LoggingDefaultFormat | Should Be $Defaults.Format 192 | Set-LoggingDefaultFormat -Format $NewFormat 193 | Get-LoggingDefaultFormat | Should Be $NewFormat 194 | } 195 | 196 | It 'change the logging format of already configured targets' { 197 | $NewFormat = '[%{level:-7}] %{message}' 198 | Add-LoggingTarget -Name Console 199 | Set-LoggingDefaultFormat -Format $NewFormat 200 | (Get-LoggingTarget -Name Console).Format | Should Be $NewFormat 201 | } 202 | 203 | It 'change the default format of available targets' { 204 | $NewFormat = '[%{level:-7}] %{message}' 205 | Set-LoggingDefaultFormat -Format $NewFormat 206 | Add-LoggingTarget -Name Console 207 | (Get-LoggingTarget -Name Console).Format | Should Be $NewFormat 208 | } 209 | } 210 | 211 | Describe -Tags Build 'Logging Caller Scope' { 212 | It 'should be the default value' { 213 | Get-LoggingCallerScope | Should -Be $Defaults.CallerScope 214 | } 215 | 216 | It 'should change the caller scope value' { 217 | $newScope = 3 218 | Get-LoggingCallerScope | Should -Be $Defaults.CallerScope 219 | Set-LoggingCallerScope -CallerScope $newScope 220 | Get-LoggingCallerScope | Should -Be $newScope 221 | } 222 | } 223 | 224 | Describe -Tags Build 'Dynamic Parameter' { 225 | It 'should contain the default levels' { 226 | $dynamicDictionary = New-LoggingDynamicParam -Name "PesterTest" -Level 227 | 228 | [String[]] $allowedValues = $dynamicDictionary["PesterTest"].Attributes[1].ValidValues 229 | 230 | "ERROR" -in $allowedValues | Should -Be $true 231 | "DEBUG" -in $allowedValues | Should -Be $true 232 | } 233 | 234 | It 'should contain the default targets' { 235 | $dynamicDictionary = New-LoggingDynamicParam -Name "PesterTest" -Target 236 | 237 | [String[]] $allowedValues = $dynamicDictionary["PesterTest"].Attributes[1].ValidValues 238 | 239 | "File" -in $allowedValues | Should -Be $true 240 | "Console" -in $allowedValues | Should -Be $true 241 | } 242 | } 243 | 244 | Describe -Tags Build 'Logging Producer-Consumer' { 245 | It 'should start logging manager after module import' { 246 | Test-Path Variable:LoggingEventQueue | Should Be $true 247 | Test-Path Variable:LoggingRunspace | Should Be $true 248 | } 249 | } 250 | } 251 | 252 | Describe -Tags Unit 'Performance load' { 253 | $ManifestPath = '{0}\..\Logging\Logging.psd1' -f $PSScriptRoot 254 | 255 | BeforeEach { 256 | Remove-Module Logging -Force 257 | Import-Module $ManifestPath -Force 258 | Start-Sleep -Seconds 1 259 | } 260 | 261 | It 'should be able to handle [light] load' { 262 | [int] $desiredCount = 100 263 | [string] $smallLog = [System.IO.Path]::GetTempFileName() 264 | 265 | Add-LoggingTarget -Name File -Configuration @{Path = $smallLog } 266 | 267 | for ([int] $lI = 0; $lI -lt $desiredCount; $lI++) { 268 | Write-Log -Level WARNING -Message 'Test: {0}' -Arguments $lI 269 | } 270 | 271 | Wait-Logging 272 | (Get-Content $smallLog).Count | Should -Be $desiredCount 273 | } 274 | 275 | It 'should be able to handle [medium] load' { 276 | [int] $desiredCount = 1000 277 | [string] $mediumLog = [System.IO.Path]::GetTempFileName() 278 | 279 | Add-LoggingTarget -Name File -Configuration @{Path = $mediumLog } 280 | 281 | for ([int] $lI = 0; $lI -lt $desiredCount; $lI++) { 282 | Write-Log -Level WARNING -Message 'Test: {0}' -Arguments $lI 283 | } 284 | 285 | Wait-Logging 286 | (Get-Content $mediumLog).Count | Should -Be $desiredCount 287 | } 288 | 289 | It 'should be able to handle [high] load' { 290 | [int] $desiredCount = 10000 291 | [string] $highLog = [System.IO.Path]::GetTempFileName() 292 | Add-LoggingTarget -Name File -Configuration @{Path = $highLog; Encoding = "UTF8" } 293 | 294 | for ([int] $lI = 0; $lI -lt $desiredCount; $lI++) { 295 | Write-Log -Level WARNING -Message 'Test: {0}' -Arguments $lI 296 | } 297 | 298 | Wait-Logging 299 | (Get-Content $highLog).Count | Should -Be $desiredCount 300 | } 301 | } 302 | -------------------------------------------------------------------------------- /Tests/Teams.Tests.ps1: -------------------------------------------------------------------------------- 1 | if (Get-Module Logging) { 2 | Remove-Module Logging -Force -ErrorAction SilentlyContinue 3 | } 4 | 5 | $ModuleManifestPath = '{0}\..\Logging\Logging.psd1' -f $PSScriptRoot 6 | Import-Module $ModuleManifestPath -Force 7 | 8 | $TargetFile = (Split-Path -Leaf $MyInvocation.MyCommand.Path) -replace '\.Tests\.ps1', '.ps1' 9 | $TargetImplementationPath = '{0}\..\Logging\targets\{1}' -f $PSScriptRoot, $TargetFile 10 | 11 | Describe -Tags Targets, TargetTeams 'Teams target' { 12 | It 'should be available in the module' { 13 | $Targets = Get-LoggingAvailableTarget 14 | $Targets.Teams | Should Not BeNullOrEmpty 15 | } 16 | 17 | It 'should have two required parameters' { 18 | $Targets = Get-LoggingAvailableTarget 19 | $Targets.Teams.ParamsRequired | Should Be @('WebHook') 20 | } 21 | 22 | It 'should call Invoke-RestMethod' { 23 | Mock Invoke-RestMethod -Verifiable 24 | 25 | $Module = . $TargetImplementationPath 26 | 27 | $Message = [hashtable] @{ 28 | level = 'ERROR' 29 | levelno = 40 30 | message = 'Hello, Teams!' 31 | } 32 | 33 | $Configuration = @{ 34 | WebHook = 'https://office.microsoft.com' 35 | Details = $true 36 | Colors = $Module.Configuration.Colors.Default 37 | } 38 | 39 | & $Module.Logger $Message $Configuration 40 | 41 | Assert-MockCalled -CommandName 'Invoke-RestMethod' -Times 1 -Exactly 42 | } 43 | } -------------------------------------------------------------------------------- /Tests/Test.ps1: -------------------------------------------------------------------------------- 1 | Remove-Module Logging -Force 2 | Import-Module ..\Logging\Logging.psm1 3 | 4 | Add-LoggingTarget -Name Console -Configuration @{Level = 'DEBUG'; Format = '[%{timestamputc}] %{filename} [lineno: %{lineno}] %{message}'} 5 | 6 | 1..10 | Foreach-Object { 7 | Write-Log -Level (Get-Random 'DEBUG', 'INFO', 'WARNING', 'ERROR') -Message 'Hello, World!' 8 | } 9 | 10 | Add-LoggingTarget -Name Console -Configuration @{Level = 'DEBUG'; Format = '[%{timestamputc}] %{filename} [%{caller}] %{message}'} 11 | 12 | function Invoke-CallerFunction { 13 | [CmdletBinding()] 14 | param() 15 | 16 | 1..5 | ForEach-Object { 17 | Write-Log -Level (Get-Random 'DEBUG', 'INFO', 'WARNING', 'ERROR') -Message 'Hello, World! (With caller scope)' 18 | } 19 | } 20 | 21 | Invoke-CallerFunction 22 | 23 | function Write-CustomLog { 24 | [CmdletBinding()] 25 | param( 26 | $Level, 27 | $Message 28 | ) 29 | 30 | Write-Log -Level $Level -Message $Message 31 | } 32 | 33 | function Invoke-CallerFunctionWithCustomLog { 34 | 35 | 1..5 | ForEach-Object { 36 | Write-CustomLog -Level (Get-Random 'DEBUG', 'INFO', 'WARNING', 'ERROR') -Message 'Hello, World! (With caller scope at level 2)' 37 | } 38 | } 39 | 40 | Invoke-CallerFunctionWithCustomLog 41 | -------------------------------------------------------------------------------- /Tests/WebexTeams.Tests.ps1: -------------------------------------------------------------------------------- 1 | if (Get-Module Logging) { 2 | Remove-Module Logging -Force -ErrorAction SilentlyContinue 3 | } 4 | 5 | $ModuleManifestPath = '{0}\..\Logging\Logging.psd1' -f $PSScriptRoot 6 | Import-Module $ModuleManifestPath -Force 7 | 8 | $TargetFile = (Split-Path -Leaf $MyInvocation.MyCommand.Path) -replace '\.Tests\.ps1', '.ps1' 9 | $TargetImplementationPath = '{0}\..\Logging\targets\{1}' -f $PSScriptRoot, $TargetFile 10 | 11 | Describe -Tags Targets, TargetWebexTeams 'WebexTeams target' { 12 | It 'should be available in the module' { 13 | $Targets = Get-LoggingAvailableTarget 14 | $Targets.WebexTeams | Should Not BeNullOrEmpty 15 | } 16 | 17 | It 'should have two required parameters' { 18 | $Targets = Get-LoggingAvailableTarget 19 | $Targets.WebexTeams.ParamsRequired | Should Be @('BotToken', 'RoomID') 20 | } 21 | 22 | It 'should call Invoke-RestMethod' -Skip { 23 | Mock Invoke-RestMethod -Verifiable 24 | 25 | $Module = . $TargetImplementationPath 26 | 27 | $Log = [hashtable] @{ 28 | level = 'ERROR' 29 | levelno = 40 30 | message = 'Hello, WebexTeams!' 31 | } 32 | 33 | $Configuration = @{ 34 | BotToken = 'SOMEINVALIDTOKEN' 35 | RoomID = 'SOMEINVALIDROOMID' 36 | Icons = @{} 37 | } 38 | 39 | & $Module.Logger $Log $Configuration 40 | 41 | Assert-MockCalled -CommandName 'Invoke-RestMethod' -Times 1 -Exactly 42 | } 43 | } -------------------------------------------------------------------------------- /Tests/WinEventLog.Tests.ps1: -------------------------------------------------------------------------------- 1 | if (Get-Module Logging) { 2 | Remove-Module Logging -Force -ErrorAction SilentlyContinue 3 | } 4 | 5 | $ModuleManifestPath = '{0}\..\Logging\Logging.psd1' -f $PSScriptRoot 6 | Import-Module $ModuleManifestPath -Force 7 | 8 | $TargetFile = (Split-Path -Leaf $MyInvocation.MyCommand.Path) -replace '\.Tests\.ps1', '.ps1' 9 | $TargetImplementationPath = '{0}\..\Logging\targets\{1}' -f $PSScriptRoot, $TargetFile 10 | 11 | Describe -Tags Targets, TargetWinEventLog 'WinEventLog target' { 12 | # Give time to the runspace to init the targets 13 | Start-Sleep -Milliseconds 100 14 | 15 | It 'should be available in the module' { 16 | $Targets = Get-LoggingAvailableTarget 17 | $Targets.WinEventLog | Should Not BeNullOrEmpty 18 | } 19 | 20 | It 'should have two required parameters' { 21 | $Targets = Get-LoggingAvailableTarget 22 | $Targets.WinEventLog.ParamsRequired | Should Be @('LogName', 'Source') 23 | } 24 | 25 | It 'should call Write-EventLog' -Skip { 26 | Mock Write-EventLog -Verifiable 27 | 28 | $Message = [hashtable] @{ 29 | level = 'ERROR' 30 | levelno = 40 31 | message = 'Hello, Windows Event Log!' 32 | body = @{ EventId = 123 } 33 | } 34 | 35 | $LoggerFormat = '[%{timestamp:+%Y-%m-%d %T%Z}] [%{level:-7}] %{message}' 36 | 37 | $Configuration = @{ 38 | LogName = 'Application' 39 | Source = 'PesterTestSource' 40 | } 41 | 42 | # Wasn't able to get a 'Write-EventLog' mock working inside of the .Logger scriptblocks which 43 | # are already loaded into the module. Instead, load the scriptblock for testing here 44 | $Module = . $TargetImplementationPath 45 | & $Module.Logger $Message $Configuration 46 | 47 | Assert-MockCalled -CommandName 'Write-EventLog' -Times 1 -Exactly -ParameterFilter { 48 | ($LogName -eq 'Application') -and 49 | ($Source -eq 'PesterTestSource') -and 50 | ($EntryType -eq 'Error') -and 51 | ($EventId -eq 123) -and 52 | ($Message -eq 'Hello, Windows Event Log!') 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /Tests/WriteLogCallstackTokens.Tests.ps1: -------------------------------------------------------------------------------- 1 | if (Get-Module Logging) { 2 | Remove-Module Logging -Force -ErrorAction SilentlyContinue 3 | } 4 | 5 | $moduleManifestPath = '{0}\..\Logging\Logging.psd1' -f $PSScriptRoot 6 | Import-Module -Name $moduleManifestPath -Force 7 | Set-StrictMode -Version Latest 8 | 9 | # These tests verify that Write-Log determines the correct values for the tokens whose values are taken from 10 | # the callstack: 'pathname', 'filename', 'lineno', and 'caller'. 11 | # 12 | # Since Write-Log doesn't produce output directly, we use the File target to generate files containing these 13 | # tokens and then inspect the files to verify that they have the expected contents. 14 | # 15 | # If we call Write-Log directly from within a Pester test, we can't predict exactly what the callstack will be, 16 | # since it will include some of our code and some of Pester's code. This makes it impossible to predict which 17 | # values Write-Log will use for the tokens. 18 | # 19 | # In order to create an environment where we can predict the contents of the callstack, we need to create scripts 20 | # that call Write-Log and then run those scripts using the PowerShell executable. In that environment, we can 21 | # predict the contents of the callstack and therefore what the values of the tokens should be. 22 | # 23 | # Accordingly, these tests use Pester to create scripts that call Write-Log, and then run those scripts using 24 | # the PowerShell executable, instead of calling Write-Log from directly within the tests. These tests will run 25 | # relatively slowly as a result of this. 26 | Describe 'CallerScope' { 27 | BeforeAll { 28 | # Set to $true to enable output of additional debugging information for this test code. 29 | $debugTests = $false 30 | 31 | $logPath = Join-Path -Path $TestDrive -ChildPath 'log.txt' 32 | 33 | $scriptName = 'script.ps1' 34 | $scriptPath = Join-Path -Path $TestDrive -ChildPath $scriptName 35 | 36 | $codeLineImportModule = "Import-Module -Name '$moduleManifestPath'" 37 | $codeLineSetCallerScope = 'Set-LoggingCallerScope -CallerScope {0}' 38 | $codeLineAddTarget = ("Add-LoggingTarget -Name 'File' -Configuration @{ Path = '$logPath'; " + 39 | "Format = '[%{pathname}] [%{filename}] [%{lineno}] [%{caller}]' }") 40 | $codeLineWriteLog = 'Write-Log -Message ''Test Message''' 41 | $codeLineRemoveModule = 'Remove-Module -Name Logging' 42 | 43 | $codeSetup = @( 44 | $codeLineImportModule 45 | $codeLineSetCallerScope 46 | $codeLineAddTarget 47 | ) 48 | $codeCleanup = @( 49 | $codeLineRemoveModule 50 | ) 51 | 52 | function InvokePowerShellExe { 53 | param ( 54 | [string]$Path = $scriptPath, 55 | [string]$Command 56 | ) 57 | 58 | if ($PSBoundParameters.ContainsKey('Command')) { 59 | $run = "-Command `"$Command`"" 60 | } else { 61 | $run = "-File `"$Path`"" 62 | } 63 | 64 | if ($PSVersionTable.PSEdition -eq 'Desktop') { 65 | $powershell_exe = 'powershell.exe' 66 | } else { 67 | $powershell_exe = 'pwsh' 68 | } 69 | $powershell_exe = Join-Path -Path $PSHOME -ChildPath $powershell_exe 70 | 71 | $params = @{ 72 | Wait = $true 73 | NoNewWindow = $true 74 | FilePath = $powershell_exe 75 | ArgumentList = @('-NoLogo','-NoProfile','-NonInteractive',$run) 76 | } 77 | 78 | Start-Process @params 79 | } 80 | 81 | # Reads through an array of code lines to determine which one contains the line that calls 82 | # Set-LoggingCallerScope, then replaces the "{0}" on that line with the value of the $Scope 83 | # parameter and returns a new array containing the modified line. 84 | function InjectScopeInCode { 85 | param ( 86 | [string[]]$Code, 87 | [int]$Scope 88 | ) 89 | 90 | # Clone the array that contains the code so that we don't modify the 91 | # original when we inject the scope. 92 | $injectedCode = $Code.Clone() 93 | 94 | $scopeIndex = $injectedCode.IndexOf($codeLineSetCallerScope) 95 | if ($scopeIndex -eq -1) { 96 | throw "Could not determine where to inject scope [$Scope]." 97 | } 98 | $injectedCode[$scopeIndex] = $injectedCode[$scopeIndex] -f $Scope 99 | 100 | if ($debugTests) { 101 | Write-Host -ForegroundColor Magenta -Object 'Code with scope injected:' 102 | foreach ($line in $injectedCode) { 103 | Write-Host -ForegroundColor Magenta -Object $line 104 | } 105 | } 106 | 107 | $injectedCode 108 | } 109 | 110 | function SetScriptFile { 111 | param ( 112 | [string]$Path = $scriptPath, 113 | [string[]]$Code, 114 | [int]$Scope 115 | ) 116 | 117 | $codeToWrite = $Code 118 | if ($PSBoundParameters.ContainsKey('Scope')) { 119 | $codeToWrite = InjectScopeInCode -Code $codeToWrite -Scope $Scope 120 | } 121 | 122 | Set-Content -Path $Path -Value $codeToWrite 123 | } 124 | 125 | function InvokeShould { 126 | param ( 127 | [string]$ExpectedValue 128 | ) 129 | 130 | if ($debugTests) { 131 | Write-Host -ForegroundColor Magenta -Object 'Contents of log file:' 132 | Write-Host -ForegroundColor Magenta -Object (Get-Content -Path $logPath) 133 | } 134 | 135 | $logPath | Should -FileContentMatch ([regex]::Escape($ExpectedValue)) 136 | } 137 | } 138 | 139 | AfterEach { 140 | if (Test-Path -Path $logPath) { 141 | Remove-Item -Path $logPath 142 | } 143 | 144 | $testScope++ 145 | } 146 | 147 | Context 'Tests that don''t use a wrapper' { 148 | BeforeAll { 149 | $codeWriteNoWrapper = $codeSetup + $codeLineWriteLog + $codeCleanup 150 | $lineNumWriteLog = $codeWriteNoWrapper.IndexOf($codeLineWriteLog) + 1 151 | } 152 | 153 | Context 'Write-Log called directly rather than from a script file' { 154 | BeforeAll { 155 | $testScope = 1 156 | } 157 | 158 | BeforeEach { 159 | $injectedCode = InjectScopeInCode -Scope $testScope -Code $codeWriteNoWrapper 160 | $commands = $injectedCode -join "; " 161 | InvokePowerShellExe -Command $commands 162 | } 163 | 164 | It 'Scope 1' { 165 | InvokeShould "[] [] [1] []" 166 | } 167 | } 168 | 169 | Context 'Script File -> Write-Log' { 170 | BeforeAll { 171 | $testScope = 1 172 | } 173 | 174 | BeforeEach { 175 | SetScriptFile -Code $codeWriteNoWrapper -Scope $testScope 176 | InvokePowerShellExe 177 | } 178 | 179 | It 'Scope 1' { 180 | InvokeShould "[$scriptPath] [$scriptName] [$lineNumWriteLog] [$scriptName]" 181 | } 182 | } 183 | 184 | Context 'Caller Script File -> Script File -> Write-Log' { 185 | BeforeAll { 186 | $testScope = 1 187 | 188 | $callerScriptName = 'caller.ps1' 189 | $callerScriptPath = Join-Path -Path $TestDrive -ChildPath $callerScriptName 190 | $callerScriptCode = @( 191 | "& $scriptPath" 192 | ) 193 | SetScriptFile -Path $callerScriptPath -Code $callerScriptCode 194 | } 195 | 196 | BeforeEach { 197 | SetScriptFile -Code $codeWriteNoWrapper -Scope $testScope 198 | InvokePowerShellExe -Path $callerScriptPath 199 | } 200 | 201 | It 'Scope 1 - Script File Calling Write-Log' { 202 | InvokeShould "[$scriptPath] [$scriptName] [$lineNumWriteLog] [$scriptName]" 203 | } 204 | 205 | It 'Scope 2 - Caller Script File Calling Script File' { 206 | InvokeShould "[$callerScriptPath] [$callerScriptName] [1] [$callerScriptName]" 207 | } 208 | } 209 | } 210 | 211 | Context 'Tests that do use a wrapper' { 212 | BeforeAll { 213 | $wrapperFunctionName = 'Wrapper' 214 | $codeLineCallWrapper = $wrapperFunctionName 215 | $codeLineCallWriteLogInWrapper = "function $wrapperFunctionName { $codeLineWriteLog }" 216 | } 217 | 218 | Context 'Script File -> Wrapper -> Write-Log' { 219 | BeforeAll { 220 | $testScope = 1 221 | } 222 | 223 | BeforeEach { 224 | $code = 225 | $codeSetup + 226 | $codeLineCallWriteLogInWrapper + 227 | $codeLineCallWrapper + 228 | $codeCleanup 229 | SetScriptFile -Code $code -Scope $testScope 230 | InvokePowerShellExe 231 | } 232 | 233 | It 'Scope 1 - Wrapper Calling Write-Log' { 234 | $lineNumWriteLogCall = $code.IndexOf($codeLineCallWriteLogInWrapper) + 1 235 | InvokeShould "[$scriptPath] [$scriptName] [$lineNumWriteLogCall] [$wrapperFunctionName]" 236 | } 237 | 238 | It 'Scope 2 - Script File Calling Wrapper' { 239 | $lineNumWrapperCall = $code.IndexOf($codeLineCallWrapper) + 1 240 | InvokeShould "[$scriptPath] [$scriptName] [$lineNumWrapperCall] [$scriptName]" 241 | } 242 | } 243 | 244 | Context 'Script File -> Business Logic -> Wrapper -> Write-Log' { 245 | BeforeAll { 246 | $testScope = 1 247 | } 248 | 249 | BeforeEach { 250 | $businessLogicFunctionName = 'BusinessLogic' 251 | $codeLineCallBusinessLogicInScript = $businessLogicFunctionName 252 | $codeLineCallWrapperInBusinessLogic = 253 | "function $businessLogicFunctionName { $codeLineCallWrapper }" 254 | $code = 255 | $codeSetup + 256 | $codeLineCallWriteLogInWrapper + 257 | $codeLineCallWrapperInBusinessLogic + 258 | $codeLineCallBusinessLogicInScript + 259 | $codeCleanup 260 | SetScriptFile -Code $code -Scope $testScope 261 | InvokePowerShellExe 262 | } 263 | 264 | It 'Scope 1 - Wrapper Calling Write-Log' { 265 | $lineNumWriteLogCall = $code.IndexOf($codeLineCallWriteLogInWrapper) + 1 266 | InvokeShould "[$scriptPath] [$scriptName] [$lineNumWriteLogCall] [$wrapperFunctionName]" 267 | } 268 | 269 | It 'Scope 2 - Business Logic Calling Wrapper' { 270 | $lineNumWrapperCall = $code.IndexOf($codeLineCallWrapperInBusinessLogic) + 1 271 | InvokeShould "[$scriptPath] [$scriptName] [$lineNumWrapperCall] [$businessLogicFunctionName]" 272 | } 273 | 274 | It 'Scope 3 - Script File Calling Business Logic' { 275 | $lineNumBusinessLogicCall = $code.IndexOf($codeLineCallBusinessLogicInScript) + 1 276 | InvokeShould "[$scriptPath] [$scriptName] [$lineNumBusinessLogicCall] [$scriptName]" 277 | } 278 | } 279 | } 280 | } 281 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: '{build}' 2 | 3 | image: 4 | - Visual Studio 2017 5 | 6 | branches: 7 | only: 8 | - master 9 | - dev 10 | 11 | skip_commits: 12 | message: /\[skip ci\]/ 13 | 14 | environment: 15 | APPVEYOR_NUGET_API_KEY: 16 | secure: AtKUh7VTiq0BCPftMVX+nkY7Jsz47iYsYR2iWJ3U//cAQwvo0MWuY0A0bbY3kP4L 17 | APPVEYOR_PERSONAL_ACCESS_TOKEN: 18 | secure: +823CdXZbrvfhBVAbcsRT/CGVmREYmkH5g5TC8+1HWjGt0SHZtgS4AWVBbeyfzLl 19 | APPVEYOR_GITHUB_USERNAME: 'EsOsO' 20 | APPVEYOR_GITHUB_EMAIL: 21 | secure: qOw7foLVABiwY5Y4Th58VNI8sbyJpNwvkFSI9DFxqII= 22 | 23 | before_build: 24 | - gitversion /l console /output buildserver 25 | 26 | build_script: 27 | - ps: . .\build.ps1 28 | 29 | test: false 30 | 31 | deploy: 32 | tag: $(ReleaseVersion) 33 | description: "# What's new in $(ReleaseVersion) 34 | $(ReleaseDescription)" 35 | force_update: true 36 | draft: false 37 | prerelease: false 38 | provider: GitHub 39 | auth_token: 40 | secure: +823CdXZbrvfhBVAbcsRT/CGVmREYmkH5g5TC8+1HWjGt0SHZtgS4AWVBbeyfzLl 41 | on: 42 | branch: master 43 | -------------------------------------------------------------------------------- /build.ps1: -------------------------------------------------------------------------------- 1 | param ( 2 | [string[]] $Task = 'Default' 3 | ) 4 | 5 | # Grab nuget bits, install modules, set build variables, start build. 6 | Get-PackageProvider -Name NuGet -ForceBootstrap | Out-Null 7 | 8 | Install-Module -Name BuildHelpers -RequiredVersion '2.0.11' -Scope CurrentUser 9 | Install-Module -Name Pester -SkipPublisherCheck -Scope CurrentUser -Force -RequiredVersion '4.10.1' 10 | Install-Module Psake, PSDeploy, platyPS, PSScriptAnalyzer -Scope CurrentUser -Force 11 | 12 | Import-Module Psake, BuildHelpers, platyPS, PSScriptAnalyzer 13 | 14 | Set-BuildEnvironment -GitPath 'git.exe' 15 | Get-Module 16 | 17 | Invoke-psake -buildFile .\build.psake.ps1 -taskList $Task -nologo 18 | 19 | exit ([int] (-not $psake.build_success)) -------------------------------------------------------------------------------- /build.psake.ps1: -------------------------------------------------------------------------------- 1 | function Update-AdditionalReleaseArtifact { 2 | param( 3 | [string] $Version, 4 | [string] $CommitDate 5 | ) 6 | 7 | $PSVersion = $Version -replace '^(\d+\.\d+\.\d+).*$', '$1' 8 | Write-Host ('Updating Module Manifest version number to: {0}' -f $PSVersion) 9 | Update-Metadata -Path $env:BHPSModuleManifest -PropertyName ModuleVersion -Value $PSVersion 10 | 11 | Write-Host 'Getting release notes' 12 | $ReleaseDescription = (gc $ReleaseFile) -join "`r`n" 13 | Clear-Content -Path $ReleaseFile 14 | 15 | if ($env:APPVEYOR) { 16 | Set-AppveyorBuildVariable -Name ReleaseDescription -Value $ReleaseDescription 17 | } 18 | 19 | $Changelog = (gc $ChangelogFile | select -Skip 2) -join "`r`n" 20 | 21 | "# CHANGELOG`r`n" | Out-File $ChangelogTemp -Encoding ascii 22 | ("## {0} ({1})`r`n" -f $Version, $CommitDate) | Out-File $ChangelogTemp -Append -Encoding ascii 23 | ("{0}`r`n" -f $ReleaseDescription) | Out-File $ChangelogTemp -Append -Encoding ascii 24 | ("{0}`r`n" -f $Changelog) | Out-File $ChangelogTemp -Append -Encoding ascii 25 | 26 | Copy-Item $ChangelogTemp $ChangelogFile -Force 27 | } 28 | 29 | Properties { 30 | $BranchName = $env:GitVersion_BranchName 31 | $SemVer = $env:GitVersion_SemVer 32 | $StableVersion = $env:GitVersion_MajorMinorPatch 33 | 34 | $TestsFolder = '.\Tests' 35 | $TestsFile = Join-Path $env:BHBuildOutput ('tests-{0}-{1}.xml' -f $env:GitVersion_ShortSha, $SemVer) 36 | 37 | $Artifact = '{0}-{1}.zip' -f $env:BHProjectName.ToLower(), $SemVer 38 | $BuildBaseModule = Join-Path $env:BHBuildOutput $env:BHProjectName 39 | $BuildVersionedModule = Join-Path $BuildBaseModule $StableVersion 40 | $ArtifactPath = Join-Path $env:BHBuildOutput $Artifact 41 | 42 | $ReleaseFile = Join-Path $env:BHProjectPath 'docs\RELEASE.md' 43 | $ChangelogFile = Join-Path $env:BHProjectPath 'docs\CHANGELOG.md' 44 | $ChangelogTemp = Join-Path $env:BHBuildOutput 'CHANGELOG.md.tmp' 45 | 46 | Import-Module $env:BHPSModuleManifest -Global 47 | $ExportedFunctions = Get-Command -Module $env:BHProjectName | select -ExpandProperty Name 48 | Remove-Module $env:BHProjectName -Force 49 | } 50 | 51 | FormatTaskName (('-' * 25) + ('[ {0,-28} ]') + ('-' * 25)) 52 | 53 | Task Default -Depends Tests, Build, PublishModule 54 | 55 | Task Init { 56 | Set-Location $env:BHProjectPath 57 | 58 | if (Test-Path $env:BHBuildOutput) { 59 | Remove-Item $env:BHBuildOutput -Force -Recurse 60 | } 61 | 62 | New-Item -Path $env:BHBuildOutput -ItemType Directory | Out-Null 63 | 64 | Write-Host ('Working folder: {0}' -f $PWD) 65 | Write-Host ('Build output: {0}' -f $env:BHBuildOutput) 66 | Write-Host ('Git Version: {0}' -f $SemVer) 67 | Write-Host ('Git Version (Stable): {0}' -f $StableVersion) 68 | Write-Host ('Git Branch: {0}' -f $BranchName) 69 | 70 | if ($env:APPVEYOR) { 71 | Set-AppveyorBuildVariable -Name 'ReleaseVersion' -Value $SemVer 72 | } 73 | 74 | $PendingChanges = git status --porcelain 75 | if ($null -ne $PendingChanges) { 76 | throw 'You have pending changes, aborting release' 77 | } 78 | } 79 | 80 | Task CodeAnalisys -Depends Init { 81 | Write-Host 'ScriptAnalyzer: Running' 82 | Invoke-ScriptAnalyzer -Path $env:BHModulePath -Recurse -Severity Warning 83 | } 84 | 85 | Task Tests -Depends CodeAnalisys { 86 | $TestResults = Invoke-Pester -Path $TestsFolder -PassThru -OutputFormat NUnitXml -OutputFile $TestsFile 87 | 88 | switch ($env:BHBuildSystem) { 89 | 'AppVeyor' { 90 | Get-ChildItem -Path $env:BHBuildOutput -Filter 'tests-*.xml' -File | ForEach-Object { 91 | (New-Object 'System.Net.WebClient').UploadFile("https://ci.appveyor.com/api/testresults/nunit/$($env:APPVEYOR_JOB_ID)", "$($_.FullName)") 92 | } 93 | } 94 | Default { 95 | Write-Warning "Publish test result not implemented for build system '$($ENV:BHBuildSystem)'" 96 | } 97 | } 98 | 99 | if ($TestResults.FailedCount -gt 0) { 100 | Write-Error "Build failed. [$($TestResults.FailedCount) Errors]" 101 | } 102 | } 103 | 104 | Task BuildDocs -Depends Tests -precondition {$BranchName -eq 'master'} { 105 | Import-Module $env:BHPSModuleManifest -Global 106 | 107 | Write-Host 'BuildDocs: Generating Help for exported functions' 108 | New-MarkdownHelp -Module $env:BHProjectName -OutputFolder .\docs\functions -Force 109 | 110 | Copy-Item -Path .\header-mkdocs.yml -Destination mkdocs.yml -Force 111 | $ExportedFunctions | %{ 112 | (" - {0}: {0}.md" -f $_) | Out-File .\mkdocs.yml -Append -Encoding ascii 113 | } 114 | 115 | Remove-Module $env:BHProjectName -Force 116 | } 117 | 118 | Task IncrementVersion -Depends BuildDocs -precondition {$BranchName -eq 'master'} { 119 | Update-AdditionalReleaseArtifact -Version $SemVer -CommitDate $env:GitVersion_CommitDate 120 | Exec {git config --global credential.helper store} 121 | 122 | Add-Content "$HOME\.git-credentials" "https://$($env:APPVEYOR_PERSONAL_ACCESS_TOKEN):x-oauth-basic@github.com`n" 123 | 124 | Exec {git config --global user.email "$env:APPVEYOR_GITHUB_EMAIL"} 125 | Exec {git config --global user.name "$env:APPVEYOR_GITHUB_USERNAME"} 126 | 127 | Write-Host 'Git: Committing updated docs' 128 | Exec {git add --all} 129 | Exec {git commit -am "Updated docs [skip ci]" --allow-empty} 130 | 131 | Write-Host 'Git: Tagging branch' 132 | Exec {git tag $SemVer} 133 | 134 | if ($LASTEXITCODE -ne 0) { 135 | Exec {git reset --hard HEAD^} 136 | throw 'No changes detected since last release' 137 | } 138 | 139 | Write-Host 'Git: Pushing tags to origin' 140 | Exec {git push -q origin $BranchName --tags} 141 | } 142 | 143 | Task Build -Depends IncrementVersion -precondition {$BranchName -eq 'master'} { 144 | if (-not (Test-Path $BuildBaseModule)) {New-Item -Path $BuildBaseModule -ItemType Directory | Out-Null} 145 | if (-not (Test-Path $BuildVersionedModule)) {New-Item -Path $BuildVersionedModule -ItemType Directory | Out-Null} 146 | 147 | Write-Host "Build: Copying module to $ArtifactFolder" 148 | Copy-Item -Path $env:BHModulePath\* -Destination $BuildVersionedModule -Recurse 149 | 150 | # Write-Host "Build: Generating catalog file" 151 | # $CatalogFilePath = '{0}\{1}.cat' -f $BuildVersionedModule, $env:BHProjectName 152 | # New-FileCatalog -CatalogVersion 2 -CatalogFilePath $CatalogFilePath -Path $env:BHModulePath 153 | 154 | Write-Host "Build: Compressing release to $ArtifactPath" 155 | Compress-Archive -Path $BuildBaseModule -DestinationPath $ArtifactPath 156 | 157 | Write-Host "Build: Pushing release to Appveyor" 158 | Push-AppveyorArtifact -Path $ArtifactPath 159 | } 160 | 161 | Task PublishModule -Depends Build -precondition {$BranchName -eq 'master'} { 162 | Write-Host "PublishModule: Publishing module to powershellgallery" 163 | Publish-Module -Path $BuildVersionedModule -NuGetApiKey $env:APPVEYOR_NUGET_API_KEY 164 | } 165 | -------------------------------------------------------------------------------- /docs/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # CHANGELOG 2 | 3 | ## 4.8.5 (2022-03-23) 4 | 5 | 6 | 7 | ## 4.8.3 (2021-10-06) 8 | 9 | 10 | 11 | ## 4.8.2 (2021-03-15) 12 | 13 | 14 | 15 | ## 4.8.1 (2021-02-18) 16 | 17 | 18 | 19 | ## 4.8.0 (2021-02-11) 20 | 21 | 22 | 23 | ## 4.7.1 (2021-02-11) 24 | 25 | 26 | - [ADD] Teams target now support additional body types for adaptive cards (@jangins101) 27 | - [FIX] Refactored Teams target (@jangins101) 28 | - [FIX] Removed formatting timestamp on log event creation, this caused lost of milliseconds later on 29 | 30 | ## 4.5.0 (2020-10-22) 31 | 32 | 33 | - [ADD] Teams target now support additional body types for adaptive cards (@jangins101) 34 | - [FIX] Refactored Teams target (@jangins101) 35 | - [FIX] Removed formatting timestamp on log event creation, this caused lost of milliseconds later on 36 | 37 | ## 4.4.0 (2020-06-17) 38 | 39 | - [FIX] NotifyBeginApplication/NotifyEndApplication calls not needed (#99) 40 | - [FIX] Fix startup race condition (#100) (@Tadas) 41 | - [FIX] Fixed an issue in AzureLogAnalytics target (#102) (@Glober777) 42 | - [FIX] Resolve relative Path in File target (#103) (@Tadas) 43 | - [FIX] Target name is case insensitive (#106) (@Tadas) 44 | 45 | ## 4.3.2 (2020-05-28) 46 | 47 | - [FIX] SEQ: fix url when ApiKey is used (#96) (@gahujipo) 48 | 49 | ## 4.3.1 (2020-05-28) 50 | 51 | - [NEW] added target for Azure Log Analytics Workspace (thx to @manualbashing) 52 | - [NEW] added target for Webex Teams (thx to @itshorty) 53 | - [FIX] fixed module autoload (thx to @Tadas) 54 | - [FIX] module don't hang shell exit (thx to @Tadas) #82 55 | 56 | ## 4.2.13 (2020-02-25) 57 | 58 | ## 4.2.12 (2019-11-08) 59 | 60 | ## 4.2.11 (2019-09-23) 61 | 62 | - [FIX] Closed issue #66 where messages are lost on Powershell ISE 63 | - [MOD] Decreased `Wait-Logging` timeout from 5 minutes to 30 seconds 64 | 65 | ## 4.2.7 (2019-09-19) 66 | 67 | ## 4.2.6 (2019-09-13) 68 | 69 | In this release we worked out an issue about setting default 70 | level or formatting was not honored by the already configured 71 | targets (#67) and one about level ignored when dispatching messages 72 | to targets (#68) 73 | 74 | Thanks to: @ZamElek 75 | 76 | ## 4.2.3 (2019-08-27) 77 | 78 | ## 4.2.2 (2019-08-05) 79 | 80 | In this minor release we fixed an annoying issue about how the module loads the available targets. 81 | Now the loading routine is run inside the runspace to isolate the scope where the targets scriptblock is created. 82 | 83 | - [BUG] Major code update to address issue #63 84 | - [FIX] `Set-LoggingDefaultLevel` sets default level on cofigured targets too (#61, #58) 85 | - [MOD] Removed validation on parameter Arguments 86 | 87 | ## 4.1.1 (2019-05-20) 88 | 89 | - [NEW] Added timestamputc to log message properties #48 90 | - [NEW] Added Icons configuration to Slack target to map Log Levels to emoji #53 91 | - [FIX] Removed self loading in runspace 92 | - [FIX] Moved Use-LogMessage to private functions 93 | - [FIX] Added timeout to Wait-Logging to avoid hangs 94 | 95 | ## 4.0.3 (2019-04-15) 96 | 97 | - [FIX] removed catalog generation until I get more grasp on the process 98 | 99 | ## 3.0.0 (2019-04-15) 100 | 101 | This major release shouldn't break anything. 102 | It should improve logging performance to a new level thanks to the amazing work of @tosoikea. 103 | 104 | - [NEW] Advanced Logging Manager (thx to @tosoikea) 105 | - [NEW] Module catalog generation on build 106 | - [FIX] Filename token (thx to @lookcloser) 107 | - [MOD] Code cleanup 108 | 109 | ## 2.10.0 (2019-04-04) 110 | 111 | - [NEW] Added support for target default config 112 | - [NEW] Added support for target initialization scriptblock 113 | - [NEW] Added DynamicParam generation function 114 | - [MOD] Synchronized variables are now Constant instead of ReadOnly (thx to @tosoikea) 115 | 116 | ## 2.9.1 (2019-03-15) 117 | 118 | - [NEW] Added Windows EventLog target (thx to @tadas) 119 | - [FIX] Fixed Write-Log -Arguments detection 120 | - [ADD] powershellgallery publishing on build 121 | 122 | ## 2.6.0-ci.10 (2018-10-24) 123 | 124 | - [FIX] copyright string in mkdocs.yml 125 | - [FIX] Build version for CD 126 | 127 | ## 2.4.13 (22/10/2018) 128 | 129 | - [ADD] Caller function in message template 130 | 131 | ## 2.4.12 (21/09/2018) 132 | 133 | - [ADD] Seq target (thx @TheSemicolon) 134 | - [ADD] $ExceptionInfo paramter to Write-Log to add Error tracing 135 | 136 | ## 2.4.11 (17 August, 2018) 137 | 138 | - Fixed custom targets for locations outside module folder (#20 thx to @jeremymcgee73) 139 | 140 | ## 2.4.10 (14 May, 2018) 141 | 142 | - Fixed ElasticSearch target 143 | - Added some more documentation 144 | - Minor tweaking 145 | 146 | ## 2.4.9 (10 April, 2018) 147 | 148 | - Implement OverrideColorMapping for Console target 149 | - Implement format [DateTimeFormatInfo] usage and tests 150 | 151 | ## 2.4.8 (Febraury 27, 2018) 152 | 153 | - Fixed email configuration address parsing 154 | 155 | ## 2.4.7 (November 6, 2017) 156 | 157 | - Fixed slack logging target 158 | 159 | ## 2.4.6 (September 12, 2017) 160 | 161 | - Set runspace ApartmentState to MTA 162 | - Set min runspaces equals to 1 163 | - Set max runspaces equals to NUMBER_OF_PROCESSORS + 1 164 | 165 | ## 2.4.5 (April 19, 2017) 166 | 167 | - Fixed timestamp based on system locale 168 | 169 | ## 2.4.4 (March 13, 2017) 170 | 171 | - Fixed module autoloading timing 172 | 173 | ## 2.4.3 (January 10, 2017) 174 | 175 | - Fixed build script to release on powershelgallery only on master branch 176 | 177 | ## 2.4.2 (January 10, 2017) 178 | 179 | - Fixed minor issues in internal functions 180 | - Added new Pester tests 181 | 182 | ## 2.4.1 (December 29, 2016) 183 | 184 | - Fixed deployment issues 185 | - Moved to AppVeyor CI 186 | 187 | ## 2.4.0 (December 28, 2016) 188 | 189 | - Moved to psake build tool 190 | - Moved to platyps doc generation tool 191 | - Major folder structure change 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | -------------------------------------------------------------------------------- /docs/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies within all project spaces, and it also applies when 49 | an individual is representing the project or its community in public spaces. 50 | Examples of representing a project or community include using an official 51 | project e-mail address, posting via an official social media account, or acting 52 | as an appointed representative at an online or offline event. Representation of 53 | a project may be further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at massimo.bonvicini@gmail.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | For answers to common questions about this code of conduct, see 76 | https://www.contributor-covenant.org/faq 77 | -------------------------------------------------------------------------------- /docs/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to contribute 2 | 3 | Contributions are welcome! 4 | 5 | ## Submitting changes 6 | 7 | Please send a [GitHub Pull Request](https://github.com/EsOsO/Logging/pull/new/dev) with a clear list of what you've done (read more about [pull requests](http://help.github.com/pull-requests/)). When you send a pull request, I will love you forever if you include Pester tests. Please follow my coding conventions (below) and make sure all of your commits are atomic (one feature per commit). 8 | 9 | Always write a clear log message for your commits. One-line messages are fine for small changes, but bigger changes should look like this: 10 | 11 | $ git commit -m "A brief summary of the commit 12 | > 13 | > A paragraph describing what changed and its impact." 14 | 15 | ## Coding conventions 16 | 17 | Start reading the code and you'll get the hang of it. I tried to optimize for readability: 18 | 19 | * I indent using four spaces (soft tabs) 20 | * Adhere to [style guide](https://github.com/PoshCode/PowerShellPracticeAndStyle) 21 | * Update [README](README.md) documentation as well to reflect contribution 22 | * This is open source software. Consider the people who will read your code, and make it look nice for them. It's sort of like driving a car: Perhaps you love doing donuts when you're alone, but with passengers the goal is to make the ride as smooth as possible. 23 | 24 | Thanks, Massimo Bonvicini 25 | -------------------------------------------------------------------------------- /docs/LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Massimo Bonvicini 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /docs/RELEASE.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RootITUp/Logging/7fb9a9cdb6cc2d7c57ce5b5d40c1ab5112ea79ae/docs/RELEASE.md -------------------------------------------------------------------------------- /docs/SUPPORT.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RootITUp/Logging/7fb9a9cdb6cc2d7c57ce5b5d40c1ab5112ea79ae/docs/SUPPORT.md -------------------------------------------------------------------------------- /docs/Usage.md: -------------------------------------------------------------------------------- 1 | ## Configuration 2 | 3 | The following section describe how to configure the Logging module. 4 | 5 | - Level 6 | - Format 7 | - Targets 8 | - CustomTargets 9 | 10 | ### Level 11 | 12 | The _Level_ property defines the default logging level. 13 | Valid values are: 14 | 15 | ```powershell 16 | * NOTSET ( 0) 17 | * DEBUG (10) 18 | * INFO (20) 19 | * WARNING (30) 20 | * ERROR (40) 21 | ``` 22 | 23 | For example: 24 | 25 | ```powershell 26 | > Get-LoggingDefaultLevel # Get the default value 27 | NOTSET # NOTSET level 28 | > Set-LoggingDefaultLevel -Level 'ERROR' # Set default level to ERROR 29 | > Get-LoggingDefaultLevel # Get the current global level 30 | ERROR 31 | ``` 32 | 33 | ### Format 34 | 35 | The _Format_ property defines how the message is rendered. 36 | 37 | The default value is: `[%{timestamp}] [%{level:-7}] %{message}` 38 | 39 | The Log object has a number of attributes that are replaced in the format string to produce the message: 40 | 41 | | Format | Description | 42 | | ----------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 43 | | `%{timestamp}` | Time when the log message was created. Defaults to `%Y-%m-%d %T%Z` (_2016-04-20 14:22:45+02_). Take a look at this [Technet article](https://technet.microsoft.com/en-us/library/hh849887.aspx#sectionSection7) about the UFormat parameter, and this [Technet article]() for available `[DateTimeFormatInfo]` | 44 | | `%{timestamputc}` | UTC Time when the log message was created. Defaults to `%Y-%m-%d %T%Z` (_2016-04-20 12:22:45+02_). Take a look at this [Technet article](https://technet.microsoft.com/en-us/library/hh849887.aspx#sectionSection7) about the UFormat parameter, and this [Technet article]() for available `[DateTimeFormatInfo]` | 45 | | `%{level}` | Text logging level for the message (_DEBUG_, _INFO_, _WARNING_, _ERROR_) | 46 | | `%{levelno}` | Number logging level for the message (_10_, _20_, _30_, _40_) | 47 | | `%{lineno}` | The line number on wich the write occured | 48 | | `%{pathname}` | The path of the caller | 49 | | `%{filename}` | The file name part of the caller | 50 | | `%{caller}` | The caller function name | 51 | | `%{message}` | The logged message | 52 | | `%{body}` | The logged body (json format not pretty printed) | 53 | | `%{execinfo}` | The ErrorRecord catched in a try/catch statement | 54 | | `%{pid}` | The process id of the currently running powershellprocess ($PID) | 55 | 56 | After the placeholder name you can pass a padding or a date format string separated by a colon (`:`): 57 | 58 | #### Padding 59 | 60 | If the padding value is negative, the field will be left aligned and padded with spaces on the right: 61 | 62 | ```powershell 63 | > Set-LoggingDefaultFormat -Format '[%{level:-7}]' 64 | [DEBUG ] 65 | [INFO ] 66 | [WARNING] 67 | [ERROR ] 68 | ``` 69 | 70 | If the padding value is positive, the field will be right aligned and padded with spaces on the left: 71 | 72 | ```powershell 73 | > Set-LoggingDefaultFormat -Format '[%{level:7}]' 74 | [ DEBUG] 75 | [ INFO] 76 | [WARNING] 77 | [ ERROR] 78 | ``` 79 | 80 | #### Date format string 81 | 82 | The date format string starts with a plus sign (`+`) followed by **UFormat** OR **Format** (`[DateTimeFormatInfo]`) parameters. See [here](https://technet.microsoft.com/en-us/library/hh849887.aspx#sectionSection7) for available **UFormat**s, and [here]() for available **Format**s. 83 | 84 | ```powershell 85 | > Set-LoggingDefaultFormat -Format '%{timestamp}' 86 | 2016-04-20 13:31:12+02 87 | 88 | > Set-LoggingDefaultFormat -Format '%{timestamp:+%A, %B %d, %Y}' 89 | Wednesday, April 20, 2016 90 | 91 | > Set-LoggingDefaultFormat -Format '[%{timestamp:+%T:12}]' # You could also use padding and date format string at the same time 92 | [ 13:31:12] 93 | 94 | > Set-LoggingDefaultFormat -Format '[%{timestamp:+yyyy/MM/dd HH:mm:ss.fff}]' 95 | [2016/04/20 13:31:12.431] 96 | ``` 97 | 98 | #### Caller 99 | 100 | By default the caller cmdlet is assumed to be the parent function in the executing stack, i.e., the function directly calling the `Write-Log` cmdlet. However, there are instances where a wrapper cmdlet is used on top of `Write-Log` to trigger the logging, thus invalidating the default assumption for the caller. 101 | 102 | In these scenarios, it is possible to set the caller scope using `Set-LoggingCallerScope`, which is shown in the example below along with the usage of a wrapper logging cmdlet. 103 | 104 | ```powershell 105 | # Write-CustomLog is the wrapper logging cmdlet 106 | # If the default caller scope is used, it would print 'Write-CustomLog' everytime 107 | # filename has value only if the code below is executed in a script 108 | 109 | Add-LoggingTarget -Name Console -Configuration @{Level = 'DEBUG'; Format = '[%{filename}] [%{caller}] %{message}'} 110 | Set-LoggingCallerScope 2 111 | 112 | function Write-CustomLog { 113 | [CmdletBinding()] 114 | param( 115 | $Level, 116 | $Message 117 | ) 118 | 119 | Write-Log -Level $Level -Message $Message 120 | } 121 | 122 | function Invoke-CallerFunctionWithCustomLog { 123 | 1..5 | ForEach-Object { 124 | # In this example, during execution of Write-Log the numeric scope represents the following: 125 | # 0 - Write-Log scope 126 | # 1 - Write-CustomLog scope (which would be default value) 127 | # 2 - Invoke-CallerFunctionWithCustomLog 128 | Write-CustomLog -Level (Get-Random 'DEBUG', 'INFO', 'WARNING', 'ERROR') -Message 'Hello, World! (With caller scope at level 2)' 129 | } 130 | } 131 | 132 | Invoke-CallerFunctionWithCustomLog 133 | ``` 134 | 135 | **Note**: A format string starting with a percent symbol (%) will use the `UFormat` parameter of `Get-Date` 136 | 137 | ### Targets 138 | 139 | The _Targets_ property stores the used logging targets, it's where you define where to route your messages. 140 | 141 | Keys of the hashtable depends on the target you are configuring. The module ships with 7 targets but you can write your own for specific usage. 142 | 143 | - [Configuration](#configuration) 144 | - [Level](#level) 145 | - [Format](#format) 146 | - [Padding](#padding) 147 | - [Date format string](#date-format-string) 148 | - [Caller](#caller) 149 | - [Targets](#targets) 150 | - [Console](#console) 151 | - [Colors](#colors) 152 | - [File](#file) 153 | - [ElasticSearch](#elasticsearch) 154 | - [NoFlatten](#noflatten) 155 | - [Flatten](#flatten) 156 | - [Slack](#slack) 157 | - [Email](#email) 158 | - [Seq](#seq) 159 | - [WinEventLog](#wineventlog) 160 | - [Teams](#teams) 161 | - [CustomTargets](#customtargets) 162 | - [AzureLogAnalytics](#azureloganalytics) 163 | - [Contributing](#contributing) 164 | - [Notes](#notes) 165 | 166 | #### Console 167 | 168 | From version 2.3.3 it supports acquiring lock for issues with git prompt that sometimes gets splitted during output. 169 | The mutex name to acquire is `ConsoleMtx` 170 | 171 | ```powershell 172 | > Add-LoggingTarget -Name Console -Configuration @{ 173 | Level = # Sets the logging level for this target 174 | Format = # Sets the logging format for this target 175 | PrintException = $true # Prints stacktrace 176 | ColorMapping = # Overrides the level:color mappings with a [hashtable]. 177 | # Only need to specify the levels you wish to override 178 | } 179 | ``` 180 | 181 | ##### Colors 182 | 183 | Default Console Colors 184 | 185 | ```powershell 186 | $ColorMapping = @{ 187 | 'DEBUG' = 'Blue' 188 | 'INFO' = 'Green' 189 | 'WARNING' = 'Yellow' 190 | 'ERROR' = 'Red' 191 | } 192 | ``` 193 | 194 | Each color will be verified against `[System.ConsoleColor]`. If it is invalid, an error will appear on the screen along with the orignal message. 195 | 196 | ```powershell 197 | Add-LoggingTarget -Name Console -Configuration @{ 198 | ColorMapping = @{ 199 | DEBUG = 'Gray' 200 | INFO = 'White' 201 | } 202 | } 203 | ``` 204 | 205 | #### File 206 | 207 | ```powershell 208 | > Add-LoggingTarget -Name File -Configuration @{ 209 | Path = # Sets the file destination (eg. 'C:\Temp\%{+%Y%m%d}.log') 210 | # It supports templating like $Logging.Format 211 | PrintBody = $false # Prints body message too 212 | PrintException = $false # Prints stacktrace 213 | Append = $true # Append to log file 214 | Encoding = 'ascii' # Sets the log file encoding 215 | Level = # Sets the logging level for this target 216 | Format = # Sets the logging format for this target 217 | ############Rotating################### 218 | RotateAfterAmount = # Sets the amount of files after which rotation is triggered 219 | RotateAmount = # Amount of files to be rotated, when RotateAfterAmount is used 220 | RotateAfterDate = # Rotate after the difference between the current datetime and the datetime of the file(s) are greater then the given timespan 221 | RotateAfterSize = # Rotate after the file(s) are greater than the given size in BYTES 222 | CompressionPath = # Path of archive (*.zip) to create for the rotated files 223 | } 224 | 225 | Write-Log -Level 'WARNING' -Message 'Hello, Powershell!' 226 | Write-Log -Level 'WARNING' -Message 'Hello, {0}!' -Arguments 'Powershell' 227 | Write-Log -Level 'WARNING' -Message 'Hello, {0}!' -Arguments 'Powershell' -Body @{source = 'Logging'} 228 | ``` 229 | 230 | ##### Rotation 231 | This module provides the functionality for the file target to rotate log files. 232 | To make full use of this functionality, variable data used inside the log path should be encoded, using the previously described format system. 233 | 234 | When the file target is initialized, **all files** are retrieved, which **expand** the given log path. 235 | All internally known **placeholders** are therefore substituted with **wildcard** characters. 236 | Based upon this list of files a file is rotated, if 237 | - the difference between it's creation and the current data is greater then the specified **RotateAfterDate** 238 | - it's size in bytes exceeds **RotateAfterSize** 239 | - more than **RotateAfterAmount** files are present and this file belongs to the oldest max(|Files| - RotateAfterAmount, RotateAmount) files. 240 | 241 | The default behavior is to remove all rotated log files. It is however possible, to use the **CompressionPath** to define an archive for the rotated log files. **This requires >= NET4.5** The following placeholders are supported 242 | - `%{timestamp}` 243 | - `%{timestamputc}` 244 | If an archive should already be present, the data is **merged**. 245 | 246 | #### ElasticSearch 247 | 248 | ```powershell 249 | > Add-LoggingTarget -Name ElasticSearch -Configuration @{ 250 | ServerName = # Sets the ES server name (eg. 'localhost') 251 | ServerPort = # Sets the ES server port (eg. 9200) 252 | Index = # Sets the ES index name to log to (eg. 'logs-%{+%Y.%m.%d}') 253 | # It supports templating like $Logging.Format 254 | Type = # Sets the ES type for the message (eg. 'log') 255 | Level = # Sets the logging format for this target 256 | Flatten = $false # Transforms the log hashtable in a 1-D hashtable 257 | Https = $false # Uses HTTPS instead of HTTP in elasticsearch URL if $true 258 | Authorization = # Converts creds to base64 and adds it to headers. (eg. 'username:password') 259 | } 260 | 261 | $Body = @{source = 'Logging'; host='bastion.constoso.com'; _metadata = @{ip = '10.10.10.10'; server_farm = 'WestEurope'}} 262 | 263 | Write-Log -Level 'WARNING' -Message 'Hello, Powershell!' -Body $Body 264 | ``` 265 | 266 | ##### NoFlatten 267 | 268 | ```json 269 | { 270 | "_index": "powershell-2018-05-10", 271 | "_type": "doc", 272 | "_id": "6BfJXWMB8moSvzgSbZgo", 273 | "_score": 1, 274 | "_source": { 275 | "body": { 276 | "host": "bastion.constoso.com", 277 | "_metadata": { 278 | "server_farm": "WestEurope", 279 | "ip": "10.10.10.10" 280 | }, 281 | "source": "Logging" 282 | }, 283 | "levelno": 30, 284 | "timestamp": "2018-05-14T10:34:31+02", 285 | "level": "WARNING", 286 | "message": "Hello, Powershell, No Flatten" 287 | } 288 | } 289 | ``` 290 | 291 | ##### Flatten 292 | 293 | ```json 294 | { 295 | "_index": "powershell-2018-05-10", 296 | "_type": "doc", 297 | "_id": "6RfJXWMB8moSvzgSeJj_", 298 | "_score": 1, 299 | "_source": { 300 | "source": "Logging", 301 | "server_farm": "WestEurope", 302 | "ip": "10.10.10.10", 303 | "levelno": 30, 304 | "level": "WARNING", 305 | "host": "bastion.constoso.com", 306 | "message": "Hello, Powershell, Flatten", 307 | "timestamp": "2018-05-14T10:34:34+02" 308 | } 309 | } 310 | ``` 311 | 312 | #### Slack 313 | 314 | ```powershell 315 | > Add-LoggingTarget -Name Slack -Configuration @{ 316 | WebHook = # Sets the Slack Webhook URI (eg. 'https://hooks.slack.com/services/xxxx/xxxx/xxxxxxxxxx') 317 | Channel = # Overrides the default channel of the Webhook (eg. '@username' or '#other-channel') 318 | BotName = # Overrides the default name of the bot (eg. 'PoshLogging') 319 | Level = # Sets the logging format for this target 320 | Format = # Sets the logging format for this target 321 | } 322 | 323 | Write-Log -Level 'WARNING' -Message 'Hello, Powershell!' 324 | Write-Log -Level 'WARNING' -Message 'Hello, {0}!' -Arguments 'Powershell' 325 | Write-Log -Level 'WARNING' -Message 'Hello, {0}!' -Arguments 'Powershell' -Body @{source = 'Logging'} 326 | ``` 327 | 328 | #### Email 329 | 330 | ```powershell 331 | > Add-LoggingTarget -Name Email -Configuration @{ 332 | SMTPServer = # SMTP server FQDN 333 | From = # From address 334 | To = # A string of recipients delimited by comma (,) (eg. 'test@contoso.com, robin@hood.eu') 335 | Subject = '[%{level:-7}] %{message}' # Email subject. Supports formatting and expansion 336 | Attachments = # Path to the desired file to attach 337 | Credential = # If your server uses authentication 338 | Level = # Sets the logging format for this target 339 | Port = # Set the SMTP server's port 340 | UseSsl = $false # Use encrypted transport to SMTP server 341 | PrintException = $false # Print stacktrace in the body 342 | } 343 | 344 | Write-Log -Level 'WARNING' -Message 'Hello, Powershell!' 345 | Write-Log -Level 'WARNING' -Message 'Hello, {0}!' -Arguments 'Powershell' 346 | Write-Log -Level 'WARNING' -Message 'Hello, {0}!' -Arguments 'Powershell' -Body @{source = 'Logging'} 347 | ``` 348 | 349 | #### Seq 350 | 351 | ```powershell 352 | > Add-LoggingTarget -Name Seq -Configuration @{ 353 | Url = # Url to Seq instance 354 | ApiKey = # Api Key to authenticate to Seq 355 | Properties = # Hashtable of user defined properties to be added to each Seq message 356 | Level = # Sets the logging level for this target 357 | } 358 | 359 | Write-Log -Level 'WARNING' -Message 'Hello, Powershell' 360 | Write-Log -Level 'WARNING' -Message 'Hello, {0}!' -Arguments 'Powershell' 361 | Write-Log -Level 'WARNING' -Message 'Hello, {0}!' -Arguments 'Powershell' -Body @{source = 'Logging'} 362 | ``` 363 | 364 | #### WinEventLog 365 | 366 | Before you can log events you need to make sure that the LogName and Source exists. This needs to be done only once (run as an Administrator) 367 | 368 | ```powershell 369 | > New-EventLog -LogName -Source 370 | ``` 371 | 372 | You can now log to the EventLog from your script 373 | 374 | ```powershell 375 | > Add-LoggingTarget -Name WinEventLog -Configuration @{ 376 | LogName = # Name of the log to which the events are written (eg. 'Application', 'System' and etc.) 377 | Source = # Event source, which is typically the name of the application that is writing the event to the log 378 | 379 | Write-Log -Level 'WARNING' -Message 'Hello, Powershell!' 380 | Write-Log -Level 'WARNING' -Message 'Hello, {0}!' -Arguments 'Powershell' 381 | Write-Log -Level 'WARNING' -Message 'Hello, {0}!' -Arguments 'Powershell' -Body @{ EventID = 123 } 382 | } 383 | ``` 384 | 385 | #### Teams 386 | 387 | ```powershell 388 | > Add-LoggingTarget -Name Teams -Configuration @{ 389 | WebHook = # Sets the Teams Connector URI (eg. 'https://outlook.office.com/webhook/...') 390 | Details = $true # Prints Log message details like PID, caller etc. 391 | Level = # Sets the logging format for this target 392 | Colors = @{ # Maps log levels to badge colors 393 | 'DEBUG' = 'blue' 394 | 'INFO' = 'brightgreen' 395 | 'WARNING' = 'orange' 396 | 'ERROR' = 'red' 397 | } 398 | } 399 | 400 | Write-Log -Level 'WARNING' -Message 'Hello, Powershell!' 401 | Write-Log -Level 'WARNING' -Message 'Hello, {0}!' -Arguments 'Powershell' 402 | Write-Log -Level 'WARNING' -Message 'Hello, {0}!' -Arguments 'Powershell' -Body @{source = 'Logging'} 403 | ``` 404 | 405 | ### CustomTargets 406 | 407 | It lets define a folder to load custom targets. 408 | 409 | ```powershell 410 | > Set-LoggingCustomTarget -Path 'C:\temp\' 411 | > Get-LoggingAvailableTarget 412 | Name Value 413 | ---- ----- 414 | Console {Configuration, ParamsRequired, Logger} 415 | ElasticSearch {Configuration, ParamsRequired, Logger} 416 | File {Configuration, ParamsRequired, Logger} 417 | Slack {Configuration, ParamsRequired, Logger} 418 | MyCustomTarget {Configuration, ParamsRequired, Logger} 419 | ``` 420 | 421 | #### AzureLogAnalytics 422 | 423 | Log directly to a Azure Log Analytics Workspace from your script 424 | 425 | ```powershell 426 | > Import-Module Logging 427 | Add-LoggingTarget -Name AzureLogAnalytics -Configuration @{ 428 | WorkspaceId = # Id of the Azure Log Analytics Workspace 429 | SharedKey = # Primary or Secondary Key to access the Azure Log Analytics Workspace 430 | LogType = "Logging" # Creates a custom LogType in Log Analytics Workspace 431 | } 432 | 433 | Write-Log -Level 'WARNING' -Message 'Hello, Powershell!' 434 | Write-Log -Level 'WARNING' -Message 'Hello, {0}!' -Arguments 'Powershell' 435 | Write-Log -Level 'WARNING' -Message 'Hello, Powershell!' -Body { Computer = $env:COMPUTERNAME } 436 | ``` 437 | 438 | ## Contributing 439 | 440 | Please use [issues](https://github.com/EsOsO/Logging/issues) system or GitHub pull requests to contribute to the project. 441 | 442 | For more information, see [CONTRIBUTING](CONTRIBUTING.md) 443 | 444 | ## Notes 445 | 446 | - The dispatcher thread starts the first time a `Write-Log` command is executed and keeps running in the background to dispatch new messages until the module is removed. 447 | - The runspace code is inspired by the work and research of Boe Prox (@proxb). 448 | -------------------------------------------------------------------------------- /docs/functions/Add-LoggingLevel.md: -------------------------------------------------------------------------------- 1 | --- 2 | external help file: Logging-help.xml 3 | Module Name: Logging 4 | online version: https://logging.readthedocs.io/en/latest/functions/Add-LoggingLevel.md 5 | schema: 2.0.0 6 | --- 7 | 8 | # Add-LoggingLevel 9 | 10 | ## SYNOPSIS 11 | Define a new severity level 12 | 13 | ## SYNTAX 14 | 15 | ``` 16 | Add-LoggingLevel [-Level] [-LevelName] [] 17 | ``` 18 | 19 | ## DESCRIPTION 20 | This function add a new severity level to the ones already defined 21 | 22 | ## EXAMPLES 23 | 24 | ### EXAMPLE 1 25 | ``` 26 | Add-LoggingLevel -Level 41 -LevelName CRITICAL 27 | ``` 28 | 29 | ### EXAMPLE 2 30 | ``` 31 | Add-LoggingLevel -Level 15 -LevelName VERBOSE 32 | ``` 33 | 34 | ## PARAMETERS 35 | 36 | ### -Level 37 | An integer that identify the severity of the level, higher the value higher the severity of the level 38 | By default the module defines this levels: 39 | NOTSET 0 40 | DEBUG 10 41 | INFO 20 42 | WARNING 30 43 | ERROR 40 44 | 45 | ```yaml 46 | Type: Int32 47 | Parameter Sets: (All) 48 | Aliases: 49 | 50 | Required: True 51 | Position: 1 52 | Default value: 0 53 | Accept pipeline input: False 54 | Accept wildcard characters: False 55 | ``` 56 | 57 | ### -LevelName 58 | The human redable name to assign to the level 59 | 60 | ```yaml 61 | Type: String 62 | Parameter Sets: (All) 63 | Aliases: 64 | 65 | Required: True 66 | Position: 2 67 | Default value: None 68 | Accept pipeline input: False 69 | Accept wildcard characters: False 70 | ``` 71 | 72 | ### CommonParameters 73 | This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). 74 | 75 | ## INPUTS 76 | 77 | ## OUTPUTS 78 | 79 | ## NOTES 80 | 81 | ## RELATED LINKS 82 | 83 | [https://logging.readthedocs.io/en/latest/functions/Add-LoggingLevel.md](https://logging.readthedocs.io/en/latest/functions/Add-LoggingLevel.md) 84 | 85 | [https://logging.readthedocs.io/en/latest/functions/Write-Log.md](https://logging.readthedocs.io/en/latest/functions/Write-Log.md) 86 | 87 | [https://github.com/EsOsO/Logging/blob/master/Logging/public/Add-LoggingLevel.ps1](https://github.com/EsOsO/Logging/blob/master/Logging/public/Add-LoggingLevel.ps1) 88 | 89 | -------------------------------------------------------------------------------- /docs/functions/Add-LoggingTarget.md: -------------------------------------------------------------------------------- 1 | --- 2 | external help file: Logging-help.xml 3 | Module Name: Logging 4 | online version: https://logging.readthedocs.io/en/latest/functions/Add-LoggingTarget.md 5 | schema: 2.0.0 6 | --- 7 | 8 | # Add-LoggingTarget 9 | 10 | ## SYNOPSIS 11 | Enable a logging target 12 | 13 | ## SYNTAX 14 | 15 | ``` 16 | Add-LoggingTarget [[-Configuration] ] -Name [] 17 | ``` 18 | 19 | ## DESCRIPTION 20 | This function configure and enable a logging target 21 | 22 | ## EXAMPLES 23 | 24 | ### EXAMPLE 1 25 | ``` 26 | Add-LoggingTarget -Name Console -Configuration @{Level = 'DEBUG'} 27 | ``` 28 | 29 | ### EXAMPLE 2 30 | ``` 31 | Add-LoggingTarget -Name File -Configuration @{Level = 'INFO'; Path = 'C:\Temp\script.log'} 32 | ``` 33 | 34 | ## PARAMETERS 35 | 36 | ### -Configuration 37 | An hashtable containing the configurations for the target 38 | 39 | ```yaml 40 | Type: Hashtable 41 | Parameter Sets: (All) 42 | Aliases: 43 | 44 | Required: False 45 | Position: 3 46 | Default value: @{} 47 | Accept pipeline input: False 48 | Accept wildcard characters: False 49 | ``` 50 | 51 | ### -Name 52 | {{ Fill Name Description }} 53 | 54 | ```yaml 55 | Type: String 56 | Parameter Sets: (All) 57 | Aliases: 58 | 59 | Required: True 60 | Position: Named 61 | Default value: None 62 | Accept pipeline input: False 63 | Accept wildcard characters: False 64 | ``` 65 | 66 | ### CommonParameters 67 | This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). 68 | 69 | ## INPUTS 70 | 71 | ## OUTPUTS 72 | 73 | ## NOTES 74 | 75 | ## RELATED LINKS 76 | 77 | [https://logging.readthedocs.io/en/latest/functions/Add-LoggingTarget.md](https://logging.readthedocs.io/en/latest/functions/Add-LoggingTarget.md) 78 | 79 | [https://logging.readthedocs.io/en/latest/functions/Write-Log.md](https://logging.readthedocs.io/en/latest/functions/Write-Log.md) 80 | 81 | [https://logging.readthedocs.io/en/latest/AvailableTargets.md](https://logging.readthedocs.io/en/latest/AvailableTargets.md) 82 | 83 | [https://github.com/EsOsO/Logging/blob/master/Logging/public/Add-LoggingTarget.ps1](https://github.com/EsOsO/Logging/blob/master/Logging/public/Add-LoggingTarget.ps1) 84 | 85 | -------------------------------------------------------------------------------- /docs/functions/Get-LoggingAvailableTarget.md: -------------------------------------------------------------------------------- 1 | --- 2 | external help file: Logging-help.xml 3 | Module Name: Logging 4 | online version: https://logging.readthedocs.io/en/latest/functions/Get-LoggingAvailableTarget.md 5 | schema: 2.0.0 6 | --- 7 | 8 | # Get-LoggingAvailableTarget 9 | 10 | ## SYNOPSIS 11 | Returns available logging targets 12 | 13 | ## SYNTAX 14 | 15 | ``` 16 | Get-LoggingAvailableTarget [] 17 | ``` 18 | 19 | ## DESCRIPTION 20 | This function returns available logging targtes 21 | 22 | ## EXAMPLES 23 | 24 | ### EXAMPLE 1 25 | ``` 26 | Get-LoggingAvailableTarget 27 | ``` 28 | 29 | ## PARAMETERS 30 | 31 | ### CommonParameters 32 | This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). 33 | 34 | ## INPUTS 35 | 36 | ## OUTPUTS 37 | 38 | ## NOTES 39 | 40 | ## RELATED LINKS 41 | 42 | [https://logging.readthedocs.io/en/latest/functions/Get-LoggingAvailableTarget.md](https://logging.readthedocs.io/en/latest/functions/Get-LoggingAvailableTarget.md) 43 | 44 | [https://logging.readthedocs.io/en/latest/functions/Write-Log.md](https://logging.readthedocs.io/en/latest/functions/Write-Log.md) 45 | 46 | [https://github.com/EsOsO/Logging/blob/master/Logging/public/Get-LoggingAvailableTarget.ps1](https://github.com/EsOsO/Logging/blob/master/Logging/public/Get-LoggingAvailableTarget.ps1) 47 | 48 | -------------------------------------------------------------------------------- /docs/functions/Get-LoggingCallerScope.md: -------------------------------------------------------------------------------- 1 | --- 2 | external help file: Logging-help.xml 3 | Module Name: Logging 4 | online version: https://logging.readthedocs.io/en/latest/functions/Get-LoggingCallerScope.md 5 | schema: 2.0.0 6 | --- 7 | 8 | # Get-LoggingCallerScope 9 | 10 | ## SYNOPSIS 11 | Returns the default caller scope 12 | 13 | ## SYNTAX 14 | 15 | ``` 16 | Get-LoggingCallerScope [] 17 | ``` 18 | 19 | ## DESCRIPTION 20 | This function returns an int representing the scope where the invocation scope for the caller should be obtained from 21 | 22 | ## EXAMPLES 23 | 24 | ### EXAMPLE 1 25 | ``` 26 | Get-LoggingCallerScope 27 | ``` 28 | 29 | ## PARAMETERS 30 | 31 | ### CommonParameters 32 | This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). 33 | 34 | ## INPUTS 35 | 36 | ## OUTPUTS 37 | 38 | ## NOTES 39 | 40 | ## RELATED LINKS 41 | 42 | [https://logging.readthedocs.io/en/latest/functions/Get-LoggingCallerScope.md](https://logging.readthedocs.io/en/latest/functions/Get-LoggingCallerScope.md) 43 | 44 | [https://logging.readthedocs.io/en/latest/functions/Write-Log.md](https://logging.readthedocs.io/en/latest/functions/Write-Log.md) 45 | 46 | [https://logging.readthedocs.io/en/latest/LoggingFormat.md](https://logging.readthedocs.io/en/latest/LoggingFormat.md) 47 | 48 | [https://github.com/EsOsO/Logging/blob/master/Logging/public/Get-LoggingCallerScope.ps1](https://github.com/EsOsO/Logging/blob/master/Logging/public/Get-LoggingCallerScope.ps1) 49 | 50 | -------------------------------------------------------------------------------- /docs/functions/Get-LoggingDefaultFormat.md: -------------------------------------------------------------------------------- 1 | --- 2 | external help file: Logging-help.xml 3 | Module Name: Logging 4 | online version: https://logging.readthedocs.io/en/latest/functions/Get-LoggingDefaultFormat.md 5 | schema: 2.0.0 6 | --- 7 | 8 | # Get-LoggingDefaultFormat 9 | 10 | ## SYNOPSIS 11 | Returns the default message format 12 | 13 | ## SYNTAX 14 | 15 | ``` 16 | Get-LoggingDefaultFormat [] 17 | ``` 18 | 19 | ## DESCRIPTION 20 | This function returns a string representing the default message format used by enabled targets that don't override it 21 | 22 | ## EXAMPLES 23 | 24 | ### EXAMPLE 1 25 | ``` 26 | Get-LoggingDefaultFormat 27 | ``` 28 | 29 | ## PARAMETERS 30 | 31 | ### CommonParameters 32 | This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). 33 | 34 | ## INPUTS 35 | 36 | ## OUTPUTS 37 | 38 | ## NOTES 39 | 40 | ## RELATED LINKS 41 | 42 | [https://logging.readthedocs.io/en/latest/functions/Get-LoggingDefaultFormat.md](https://logging.readthedocs.io/en/latest/functions/Get-LoggingDefaultFormat.md) 43 | 44 | [https://logging.readthedocs.io/en/latest/functions/Write-Log.md](https://logging.readthedocs.io/en/latest/functions/Write-Log.md) 45 | 46 | [https://logging.readthedocs.io/en/latest/LoggingFormat.md](https://logging.readthedocs.io/en/latest/LoggingFormat.md) 47 | 48 | [https://github.com/EsOsO/Logging/blob/master/Logging/public/Get-LoggingDefaultFormat.ps1](https://github.com/EsOsO/Logging/blob/master/Logging/public/Get-LoggingDefaultFormat.ps1) 49 | 50 | -------------------------------------------------------------------------------- /docs/functions/Get-LoggingDefaultLevel.md: -------------------------------------------------------------------------------- 1 | --- 2 | external help file: Logging-help.xml 3 | Module Name: Logging 4 | online version: https://logging.readthedocs.io/en/latest/functions/Get-LoggingDefaultLevel.md 5 | schema: 2.0.0 6 | --- 7 | 8 | # Get-LoggingDefaultLevel 9 | 10 | ## SYNOPSIS 11 | Returns the default message level 12 | 13 | ## SYNTAX 14 | 15 | ``` 16 | Get-LoggingDefaultLevel [] 17 | ``` 18 | 19 | ## DESCRIPTION 20 | This function returns a string representing the default message level used by enabled targets that don't override it 21 | 22 | ## EXAMPLES 23 | 24 | ### EXAMPLE 1 25 | ``` 26 | Get-LoggingDefaultLevel 27 | ``` 28 | 29 | ## PARAMETERS 30 | 31 | ### CommonParameters 32 | This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). 33 | 34 | ## INPUTS 35 | 36 | ## OUTPUTS 37 | 38 | ## NOTES 39 | 40 | ## RELATED LINKS 41 | 42 | [https://logging.readthedocs.io/en/latest/functions/Get-LoggingDefaultLevel.md](https://logging.readthedocs.io/en/latest/functions/Get-LoggingDefaultLevel.md) 43 | 44 | [https://logging.readthedocs.io/en/latest/functions/Write-Log.md](https://logging.readthedocs.io/en/latest/functions/Write-Log.md) 45 | 46 | [https://github.com/EsOsO/Logging/blob/master/Logging/public/Get-LoggingDefaultLevel.ps1](https://github.com/EsOsO/Logging/blob/master/Logging/public/Get-LoggingDefaultLevel.ps1) 47 | 48 | -------------------------------------------------------------------------------- /docs/functions/Get-LoggingMessageCount.md: -------------------------------------------------------------------------------- 1 | --- 2 | external help file: Logging-help.xml 3 | Module Name: Logging 4 | online version: https://logging.readthedocs.io/en/latest/functions/Get-LoggingMessageCount.md 5 | schema: 2.0.0 6 | --- 7 | 8 | # Get-LoggingMessageCount 9 | 10 | ## SYNOPSIS 11 | Returns the currently processed log message count 12 | 13 | ## SYNTAX 14 | 15 | ``` 16 | Get-LoggingMessageCount [] 17 | ``` 18 | 19 | ## DESCRIPTION 20 | When a message is processed by any log target it will be added 21 | to the count of logged messages. 22 | Get-LoggingMessageCount retrieves the sum of those messages. 23 | 24 | ## EXAMPLES 25 | 26 | ### EXAMPLE 1 27 | ``` 28 | Set-LoggingDefaultLevel -Level ERROR 29 | ``` 30 | 31 | Add-LoggingTarget -Name Console 32 | write-Log -Message "Test1" 33 | Write-Log -Message "Test2" -Level ERROR 34 | 35 | Get-LoggingMessageCount 36 | =\> 1 37 | 38 | ## PARAMETERS 39 | 40 | ### CommonParameters 41 | This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). 42 | 43 | ## INPUTS 44 | 45 | ## OUTPUTS 46 | 47 | ## NOTES 48 | 49 | ## RELATED LINKS 50 | 51 | [https://logging.readthedocs.io/en/latest/functions/Get-LoggingMessageCount.md](https://logging.readthedocs.io/en/latest/functions/Get-LoggingMessageCount.md) 52 | 53 | [https://logging.readthedocs.io/en/latest/functions/Write-Log.md](https://logging.readthedocs.io/en/latest/functions/Write-Log.md) 54 | 55 | [https://github.com/EsOsO/Logging/blob/master/Logging/public/Get-LoggingMessageCount.ps1](https://github.com/EsOsO/Logging/blob/master/Logging/public/Get-LoggingMessageCount.ps1) 56 | 57 | -------------------------------------------------------------------------------- /docs/functions/Get-LoggingTarget.md: -------------------------------------------------------------------------------- 1 | --- 2 | external help file: Logging-help.xml 3 | Module Name: Logging 4 | online version: https://logging.readthedocs.io/en/latest/functions/Get-LoggingTarget.md 5 | schema: 2.0.0 6 | --- 7 | 8 | # Get-LoggingTarget 9 | 10 | ## SYNOPSIS 11 | Returns enabled logging targets 12 | 13 | ## SYNTAX 14 | 15 | ``` 16 | Get-LoggingTarget [[-Name] ] [] 17 | ``` 18 | 19 | ## DESCRIPTION 20 | This function returns enabled logging targtes 21 | 22 | ## EXAMPLES 23 | 24 | ### EXAMPLE 1 25 | ``` 26 | Get-LoggingTarget 27 | ``` 28 | 29 | ### EXAMPLE 2 30 | ``` 31 | Get-LoggingTarget -Name Console 32 | ``` 33 | 34 | ## PARAMETERS 35 | 36 | ### -Name 37 | The Name of the target to retrieve, if not passed all configured targets will be returned 38 | 39 | ```yaml 40 | Type: String 41 | Parameter Sets: (All) 42 | Aliases: 43 | 44 | Required: False 45 | Position: 1 46 | Default value: None 47 | Accept pipeline input: False 48 | Accept wildcard characters: False 49 | ``` 50 | 51 | ### CommonParameters 52 | This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). 53 | 54 | ## INPUTS 55 | 56 | ## OUTPUTS 57 | 58 | ## NOTES 59 | 60 | ## RELATED LINKS 61 | 62 | [https://logging.readthedocs.io/en/latest/functions/Get-LoggingTarget.md](https://logging.readthedocs.io/en/latest/functions/Get-LoggingTarget.md) 63 | 64 | [https://logging.readthedocs.io/en/latest/functions/Write-Log.md](https://logging.readthedocs.io/en/latest/functions/Write-Log.md) 65 | 66 | [https://github.com/EsOsO/Logging/blob/master/Logging/public/Get-LoggingTarget.ps1](https://github.com/EsOsO/Logging/blob/master/Logging/public/Get-LoggingTarget.ps1) 67 | 68 | -------------------------------------------------------------------------------- /docs/functions/Get-LoggingTargetAvailable.md: -------------------------------------------------------------------------------- 1 | --- 2 | external help file: Logging-help.xml 3 | Module Name: Logging 4 | online version: https://logging.readthedocs.io/en/latest/functions/Get-LoggingTarget.md 5 | schema: 2.0.0 6 | --- 7 | 8 | # Get-LoggingTargetAvailable 9 | 10 | ## SYNOPSIS 11 | {{Fill in the Synopsis}} 12 | 13 | ## SYNTAX 14 | 15 | ``` 16 | Get-LoggingTargetAvailable [] 17 | ``` 18 | 19 | ## DESCRIPTION 20 | {{Fill in the Description}} 21 | 22 | ## EXAMPLES 23 | 24 | ### Example 1 25 | ```powershell 26 | PS C:\> {{ Add example code here }} 27 | ``` 28 | 29 | {{ Add example description here }} 30 | 31 | ## PARAMETERS 32 | 33 | ### CommonParameters 34 | This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. 35 | For more information, see about_CommonParameters (http://go.microsoft.com/fwlink/?LinkID=113216). 36 | 37 | ## INPUTS 38 | 39 | ### None 40 | 41 | ## OUTPUTS 42 | 43 | ### System.Object 44 | ## NOTES 45 | 46 | ## RELATED LINKS 47 | -------------------------------------------------------------------------------- /docs/functions/Set-LoggingCallerScope.md: -------------------------------------------------------------------------------- 1 | --- 2 | external help file: Logging-help.xml 3 | Module Name: Logging 4 | online version: https://logging.readthedocs.io/en/latest/functions/Set-LoggingCallerScope.md 5 | schema: 2.0.0 6 | --- 7 | 8 | # Set-LoggingCallerScope 9 | 10 | ## SYNOPSIS 11 | Sets the scope from which to get the caller scope 12 | 13 | ## SYNTAX 14 | 15 | ``` 16 | Set-LoggingCallerScope [[-CallerScope] ] [] 17 | ``` 18 | 19 | ## DESCRIPTION 20 | This function sets the scope to obtain information from the caller 21 | 22 | ## EXAMPLES 23 | 24 | ### EXAMPLE 1 25 | ``` 26 | Set-LoggingCallerScope -CallerScope 2 27 | ``` 28 | 29 | ### EXAMPLE 2 30 | ``` 31 | Set-LoggingCallerScope 32 | ``` 33 | 34 | It sets the caller scope to 1 35 | 36 | ## PARAMETERS 37 | 38 | ### -CallerScope 39 | Integer representing the scope to use to find the caller information. 40 | Defaults to 1 which represent the scope of the function where Write-Log is being called from 41 | 42 | ```yaml 43 | Type: Int32 44 | Parameter Sets: (All) 45 | Aliases: 46 | 47 | Required: False 48 | Position: 1 49 | Default value: $Defaults.CallerScope 50 | Accept pipeline input: False 51 | Accept wildcard characters: False 52 | ``` 53 | 54 | ### CommonParameters 55 | This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). 56 | 57 | ## INPUTS 58 | 59 | ## OUTPUTS 60 | 61 | ## NOTES 62 | 63 | ## RELATED LINKS 64 | 65 | [https://logging.readthedocs.io/en/latest/functions/Set-LoggingCallerScope.md](https://logging.readthedocs.io/en/latest/functions/Set-LoggingCallerScope.md) 66 | 67 | [https://logging.readthedocs.io/en/latest/functions/Write-Log.md](https://logging.readthedocs.io/en/latest/functions/Write-Log.md) 68 | 69 | [https://github.com/EsOsO/Logging/blob/master/Logging/public/Set-LoggingCallerScope.ps1](https://github.com/EsOsO/Logging/blob/master/Logging/public/Set-LoggingCallerScope.ps1) 70 | 71 | -------------------------------------------------------------------------------- /docs/functions/Set-LoggingCustomTarget.md: -------------------------------------------------------------------------------- 1 | --- 2 | external help file: Logging-help.xml 3 | Module Name: Logging 4 | online version: https://logging.readthedocs.io/en/latest/functions/Set-LoggingCustomTarget.md 5 | schema: 2.0.0 6 | --- 7 | 8 | # Set-LoggingCustomTarget 9 | 10 | ## SYNOPSIS 11 | Sets a folder as custom target repository 12 | 13 | ## SYNTAX 14 | 15 | ``` 16 | Set-LoggingCustomTarget [-Path] [] 17 | ``` 18 | 19 | ## DESCRIPTION 20 | This function sets a folder as a custom target repository. 21 | Every *.ps1 file will be loaded as a custom target and available to be enabled for logging to. 22 | 23 | ## EXAMPLES 24 | 25 | ### EXAMPLE 1 26 | ``` 27 | Set-LoggingCustomTarget -Path C:\Logging\CustomTargets 28 | ``` 29 | 30 | ## PARAMETERS 31 | 32 | ### -Path 33 | A valid path containing *.ps1 files that defines new loggin targets 34 | 35 | ```yaml 36 | Type: String 37 | Parameter Sets: (All) 38 | Aliases: 39 | 40 | Required: True 41 | Position: 1 42 | Default value: None 43 | Accept pipeline input: False 44 | Accept wildcard characters: False 45 | ``` 46 | 47 | ### CommonParameters 48 | This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). 49 | 50 | ## INPUTS 51 | 52 | ## OUTPUTS 53 | 54 | ## NOTES 55 | 56 | ## RELATED LINKS 57 | 58 | [https://logging.readthedocs.io/en/latest/functions/Set-LoggingCustomTarget.md](https://logging.readthedocs.io/en/latest/functions/Set-LoggingCustomTarget.md) 59 | 60 | [https://logging.readthedocs.io/en/latest/functions/CustomTargets.md](https://logging.readthedocs.io/en/latest/functions/CustomTargets.md) 61 | 62 | [https://logging.readthedocs.io/en/latest/functions/Write-Log.md](https://logging.readthedocs.io/en/latest/functions/Write-Log.md) 63 | 64 | [https://github.com/EsOsO/Logging/blob/master/Logging/public/Set-LoggingCustomTarget.ps1](https://github.com/EsOsO/Logging/blob/master/Logging/public/Set-LoggingCustomTarget.ps1) 65 | 66 | -------------------------------------------------------------------------------- /docs/functions/Set-LoggingDefaultFormat.md: -------------------------------------------------------------------------------- 1 | --- 2 | external help file: Logging-help.xml 3 | Module Name: Logging 4 | online version: https://logging.readthedocs.io/en/latest/functions/Set-LoggingDefaultFormat.md 5 | schema: 2.0.0 6 | --- 7 | 8 | # Set-LoggingDefaultFormat 9 | 10 | ## SYNOPSIS 11 | Sets a global logging message format 12 | 13 | ## SYNTAX 14 | 15 | ``` 16 | Set-LoggingDefaultFormat [[-Format] ] [] 17 | ``` 18 | 19 | ## DESCRIPTION 20 | This function sets a global logging message format 21 | 22 | ## EXAMPLES 23 | 24 | ### EXAMPLE 1 25 | ``` 26 | Set-LoggingDefaultFormat -Format '[%{level:-7}] %{message}' 27 | ``` 28 | 29 | ### EXAMPLE 2 30 | ``` 31 | Set-LoggingDefaultFormat 32 | ``` 33 | 34 | It sets the default format as \[%{timestamp:+%Y-%m-%d %T%Z}\] \[%{level:-7}\] %{message} 35 | 36 | ## PARAMETERS 37 | 38 | ### -Format 39 | The string used to format the message to log 40 | 41 | ```yaml 42 | Type: String 43 | Parameter Sets: (All) 44 | Aliases: 45 | 46 | Required: False 47 | Position: 1 48 | Default value: $Defaults.Format 49 | Accept pipeline input: False 50 | Accept wildcard characters: False 51 | ``` 52 | 53 | ### CommonParameters 54 | This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). 55 | 56 | ## INPUTS 57 | 58 | ## OUTPUTS 59 | 60 | ## NOTES 61 | 62 | ## RELATED LINKS 63 | 64 | [https://logging.readthedocs.io/en/latest/functions/Set-LoggingDefaultFormat.md](https://logging.readthedocs.io/en/latest/functions/Set-LoggingDefaultFormat.md) 65 | 66 | [https://logging.readthedocs.io/en/latest/functions/LoggingFormat.md](https://logging.readthedocs.io/en/latest/functions/LoggingFormat.md) 67 | 68 | [https://logging.readthedocs.io/en/latest/functions/Write-Log.md](https://logging.readthedocs.io/en/latest/functions/Write-Log.md) 69 | 70 | [https://github.com/EsOsO/Logging/blob/master/Logging/public/Set-LoggingDefaultFormat.ps1](https://github.com/EsOsO/Logging/blob/master/Logging/public/Set-LoggingDefaultFormat.ps1) 71 | 72 | -------------------------------------------------------------------------------- /docs/functions/Set-LoggingDefaultLevel.md: -------------------------------------------------------------------------------- 1 | --- 2 | external help file: Logging-help.xml 3 | Module Name: Logging 4 | online version: https://logging.readthedocs.io/en/latest/functions/Set-LoggingDefaultLevel.md 5 | schema: 2.0.0 6 | --- 7 | 8 | # Set-LoggingDefaultLevel 9 | 10 | ## SYNOPSIS 11 | Sets a global logging severity level. 12 | 13 | ## SYNTAX 14 | 15 | ``` 16 | Set-LoggingDefaultLevel -Level [] 17 | ``` 18 | 19 | ## DESCRIPTION 20 | This function sets a global logging severity level. 21 | Log messages written with a lower logging level will be discarded. 22 | 23 | ## EXAMPLES 24 | 25 | ### EXAMPLE 1 26 | ``` 27 | Set-LoggingDefaultLevel -Level ERROR 28 | ``` 29 | 30 | PS C:\\\> Write-Log -Level INFO -Message "Test" 31 | =\> Discarded. 32 | 33 | ## PARAMETERS 34 | 35 | ### -Level 36 | {{ Fill Level Description }} 37 | 38 | ```yaml 39 | Type: String 40 | Parameter Sets: (All) 41 | Aliases: 42 | 43 | Required: True 44 | Position: Named 45 | Default value: None 46 | Accept pipeline input: False 47 | Accept wildcard characters: False 48 | ``` 49 | 50 | ### CommonParameters 51 | This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). 52 | 53 | ## INPUTS 54 | 55 | ## OUTPUTS 56 | 57 | ## NOTES 58 | 59 | ## RELATED LINKS 60 | 61 | [https://logging.readthedocs.io/en/latest/functions/Set-LoggingDefaultLevel.md](https://logging.readthedocs.io/en/latest/functions/Set-LoggingDefaultLevel.md) 62 | 63 | [https://logging.readthedocs.io/en/latest/functions/Write-Log.md](https://logging.readthedocs.io/en/latest/functions/Write-Log.md) 64 | 65 | [https://github.com/EsOsO/Logging/blob/master/Logging/public/Set-LoggingDefaultLevel.ps1](https://github.com/EsOsO/Logging/blob/master/Logging/public/Set-LoggingDefaultLevel.ps1) 66 | 67 | -------------------------------------------------------------------------------- /docs/functions/Use-LogMessage.md: -------------------------------------------------------------------------------- 1 | --- 2 | external help file: Logging-help.xml 3 | Module Name: Logging 4 | online version: https://logging.readthedocs.io/en/latest/functions/Use-LogMessage.md 5 | schema: 2.0.0 6 | --- 7 | 8 | # Use-LogMessage 9 | 10 | ## SYNOPSIS 11 | Spawned by LoggingManager to consume log messages. 12 | 13 | ## SYNTAX 14 | 15 | ``` 16 | Use-LogMessage [] 17 | ``` 18 | 19 | ## DESCRIPTION 20 | Do not call this method manually. 21 | This method will block until log messages 22 | are laid into the LoggingEventQueue and is then going to properly handle the logging. 23 | 24 | ## EXAMPLES 25 | 26 | ### EXAMPLE 1 27 | ``` 28 | DO NOT RUN MANUALLY 29 | ``` 30 | 31 | ## PARAMETERS 32 | 33 | ### CommonParameters 34 | This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). 35 | 36 | ## INPUTS 37 | 38 | ## OUTPUTS 39 | 40 | ### System.Int32 41 | ## NOTES 42 | 43 | ## RELATED LINKS 44 | 45 | [https://logging.readthedocs.io/en/latest/functions/Use-LogMessage.md](https://logging.readthedocs.io/en/latest/functions/Use-LogMessage.md) 46 | 47 | [https://github.com/EsOsO/Logging/blob/master/Logging/public/Use-LogMessage.ps1](https://github.com/EsOsO/Logging/blob/master/Logging/public/Use-LogMessage.ps1) 48 | 49 | -------------------------------------------------------------------------------- /docs/functions/Wait-Logging.md: -------------------------------------------------------------------------------- 1 | --- 2 | external help file: Logging-help.xml 3 | Module Name: Logging 4 | online version: https://logging.readthedocs.io/en/latest/functions/Wait-Logging.md 5 | schema: 2.0.0 6 | --- 7 | 8 | # Wait-Logging 9 | 10 | ## SYNOPSIS 11 | Wait for the message queue to be emptied 12 | 13 | ## SYNTAX 14 | 15 | ``` 16 | Wait-Logging [] 17 | ``` 18 | 19 | ## DESCRIPTION 20 | This function can be used to block the execution of a script waiting for the message queue to be emptied 21 | 22 | ## EXAMPLES 23 | 24 | ### EXAMPLE 1 25 | ``` 26 | Wait-Logging 27 | ``` 28 | 29 | ## PARAMETERS 30 | 31 | ### CommonParameters 32 | This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). 33 | 34 | ## INPUTS 35 | 36 | ## OUTPUTS 37 | 38 | ## NOTES 39 | 40 | ## RELATED LINKS 41 | 42 | [https://logging.readthedocs.io/en/latest/functions/Wait-Logging.md](https://logging.readthedocs.io/en/latest/functions/Wait-Logging.md) 43 | 44 | [https://github.com/EsOsO/Logging/blob/master/Logging/public/Wait-Logging.ps1](https://github.com/EsOsO/Logging/blob/master/Logging/public/Wait-Logging.ps1) 45 | 46 | -------------------------------------------------------------------------------- /docs/functions/Write-Log.md: -------------------------------------------------------------------------------- 1 | --- 2 | external help file: Logging-help.xml 3 | Module Name: Logging 4 | online version: https://logging.readthedocs.io/en/latest/functions/Write-Log.md 5 | schema: 2.0.0 6 | --- 7 | 8 | # Write-Log 9 | 10 | ## SYNOPSIS 11 | Emits a log record 12 | 13 | ## SYNTAX 14 | 15 | ``` 16 | Write-Log [-Message] [[-Arguments] ] [[-Body] ] [[-ExceptionInfo] ] 17 | [-Level ] [] 18 | ``` 19 | 20 | ## DESCRIPTION 21 | This function write a log record to configured targets with the matching level 22 | 23 | ## EXAMPLES 24 | 25 | ### EXAMPLE 1 26 | ``` 27 | Write-Log 'Hello, World!' 28 | ``` 29 | 30 | ### EXAMPLE 2 31 | ``` 32 | Write-Log -Level ERROR -Message 'Hello, World!' 33 | ``` 34 | 35 | ### EXAMPLE 3 36 | ``` 37 | Write-Log -Level ERROR -Message 'Hello, {0}!' -Arguments 'World' 38 | ``` 39 | 40 | ### EXAMPLE 4 41 | ``` 42 | Write-Log -Level ERROR -Message 'Hello, {0}!' -Arguments 'World' -Body @{Server='srv01.contoso.com'} 43 | ``` 44 | 45 | ## PARAMETERS 46 | 47 | ### -Message 48 | The text message to write 49 | 50 | ```yaml 51 | Type: String 52 | Parameter Sets: (All) 53 | Aliases: 54 | 55 | Required: True 56 | Position: 3 57 | Default value: None 58 | Accept pipeline input: False 59 | Accept wildcard characters: False 60 | ``` 61 | 62 | ### -Arguments 63 | An array of objects used to format \ 64 | 65 | ```yaml 66 | Type: Array 67 | Parameter Sets: (All) 68 | Aliases: 69 | 70 | Required: False 71 | Position: 4 72 | Default value: None 73 | Accept pipeline input: False 74 | Accept wildcard characters: False 75 | ``` 76 | 77 | ### -Body 78 | An object that can contain additional log metadata (used in target like ElasticSearch) 79 | 80 | ```yaml 81 | Type: Object 82 | Parameter Sets: (All) 83 | Aliases: 84 | 85 | Required: False 86 | Position: 5 87 | Default value: None 88 | Accept pipeline input: False 89 | Accept wildcard characters: False 90 | ``` 91 | 92 | ### -ExceptionInfo 93 | An optional ErrorRecord 94 | 95 | ```yaml 96 | Type: ErrorRecord 97 | Parameter Sets: (All) 98 | Aliases: 99 | 100 | Required: False 101 | Position: 6 102 | Default value: None 103 | Accept pipeline input: False 104 | Accept wildcard characters: False 105 | ``` 106 | 107 | ### -Level 108 | {{ Fill Level Description }} 109 | 110 | ```yaml 111 | Type: String 112 | Parameter Sets: (All) 113 | Aliases: 114 | 115 | Required: False 116 | Position: Named 117 | Default value: None 118 | Accept pipeline input: False 119 | Accept wildcard characters: False 120 | ``` 121 | 122 | ### CommonParameters 123 | This cmdlet supports the common parameters: -Debug, -ErrorAction, -ErrorVariable, -InformationAction, -InformationVariable, -OutVariable, -OutBuffer, -PipelineVariable, -Verbose, -WarningAction, and -WarningVariable. For more information, see [about_CommonParameters](http://go.microsoft.com/fwlink/?LinkID=113216). 124 | 125 | ## INPUTS 126 | 127 | ## OUTPUTS 128 | 129 | ## NOTES 130 | 131 | ## RELATED LINKS 132 | 133 | [https://logging.readthedocs.io/en/latest/functions/Write-Log.md](https://logging.readthedocs.io/en/latest/functions/Write-Log.md) 134 | 135 | [https://logging.readthedocs.io/en/latest/functions/Add-LoggingLevel.md](https://logging.readthedocs.io/en/latest/functions/Add-LoggingLevel.md) 136 | 137 | [https://github.com/EsOsO/Logging/blob/master/Logging/public/Write-Log.ps1](https://github.com/EsOsO/Logging/blob/master/Logging/public/Write-Log.ps1) 138 | 139 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # Powershell Logging Module 2 | 3 | ## Features 4 | 5 | * Separate thread that dispatch messages to targets to avoid bottleneck in the main script 6 | * Extensible with new targets 7 | * Custom formatting 8 | * Each target can have his own logging level 9 | 10 | ## Installation 11 | 12 | ### PowerShell Gallery 13 | 14 | ```powershell 15 | > Install-Module Logging 16 | > Import-Module Logging 17 | ``` 18 | 19 | ### GitHub 20 | 21 | #### Clone Repo 22 | 23 | ```terminal 24 | > git clone https://github.com/EsOsO/Logging.git 25 | > Import-Module .\Logging\Logging.psm1 26 | 27 | ``` 28 | 29 | #### Download Repo 30 | 31 | * Download [the zip](https://github.com/EsOsO/Logging/releases/latest) 32 | * Unblock the zip file (`Unblock-File -Path `) 33 | * Unzip the content of "Logging-master" to: 34 | * C:\Program Files\WindowsPowerShell\Modules\Logging **[System wide]** 35 | * D:\Users\\{username}\Documents\WindowsPowerShell\Modules\Logging **[User only]** 36 | 37 | ```powershell 38 | > Import-Module Logging 39 | ``` 40 | 41 | ## TL;DR 42 | 43 | ```powershell 44 | Set-LoggingDefaultLevel -Level 'WARNING' 45 | Add-LoggingTarget -Name Console 46 | Add-LoggingTarget -Name File -Configuration @{Path = 'C:\Temp\example_%{+%Y%m%d}.log'} 47 | 48 | $Level = 'DEBUG', 'INFO', 'WARNING', 'ERROR' 49 | foreach ($i in 1..100) { 50 | Write-Log -Level ($Level | Get-Random) -Message 'Message n. {0}' -Arguments $i 51 | Start-Sleep -Milliseconds (Get-Random -Min 100 -Max 1000) 52 | } 53 | 54 | Wait-Logging # See Note 55 | ``` 56 | 57 | ### NOTE 58 | 59 | When used in *unattended* scripts (scheduled tasks, spawned process) you need to call `Wait-Logging` to avoid losing messages. If you run your main script in an interactive shell that stays open at the end of the execution you could avoid using it (keep in mind that if there are messeages in the queue when you close the shell, you'll lose them) 60 | -------------------------------------------------------------------------------- /header-mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: Powershell Logging Module Documentation 2 | site_description: Powershell Logging Module 3 | site_author: Massimo Bonvicini 4 | repo_url: https://github.com/EsOsO/Logging 5 | edit_uri: edit/master/docs/ 6 | copyright: Powershell Logging Module is licensed under the MIT license 7 | theme: readthedocs 8 | nav: 9 | - Home: index.md 10 | - Usage: Usage.md 11 | - Available Targets: AvailableTargets.md 12 | - About: 13 | - Changelog: CHANGELOG.md 14 | - Contributing: CONTRIBUTING.md 15 | - License: LICENSE 16 | - Functions: 17 | -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: Powershell Logging Module Documentation 2 | site_description: Powershell Logging Module 3 | site_author: Massimo Bonvicini 4 | repo_url: https://github.com/EsOsO/Logging 5 | edit_uri: edit/master/docs/ 6 | copyright: Powershell Logging Module is licensed under the MIT license 7 | theme: readthedocs 8 | nav: 9 | - Home: index.md 10 | - Usage: Usage.md 11 | - Available Targets: AvailableTargets.md 12 | - About: 13 | - Changelog: CHANGELOG.md 14 | - Contributing: CONTRIBUTING.md 15 | - License: LICENSE 16 | - Functions: 17 | - Add-LoggingLevel: Add-LoggingLevel.md 18 | - Add-LoggingTarget: Add-LoggingTarget.md 19 | - Get-LoggingAvailableTarget: Get-LoggingAvailableTarget.md 20 | - Get-LoggingCallerScope: Get-LoggingCallerScope.md 21 | - Get-LoggingDefaultFormat: Get-LoggingDefaultFormat.md 22 | - Get-LoggingDefaultLevel: Get-LoggingDefaultLevel.md 23 | - Get-LoggingTarget: Get-LoggingTarget.md 24 | - Set-LoggingCallerScope: Set-LoggingCallerScope.md 25 | - Set-LoggingCustomTarget: Set-LoggingCustomTarget.md 26 | - Set-LoggingDefaultFormat: Set-LoggingDefaultFormat.md 27 | - Set-LoggingDefaultLevel: Set-LoggingDefaultLevel.md 28 | - Wait-Logging: Wait-Logging.md 29 | - Write-Log: Write-Log.md 30 | --------------------------------------------------------------------------------