Logging module written in Power Shell
Originalmente, essa solução foi concebida para atender a necessidade de “logar” a execução de um procedimento qualquer, podendo ser importada em qualquer solução na qual o código Posh possa ser importado. A solução foi originalmente escrita num tempo vago entre um projeto e outro para manter minha mente ocupada e para não deixar a skill de desenvolvimento inutilizada. Toda sua extensão foi arquitetada e escrita em quatro dias completos de trabalho, e este artigo é a primeira entrega da solução como um produto.
Considerando o enorme esforço do mundo Microsoft para a integração com sistemas Linux, podemos, hoje, afirmar que essa solução pode ser importada em diversas plataformas e soluções nas mais variadas linguagens e arquiteturas. Para obtê-la, é só clonar o repositório: https://github.com/ottogori/Posh-Logger.git
Junto ao código, já foi escrito um extenso “how to” por meio dos comentários formalizados no “help” de cada uma das funções complexas, portanto, me conterei em demonstrar o funcionamento de seus módulos principais, sem tomar muito do texto com a explicação dos procedimentos encapsulados e/ou secundários. Esses “how to” podem ser obtidos usando a função get-help de cada um dos procedimentos. Um exemplo disso é mostrado abaixo:
<#Write-OPSLogInput .SYNOPSIS Writes a log input to a stream based on the log level. .DESCRIPTION This function chooses the stream to write to based on the log level parameter and writes the input to the chosen stream. .PARAMETER logInput Input to write to the stream. .PARAMETER logLevel Level of the log input to determine which stream to write to. - Info and Verbose writes to the Verbose stream. - Debug writes to the Debug stream - Warning writes to the Warning stream - Error writes to the Error stream This is an optional parameter. If not set, it will write to the verbose stream. .NOTES Author: Otto Gori Data: 06/2017 testVersion: 0.0 Should be run on systems with PS >= 3.0 .INPUT EX Add-OPSLogInput -logInput "Initiating foobar" Add-OPSLogInput -logInput "Error on foobar" -logLevel Error .OUTPUTS Null #> function Write-OPSLogInput{ [CmdletBinding()] param( [parameter(Position=0, ValueFromPipeline=$true)] [ValidateNotNullOrEmpty()][string]$logInput = $(throw "logInput is mandatory and was not set."), [ValidateSet('verbose','info','debug','warning','error')][string]$logLevel = "verbose" ) process { switch ($logLevel) { warning{Write-Warning $logInput} error{Write-Error $logInput} debug{Write-Debug $logInput} {(($_ -eq 'verbose') -or ($_ -eq 'info'))}{Write-Verbose $logInput} Default{Write-Verbose $logInput} } } }
Dito isto, vamos à inicialização do procedimento de logging que demanda os parâmetros demonstrados abaixo e é inicializado na última linha do trecho.
# Set power shell stream handling preferences $VerbosePreference = "Continue" $DebugPreference = "SilentlyContinue" $ErrorActionPreference = "Stop" # Define global variables $global:stepInitialize = 1 $global:stepExecCmd = 2 $global:stepValidate = 3 $global:totalSteps = 3 # Initialize log $global:logLevel = "Debug" # Possible values: Error, Warning, Info and Debug New-OPSLogger -logPath "$PSScriptRoot\logs" -actionName 'Automation' | Out-Null
A arquitetura desta solução permite que você crie loggin com complexidades diferentes simultaneamente.
Isso se dá pela presença da possibilidade de logar em modo “debug” ou “info”, que permite um log detalhado e outro que é mais simples e mais “user friendly”, podendo ser anexado em um e-mail, por exemplo, no fim da execução de um procedimento de deploy ou de preparação de um ambiente. Uma terceira possibilidade é o log em estado de erro, que além do comentário do desenvolvedor, acompanhará a mensagem original de erro, permitindo um diagnóstico assertivo do problema.
O log em nível debug inclui desde o step de execução listado pelo desenvolvedor até a função na qual foi originado o erro. Já o log em nível Informativo é mais verbal, mais fácil de interpretar, podendo, então, ser usado para um informe de business, caso seja (em rara situação) requerido.
A solução inclui também um procedimento de rotação dos logs criados, filtrando-os por data e nome.
function Delete-OPSOldFiles { [CmdletBinding()] Param( [parameter(Position = 0, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true )][Alias('FullName')] [ValidateNotNullOrEmpty()][string]$path = $(throw "path is mandatory and was not set."), [parameter(Mandatory=$true)] [int]$days, [string]$filter = "*" ) begin { $limit = (Get-Date).AddDays(-$days) } process { Add-OPSLoggerInput "Deleting files from $path\$filter older then $limit ($days)..." -logLevel Info -invocation $MyInvocation Get-ChildItem "$path" -filter $filter | ` Where-Object { -not $_.PSIsContainer -and $_.CreationTime -lt $limit } | ` Add-OPSLoggerInput -format "Deleting file {0}" -logLevel Debug -invocation $MyInvocation -passthru | ` Remove-Item } } #Call for this function Delete-OPSOldFiles -path "$PSScriptRoot\logs" -days 90 -filter *.log -ErrorAction $DebugPreference
A execução da inicialização dos arquivos de log:
<#New-OPSLogger .SYNOPSIS Create a new pair of summary and detailed log files. .DESCRIPTION Creates two new log files on $logPath directory, one for detailed log and one for summary log, following the naming convention below. [Current date as dd-MM-yyyy] [$actionName] detailed.log [Current date as dd-MM-yyyy] [$actionName] summary.log Examples: 11-02-2016 AllinOne_5.0.3.5_Update detailed.log 11-02-2016 AllinOne_5.0.3.5_Update summary.log If any of the log files already exists, it will rename the existing file with a number version at the end unless explicitly requesting to replace any existing file with the alwaysReplace parameter. .PARAMETER logPath Path to where the log file will be created .PARAMETER actionName Name of the package to use as part of the file name to identify which package processing created the log file .PARAMETER alwaysReplace This is a switch parameter. If set, will always replace log file if one exists with the same name. .PARAMETER alwaysReplace This is a switch parameter. If set, The logger object stored in $global:logger will be returned. .NOTES Author: Otto Gori Data: 06/2017 testVersion: 0.0 Application user must have permition to create and rename files on the directory specified by $logPath parameter Should be run on systems with PS >= 3.0 .INPUT EX New-OPSLogger -logPath "C:\logs" -actionName "AllinOne_5.0.3.5_Update" New-OPSLogger -logPath "C:\logs" -actionName "AllinOne_5.0.3.5_Update" -alwaysReplace .OUTPUTS If passthru is set, a Dictionary with a SummaryLogFile member containg the log path, the full path to the newly created summary log file and a DetailedLogFile member containg the full path to the newly created detailed log file will be returned. The returned object is also stored in $global:logger If passthru is not set, the Dictionary will just be stored in $global:logger and not be returned. #> function New-OPSLogger{ [CmdletBinding()] param( [ValidateNotNullOrEmpty()][string]$logPath = $(throw "logPath is mandatory and was not set."), [ValidateNotNullOrEmpty()][string]$actionName = $(throw "actionName is mandatory and was not set."), [switch]$alwaysReplace, [switch]$passthru ) $summaryLogFile = New-OPSLogFile -logPath $logPath -actionName $actionName -logType summarized -alwaysReplace:$alwaysReplace $detailedLogFile = New-OPSLogFile -logPath $logPath -actionName $actionName -logType detailed -alwaysReplace:$alwaysReplace $global:logger = @{ LogPath = $logPath SummaryLogFile = $summaryLogFile DetailedLogFile = $detailedLogFile } if ($passthru) { Write-Output $global:logger } }
Criará dois logs seguindo a nomenclatura padrão descrita abaixo:
[Current date as dd-MM-yyyy] [$actionName] detailed.log [Current date as dd-MM-yyyy] [$actionName] summary.log
Exemplo: 11-02-2016 AllinOne_5.0.3.5_Update detailed.log 11-02-2016 AllinOne_5.0.3.5_Update summary.log
Com os conteúdos mostrados abaixo, nos quais foi incluído uma chamada da função inexistente “asd” para ilustrar o terceiro caso, erro.
Para incluir mais dados nos logs, basta usar as chamadas:
<span class="pl-c1">Add-OPSLoggerInput</span> <span class="pl-k">-</span>logInput <span class="pl-s">"Initiating foobar"</span> <span class="pl-k">-</span>logLevel Info <span class="pl-k">-</span>silent <span class="pl-k">-</span>invocation <span class="pl-k">lt;/span><span class="pl-c1">MyInvocation</span>
Ou:
<span class="pl-c1">Add-OPSLoggerException</span> <span class="pl-s">"Error on foobar"</span> <span class="pl-k">-</span>step <span class="pl-s">"foobar"</span> <span class="pl-k">-</span>invocation <span class="pl-k">lt;/span><span class="pl-c1">MyInvocation</span>
Para o caso de erro.
Caso você também seja um devorador de bits e queira interpretar, o código completo da solução segue abaixo:
<#New-OPSLogger .SYNOPSIS Create a new pair of summary and detailed log files. .DESCRIPTION Creates two new log files on $logPath directory, one for detailed log and one for summary log, following the naming convention below. [Current date as dd-MM-yyyy] [$actionName] detailed.log [Current date as dd-MM-yyyy] [$actionName] summary.log Examples: 11-02-2016 AllinOne_5.0.3.5_Update detailed.log 11-02-2016 AllinOne_5.0.3.5_Update summary.log If any of the log files already exists, it will rename the existing file with a number version at the end unless explicitly requesting to replace any existing file with the alwaysReplace parameter. .PARAMETER logPath Path to where the log file will be created .PARAMETER actionName Name of the package to use as part of the file name to identify which package processing created the log file .PARAMETER alwaysReplace This is a switch parameter. If set, will always replace log file if one exists with the same name. .PARAMETER alwaysReplace This is a switch parameter. If set, The logger object stored in $global:logger will be returned. .NOTES Author: Otto Gori Data: 06/2017 testVersion: 0.0 Application user must have permition to create and rename files on the directory specified by $logPath parameter Should be run on systems with PS >= 3.0 .INPUT EX New-OPSLogger -logPath "C:\logs" -actionName "AllinOne_5.0.3.5_Update" New-OPSLogger -logPath "C:\logs" -actionName "AllinOne_5.0.3.5_Update" -alwaysReplace .OUTPUTS If passthru is set, a Dictionary with a SummaryLogFile member containg the log path, the full path to the newly created summary log file and a DetailedLogFile member containg the full path to the newly created detailed log file will be returned. The returned object is also stored in $global:logger If passthru is not set, the Dictionary will just be stored in $global:logger and not be returned. #> function New-OPSLogger{ [CmdletBinding()] param( [ValidateNotNullOrEmpty()][string]$logPath = $(throw "logPath is mandatory and was not set."), [ValidateNotNullOrEmpty()][string]$actionName = $(throw "actionName is mandatory and was not set."), [switch]$alwaysReplace, [switch]$passthru ) $summaryLogFile = New-OPSLogFile -logPath $logPath -actionName $actionName -logType summarized -alwaysReplace:$alwaysReplace $detailedLogFile = New-OPSLogFile -logPath $logPath -actionName $actionName -logType detailed -alwaysReplace:$alwaysReplace $global:logger = @{ LogPath = $logPath SummaryLogFile = $summaryLogFile DetailedLogFile = $detailedLogFile } if ($passthru) { Write-Output $global:logger } } <#Add-OPSLoggerInput .SYNOPSIS Adds a log input to log files. .DESCRIPTION This function formats the input adding the current date, log level and caller's function name to the input, adds it to the log files and optionaly writes the input to a stream according to the log level. It can add the input only to the detailed log file, only to the summary log file or to both detailed and log file and can also add different inputs to the summary and detailed log files. The parameter $logInput is used as the input of both detailed and summary log, with the switch parameter $summary indicating if the input should be added to the summary log. Or the parameters detailedInput and summaryInput can be used to add different inputs to each log or only one of them. .PARAMETER logInput Input to write to the log files. This input will be formated using the Format-OPSLogInput function which adds the current date and log level to the input. Used in conjunction with $summary, indicates if the logInput will be written only to the detailed log or to both logs. .PARAMETER logger Dictionary returned from New-OPSLogger containing the Full path to both detailed and summarized log files. This parameter is optional. If ommited, uses the $global:logger set by the last call to New-OPSLogger .PARAMETER summaryInput Input to write to the summary log. This input will be formated using the Format-OPSLogInput function which adds the current date and log level to the input. If $logInput is set and the switch parameter $summary is also set this parameter will be replaced with the $logInput parameter value as the input to write to the summary log. .PARAMETER detailedInput Input to write to the detailed log. This input will be formated using the Format-OPSLogInput function which adds the current date and log level to the input. If $logInput is set this parameter will be replaced with the $logInput parameter value as the input to write to the detailed log. .PARAMETER format The logged information will be formated using this string replacing {0} with value from logInput. The output information (when $output parameter is set) will not be affected by this parameter as it passes through the input as is. This parameter is optional. If ommited, the string "{0}" will be used resulting in the logInput being logged as is. .PARAMETER summaryFormat The logged information for the summary log will be formated using this string replacing {0} with value from logInput. The information is only formated if logInput is used. If summaryInput is used, the logged information will be summaryInput as is. This is done because summaryInput does not come from pipe and will always be a single line of string. So it can easily be formated prior to calling this function, which is not the case when piping the input to logInput and passing through the output without any modification. The output information (when $output parameter is set) will not be affected by this parameter as it passes through the input as is. This parameter is optional. If ommited, the string "{0}" will be used resulting in the logInput being logged as is. .PARAMETER detailedFormat The logged information for the detailed log will be formated using this string replacing {0} with value from logInput. The information is only formated if logInput is used. If detailedInput is used, the logged information will be detailedInput as is. This is done because detailedInput does not come from pipe and will always be a single line of string. So it can easily be formated prior to calling this function, which is not the case when piping the input to logInput and passing through the output without any modification. The output information (when $output parameter is set) will not be affected by this parameter as it passes through the input as is. This parameter is optional. If ommited, the string "{0}" will be used resulting in the logInput being logged as is. .PARAMETER logLevel Level of the log input. This parameter is also used to determine which stream to write to. - Info and Verbose writes to the Verbose stream. - Debug writes to the Debug stream - Warning writes to the Warning stream - Error writes to the Error stream This is an optional parameter. If not set, only the current date will be added to the input. .PARAMETER invocation The invocation variable of the function that has the name to be put on the log input. The name of the function will be put only on the detailed log. This is an optional parameter. .PARAMETER summary This is a switch parameter. It is used to indicate if the logInput parameter will be written only to the detailed log or to both detailed and summary log. .PARAMETER passthru This is a switch parameter. It is used to passthru the input when piping. This allows adding the Add-OPSLoggerInput call in the middle of a piped instruction to log what information is being piped. .PARAMETER silent This is a switch parameter. If set, nothing will be written to any stream. If ommited the input (without formating) will be sent to a stream. .NOTES Author: Otto Gori Data: 06/2017 testVersion: 0.1 Application user must have write permition to the log file Should be run on systems with PS >= 3.0 .INPUT EX Add-OPSLoggerInput -logInput "Initiating foobar" -logLevel Info -silent -invocation $MyInvocation Add-OPSLoggerInput -logger $logger -logInput "Error on foobar" -logLevel Error -summary -invocation $MyInvocation Add-OPSLoggerInput -logger $logger -detailedInput "Done processing foobar with warnings" -summaryInput "Done processing foobar" -logLevel Warning .OUTPUTS Null #> function Add-OPSLoggerInput{ [CmdletBinding()] param( [parameter(Position = 0, ValueFromPipeline = $true)]$logInput, $logger = $global:logger, [string]$summaryInput, [string]$detailedInput, [string]$format, [string]$summaryFormat = "{0}", [string]$detailedFormat = "{0}", [string]$logLevel = "Info", $invocation, [switch]$summary, [switch]$passthru, [switch]$silent ) process { if ($format) { $detailedFormat = $format if ($summary) { $summaryFormat = $format } } if ($logInput) { $detailedInput = "$detailedFormat" -f "$logInput" if ($summary) { $summaryInput = "$summaryFormat" -f "$logInput" } } if ($logger) { if ($detailedInput -and $logger.DetailedLogFile) { $detailedFileName = $logger.DetailedLogFile Add-OPSLogInput -logFileName $detailedFileName -logInput $detailedInput -logLevel $logLevel -invocation $invocation -silent } if ($summaryInput -and $logger.SummaryLogFile) { $summaryFileName = $logger.SummaryLogFile Add-OPSLogInput -logFileName $summaryFileName -logInput $summaryInput -logLevel $logLevel -silent } } if (-not $silent) { if ($detailedInput) { Write-OPSLogInput -logInput $detailedInput -logLevel $logLevel } elseif ($summaryInput) { Write-OPSLogInput -logInput $summaryInput -logLevel $logLevel } } if ($passthru) { Write-Output $logInput } } } <#Add-OPSLoggerException .SYNOPSIS Adds a error log input to log files and creates an object for exceptions. .DESCRIPTION This function calls Add-OPSLoggerInput with log level as error, but without stoping execution. It also creates an object with details of the error that can be used by throwing it and catching on caller's function This function does not accept piping like Add-OPSLoggerInput, so it doesn't have the format parameters. And it always sets log level to Error, so it neither has the logLevel parameter. Besides these things, the behaviour is the same as Add-OPSLoggerInput .PARAMETER logInput Input to write to the log files. This input will be formated using the Format-OPSLogInput function which adds the current date and log level to the input. Used in conjunction with $summary, indicates if the logInput will be written only to the detailed log or to both logs. .PARAMETER logger Dictionary returned from New-OPSLogger containing the Full path to both detailed and summarized log files. This parameter is optional. If ommited, uses the $global:logger set by the last call to New-OPSLogger .PARAMETER step Step that was being executed when the error happened. This value is added to the object returned and if thrown, can be retrived in the catch block with $_.TargetObject.step. .PARAMETER summaryInput Input to write to the summary log. This input will be formated using the Format-OPSLogInput function which adds the current date and log level to the input. If $logInput is set and the switch parameter $summary is also set this parameter will be replaced with the $logInput parameter value as the input to write to the summary log. .PARAMETER detailedInput Input to write to the detailed log. This input will be formated using the Format-OPSLogInput function which adds the current date and log level to the input. If $logInput is set this parameter will be replaced with the $logInput parameter value as the input to write to the detailed log. .PARAMETER exceptionMessage Message to add to the object returned and if thrown can be retrived in the catch block with $_.TargetObject.message This parameter is optional. If ommited, one of the input parameters will be used following the order of which one has value in the order logInput, detailedInput and summaryInput .PARAMETER invocation The invocation variable of the function that has the name to be put on the log input and on the object returned. The name of the function will be put only on the detailed log. This is an optional parameter. .PARAMETER summary This is a switch parameter. It is used to indicate if the logInput parameter will be written only to the detailed log or to both detailed and summary log. .PARAMETER silent This is a switch parameter. If set, nothing will be written to the error stream. If ommited the input will be sent to the error stream. .NOTES Author: Otto Gori Data: 06/2017 testVersion: 0.0 Application user must have write permition to the log file Should be run on systems with PS >= 3.0 .INPUT EX Add-OPSLoggerException "Error on foobar" -step "foobar" -invocation $MyInvocation .OUTPUTS Ditionary containing the keys step, message and invocation #> function Add-OPSLoggerException{ [CmdletBinding()] param( [parameter(Position = 0)]$logInput, $logger = $global:logger, [ValidateNotNullOrEmpty()][string]$step = $(throw "step is mandatory and was not set."), [string]$summaryInput, [string]$detailedInput, [string]$exceptionMessage, $invocation, [switch]$summary, [switch]$silent ) if ($logInput) { $detailedInput = $logInput if ($summary) { $summaryInput = $logInput } } if (-not $exceptionMessage) { if ($detailedInput) { $exceptionMessage = $detailedInput } else { $exceptionMessage = $summaryInput } } Add-OPSLoggerInput -logger $logger -summaryInput $summaryInput -detailedInput $detailedInput ` -logLevel Error -invocation $invocation -summary:$summary -silent:$silent -ErrorAction "Continue" return New-OPSStepException -step $step -message $exceptionMessage -invocation $invocation } <#New-OPSLogFile .SYNOPSIS Create new log file .DESCRIPTION Creates a new log file on $logPath directory following the naming convention below. [Current date as dd-MM-yyyy] [$actionName] [$logType].log Examples: 11-02-2016 Update detailed.log 11-02-2016 Update summary.log If the log file already exists, it will rename the existing file with a number version at the end unless explicitly requesting to replace existing file with the alwaysReplace parameter. .PARAMETER logPath Path to where the log file will be created .PARAMETER actionName Name of the package to use as part of the file name to identify which package processing created the log file .PARAMETER logType Type of the log (summary or detailed) This is an optional parameter. If not included, the log file name will be just [Current date as dd-MM-yyyy] [$actionName].log .PARAMETER alwaysReplace This is a switch parameter. If set, will always replace log file if one exists with the same name. .NOTES Author: Otto Gori Data: 06/2017 testVersion: 0.1 Application user must have permition to create and rename files on the directory specified by $logPath parameter Should be run on systems with PS >= 3.0 .INPUT EX New-OPSLogFile -logPath "C:\logs" -actionName "AllinOne_5.0.3.5_Update" -logType detailed New-OPSLogFile -logPath "C:\logs" -actionName "AllinOne_5.0.3.5_Update" -logType summary -alwaysReplace .OUTPUTS String with the full path to the newly created log file #> function New-OPSLogFile{ [CmdletBinding()] param( [ValidateNotNullOrEmpty()][string]$logPath = $(throw "logPath is mandatory and was not set."), [ValidateNotNullOrEmpty()][string]$actionName = $(throw "actionName is mandatory and was not set."), [string]$logType, [switch]$alwaysReplace ) [string]$dateString = (Get-Date -UFormat %d-%b-%Y) if ($logType){ [string]$logFileName = "$dateString $actionName $logType" } else { [string]$logFileName = "$dateString $actionName" } [string]$logFullName = "$logFileName.log" if ($alwaysReplace) { Format-OPSLogInput "Creating log file at '$logPath\$logFullName' overriding if exists" | Write-Verbose New-Item "$logPath\$logFullName" -ItemType file -Force | Out-Null } else { if (Test-Path "$logPath\$logFullName") { Format-OPSLogInput "'$logPath\$logFullName' already exists. Attempting to rename old log file." | Write-Verbose Rename-OPSLastLogFile -logPath $logPath -logFileName $logFileName } Format-OPSLogInput "Creating log file at '$logPath\$logFullName'" | Write-Verbose New-Item "$logPath\$logFullName" -ItemType file -Force | Out-Null } Format-OPSLogInput "File '$logPath\$logFullName' created" | Write-Verbose return "$logPath\$logFullName" } <#Rename-OPSLastLogFile .SYNOPSIS Adds a version number to the name of an old log file. .DESCRIPTION Renames a log file adding a version number to keep log files from being replaced. It searches for all the log files that has the same name already with a version number and picks the next number to the maximum version number found to use as the version number. It is used by New-OPSLogFile to keep all log files for the same date, package and log type. .PARAMETER logPath Path to where the log file will be created. .PARAMETER logFileName Name of the log file without the extension. .NOTES Author: Otto Gori Data: 06/2017 testVersion: 0.0 Application user must have permition to create and rename files on the directory specified by $logPath parameter Should be run on systems with PS >= 3.0 .INPUT EX Rename-OPSLastLogFile -logPath "C:\logs" -logFileName "11-02-2016 AllinOne_5.0.3.5_Update detailed" Rename-OPSLastLogFile -logPath "C:\logs" -logFileName "11-02-2016 AllinOne_5.0.3.5_Update summary" .OUTPUTS Null #> function Rename-OPSLastLogFile{ [CmdletBinding()] param( [ValidateNotNullOrEmpty()][string]$logPath = $(throw "logPath is mandatory and was not set."), [ValidateNotNullOrEmpty()][string]$logFileName = $(throw "logFileName is mandatory and was not set.") ) $files = get-childitem "$logPath\${logFileName}_*.log" [int]$lastFileNum = 0 if ($files) { Format-OPSLogInput "Additional old log files for '$logFileName' found. Searching for highest number suffix." | Write-Verbose $filesNumMeasure = $files | Select-Object -ExpandProperty BaseName ` | Select-String -pattern '\d+#039; ` | Select-Object -ExpandProperty matches ` | Select-Object -ExpandProperty value ` | ForEach-Object {[int]$_} ` | Measure-Object -Maximum [int]$filesNumCount = $filesNumMeasure | Select-Object -ExpandProperty Count if ($filesNumCount -gt 0) { $lastFileNum = $filesNumMeasure | Select-Object -ExpandProperty Maximum } Format-OPSLogInput "Highest number suffix found for '$logFileName' is $lastFileNum" | Write-Verbose } [int]$nextFileNum = $lastFileNum + 1 [string]$oldFileName = "$logPath\$logFileName.log" [string]$newFileName = "${logFileName}_$nextFileNum.log" Format-OPSLogInput "Attepting to rename log file '$oldFileName' to '$newFileName'" | Write-Verbose Rename-Item $oldFileName $newFileName -Force Format-OPSLogInput "Succeeded renaming log file '$oldFileName' to '$newFileName'" | Write-Verbose } <#Format-OPSLogInput .SYNOPSIS Formats a log input .DESCRIPTION Formats the input of a log by adding to the input the current date and optionaly the log level and function name. .PARAMETER logInput Log input that will be formated. .PARAMETER logLevel Level of the log input. This is an optional parameter. .PARAMETER invocation The invocation variable of the function that has the name to be put on the log input. This is an optional parameter. .NOTES Author: Otto Gori Data: 06/2017 testVersion: 0.0 .INPUT EX Format-OPSLogInput -logInput "Initiating foobar" Format-OPSLogInput -logInput "Error on foobar" -logLevel Error -invocation $MyInvocation .OUTPUTS String with the formated log input. #> function Format-OPSLogInput{ [CmdletBinding()] param( [parameter(Position=0, ValueFromPipeline=$true)] [ValidateNotNullOrEmpty()][string]$logInput = $(throw "logInput is mandatory and was not set."), [string]$logLevel, $invocation ) process { $dateTime = Get-Date -Format "dd-MM-yyyy HH:mm:ss.ffff" if ($invocation) { $functionName = $invocation.MyCommand.Name $formatedInput = "$dateTime - [$functionName] $logInput" } else { $formatedInput = "$dateTime - $logInput" } if ($logLevel) { return "[$logLevel]: $formatedInput" } else { return "$formatedInput" } } } <#Add-OPSLogInput .SYNOPSIS Adds a log input to a log file. .DESCRIPTION This function formats the input adding the current date and log level to the input, adds it to the log file and optionaly writes the input to a stream according to the log level. .PARAMETER logFileName Full path to the log's file. .PARAMETER logInput Input to write to the log. This input will be formated using the Format-OPSLogInput function which adds the current date and log level to the input. .PARAMETER logLevel Level of the log input. This parameter is also used to determine which stream to write to. - Info and Verbose writes to the Verbose stream. - Debug writes to the Debug stream - Warning writes to the Warning stream - Error writes to the Error stream This is an optional parameter. If not set, only the current date will be added to the input. .PARAMETER invocation The invocation variable of the function that has the name to be put on the log input. This is an optional parameter. .PARAMETER silent This is a switch parameter. If set, nothing will be written to any stream. If omitted the input (without formating) will be sent to a stream. .NOTES Author: Otto Gori Data: 06/2017 testVersion: 0.1 Application user must have write permition to the log file Should be run on systems with PS >= 3.0 .INPUT EX Add-OPSLogInput -logFileName "C:\logs\11-02-2016 AllinOne_5.0.3.5_Update detailed" -logInput "Initiating foobar" -logLevel Info -silent Add-OPSLogInput -logFileName "C:\logs\11-02-2016 AllinOne_5.0.3.5_Update summary" -logInput "Error on foobar" -logLevel Error -invocation $MyInvocation .OUTPUTS Null #> function Add-OPSLogInput{ [CmdletBinding()] param( [parameter(Position=0, ValueFromPipeline=$true)] [ValidateNotNullOrEmpty()][string]$logInput = $(throw "logInput is mandatory and was not set."), [ValidateNotNullOrEmpty()][string]$logFileName = $(throw "logFileName is mandatory and was not set."), [string]$logLevel = "Info", $invocation, [switch]$silent ) begin { $globalLevelNum = Convert-OPSLogLevel $global:logLevel $levelNum = Convert-OPSLogLevel $logLevel } process { if ($levelNum -ge $globalLevelNum) { [string]$formatedInput = Format-OPSLogInput -logInput $logInput -logLevel $logLevel -invocation $invocation Add-Content $logFileName "$formatedInput" } if (-not $silent) { Write-OPSLogInput -logInput $logInput -logLevel $logLevel } } } <#Write-OPSLogInput .SYNOPSIS Writes a log input to a stream based on the log level. .DESCRIPTION This function chooses the stream to write to based on the log level parameter and writes the input to the chosen stream. .PARAMETER logInput Input to write to the stream. .PARAMETER logLevel Level of the log input to determine which stream to write to. - Info and Verbose writes to the Verbose stream. - Debug writes to the Debug stream - Warning writes to the Warning stream - Error writes to the Error stream This is an optional parameter. If not set, it will write to the verbose stream. .NOTES Author: Otto Gori Data: 06/2017 testVersion: 0.0 Should be run on systems with PS >= 3.0 .INPUT EX Add-OPSLogInput -logInput "Initiating foobar" Add-OPSLogInput -logInput "Error on foobar" -logLevel Error .OUTPUTS Null #> function Write-OPSLogInput{ [CmdletBinding()] param( [parameter(Position=0, ValueFromPipeline=$true)] [ValidateNotNullOrEmpty()][string]$logInput = $(throw "logInput is mandatory and was not set."), [ValidateSet('verbose','info','debug','warning','error')][string]$logLevel = "verbose" ) process { switch ($logLevel) { warning{Write-Warning $logInput} error{Write-Error $logInput} debug{Write-Debug $logInput} {(($_ -eq 'verbose') -or ($_ -eq 'info'))}{Write-Verbose $logInput} Default{Write-Verbose $logInput} } } } <#Convert-OPSLogLevel .SYNOPSIS Converts a string log level into a numeric log level. .DESCRIPTION Converts the strings for log levels (Error, Warning, Info, Verbose and Debug) into numeric representation to facilitate in log level calculations to determine whether a log input should be logged. .PARAMETER logLevel Level to convert. The conversions are as follows - Error: 3 - Warning: 2 - Info and Verbose: 1 - Debug: 0 .NOTES Author: Otto Gori Data: 06/2017 testVersion: 0.0 .INPUT EX Convert-OPSLogLevel Error Convert-OPSLogLevel -logLevel Debug .OUTPUTS Numeric representation of the log level. #> function Convert-OPSLogLevel { [CmdletBinding()] param( [parameter(Position=0, ValueFromPipeline=$true)] [ValidateSet('verbose','info','debug','warning','error')][string]$logLevel = $(throw "logLevel is mandatory and was not set.") ) process{ switch ($logLevel) { debug{return 0} {(($_ -eq 'verbose') -or ($_ -eq 'info'))}{return 1} warning{return 2} error{return 3} Default{return 0} } } } function New-OPSDirectory { [CmdletBinding()] Param( [parameter(Position = 0, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true )][Alias('FullName')] [ValidateNotNullOrEmpty()][string]$path = $(throw "path is mandatory and was not set."), [switch]$ignoreIfExists ) process { if (-not $ignoreIfExists -or -not (test-path $path)) { New-Item -path $path -ItemType Directory Add-OPSLoggerInput "Directory $path created" -logLevel Debug -invocation $MyInvocation } else { $directory = Get-Item -path $path if ($directory.PSIsContainer) { Add-OPSLoggerInput "Directory $path already existed. Returned existing directory" -logLevel Debug -invocation $MyInvocation return $directory } else { throw [System.Exception] "Could not create directory $path because a file with this name already exists" } } } } function Delete-OPSOldFiles { [CmdletBinding()] Param( [parameter(Position = 0, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true )][Alias('FullName')] [ValidateNotNullOrEmpty()][string]$path = $(throw "path is mandatory and was not set."), [parameter(Mandatory=$true)] [int]$days, [string]$filter = "*" ) begin { $limit = (Get-Date).AddDays(-$days) } process { Add-OPSLoggerInput "Deleting files from $path\$filter older then $limit ($days)..." -logLevel Info -invocation $MyInvocation Get-ChildItem "$path" -filter $filter | ` Where-Object { -not $_.PSIsContainer -and $_.CreationTime -lt $limit } | ` Add-OPSLoggerInput -format "Deleting file {0}" -logLevel Debug -invocation $MyInvocation -passthru | ` Remove-Item } } function New-OPSStepException{ [CmdletBinding()] param( [ValidateNotNullOrEmpty()][string]$step = $(throw "step is mandatory and was not set."), [ValidateNotNullOrEmpty()][string]$message = $(throw "message is mandatory and was not set."), $invocation = $(throw "invocation is mandatory and was not set.") ) return @{ step = $step message = $message invocation = $invocation } }
Ficou alguma dúvida ou tem alguma observação a fazer? Aproveite os campos abaixo. Até a próxima!