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!





