First commit - backup RDOC
This commit is contained in:
575
scripts/logging-notifications.ps1
Normal file
575
scripts/logging-notifications.ps1
Normal file
@@ -0,0 +1,575 @@
|
||||
# Sistema de Logs e Notificações para RDO-C
|
||||
# Gerencia logs detalhados e notificações em tempo real
|
||||
# Uso: Import-Module .\scripts\logging-notifications.ps1
|
||||
|
||||
# Configurações globais de logging
|
||||
$script:LogConfig = @{
|
||||
LogDirectory = "logs"
|
||||
MaxLogSize = 10MB
|
||||
MaxLogFiles = 10
|
||||
LogLevels = @("TRACE", "DEBUG", "INFO", "WARN", "ERROR", "FATAL")
|
||||
DateFormat = "yyyy-MM-dd HH:mm:ss.fff"
|
||||
EnableConsole = $true
|
||||
EnableFile = $true
|
||||
EnableNotifications = $true
|
||||
NotificationTypes = @("desktop", "email", "webhook")
|
||||
}
|
||||
|
||||
# Configurações de notificação
|
||||
$script:NotificationConfig = @{
|
||||
Desktop = @{
|
||||
Enabled = $true
|
||||
ShowSuccess = $false
|
||||
ShowWarning = $true
|
||||
ShowError = $true
|
||||
Duration = 5000
|
||||
}
|
||||
Email = @{
|
||||
Enabled = $false
|
||||
SmtpServer = ""
|
||||
Port = 587
|
||||
Username = ""
|
||||
Password = ""
|
||||
From = ""
|
||||
To = @()
|
||||
Subject = "RDO-C Auto-Sync Notification"
|
||||
}
|
||||
Webhook = @{
|
||||
Enabled = $false
|
||||
Url = ""
|
||||
Method = "POST"
|
||||
Headers = @{}
|
||||
AuthToken = ""
|
||||
}
|
||||
Slack = @{
|
||||
Enabled = $false
|
||||
WebhookUrl = ""
|
||||
Channel = "#dev-notifications"
|
||||
Username = "RDO-C Bot"
|
||||
IconEmoji = ":robot_face:"
|
||||
}
|
||||
Discord = @{
|
||||
Enabled = $false
|
||||
WebhookUrl = ""
|
||||
Username = "RDO-C Auto-Sync"
|
||||
AvatarUrl = ""
|
||||
}
|
||||
}
|
||||
|
||||
# Cores para diferentes níveis de log
|
||||
$script:LogColors = @{
|
||||
TRACE = "DarkGray"
|
||||
DEBUG = "Gray"
|
||||
INFO = "White"
|
||||
WARN = "Yellow"
|
||||
ERROR = "Red"
|
||||
FATAL = "Magenta"
|
||||
SUCCESS = "Green"
|
||||
}
|
||||
|
||||
# Emojis para notificações
|
||||
$script:LogEmojis = @{
|
||||
TRACE = "🔍"
|
||||
DEBUG = "🐛"
|
||||
INFO = "ℹ️"
|
||||
WARN = "⚠️"
|
||||
ERROR = "❌"
|
||||
FATAL = "💀"
|
||||
SUCCESS = "✅"
|
||||
|
||||
SYNC = "🔄"
|
||||
DEPLOY = "🚀"
|
||||
GIT = "📝"
|
||||
}
|
||||
|
||||
# Classe para gerenciar logs estruturados
|
||||
class LogEntry {
|
||||
[string]$Timestamp
|
||||
[string]$Level
|
||||
[string]$Category
|
||||
[string]$Message
|
||||
[hashtable]$Context
|
||||
[string]$Source
|
||||
[string]$Thread
|
||||
[string]$SessionId
|
||||
|
||||
LogEntry([string]$level, [string]$message, [string]$category = "General", [hashtable]$context = @{}) {
|
||||
$this.Timestamp = Get-Date -Format $script:LogConfig.DateFormat
|
||||
$this.Level = $level.ToUpper()
|
||||
$this.Category = $category
|
||||
$this.Message = $message
|
||||
$this.Context = $context
|
||||
$this.Source = (Get-PSCallStack)[2].Command
|
||||
$this.Thread = [System.Threading.Thread]::CurrentThread.ManagedThreadId
|
||||
$this.SessionId = $env:RDO_SESSION_ID ?? (New-Guid).ToString().Substring(0,8)
|
||||
}
|
||||
|
||||
[string] ToString() {
|
||||
$contextStr = if ($this.Context.Count -gt 0) {
|
||||
" | Context: $($this.Context | ConvertTo-Json -Compress)"
|
||||
} else { "" }
|
||||
|
||||
return "[$($this.Timestamp)] [$($this.Level)] [$($this.Category)] [$($this.Source)] $($this.Message)$contextStr"
|
||||
}
|
||||
|
||||
[hashtable] ToHashtable() {
|
||||
return @{
|
||||
timestamp = $this.Timestamp
|
||||
level = $this.Level
|
||||
category = $this.Category
|
||||
message = $this.Message
|
||||
context = $this.Context
|
||||
source = $this.Source
|
||||
thread = $this.Thread
|
||||
session_id = $this.SessionId
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Inicializar sistema de logging
|
||||
function Initialize-LoggingSystem {
|
||||
param(
|
||||
[string]$ConfigPath = "logging-config.json"
|
||||
)
|
||||
|
||||
# Criar diretório de logs
|
||||
if (-not (Test-Path $script:LogConfig.LogDirectory)) {
|
||||
New-Item -ItemType Directory -Path $script:LogConfig.LogDirectory -Force | Out-Null
|
||||
}
|
||||
|
||||
# Carregar configuração personalizada se existir
|
||||
if (Test-Path $ConfigPath) {
|
||||
try {
|
||||
$customConfig = Get-Content $ConfigPath | ConvertFrom-Json
|
||||
|
||||
# Mesclar configurações
|
||||
foreach ($key in $customConfig.PSObject.Properties.Name) {
|
||||
if ($script:LogConfig.ContainsKey($key)) {
|
||||
$script:LogConfig[$key] = $customConfig.$key
|
||||
}
|
||||
}
|
||||
|
||||
Write-LogEntry "INFO" "Configuração de logging carregada: $ConfigPath" "System"
|
||||
} catch {
|
||||
Write-LogEntry "WARN" "Falha ao carregar configuração: $($_.Exception.Message)" "System"
|
||||
}
|
||||
}
|
||||
|
||||
# Configurar ID da sessão
|
||||
if (-not $env:RDO_SESSION_ID) {
|
||||
$env:RDO_SESSION_ID = (New-Guid).ToString().Substring(0,8)
|
||||
}
|
||||
|
||||
Write-LogEntry "INFO" "Sistema de logging inicializado - Sessão: $env:RDO_SESSION_ID" "System"
|
||||
|
||||
return $true
|
||||
}
|
||||
|
||||
# Função principal de logging
|
||||
function Write-LogEntry {
|
||||
param(
|
||||
[Parameter(Mandatory=$true)]
|
||||
[ValidateSet("TRACE", "DEBUG", "INFO", "WARN", "ERROR", "FATAL", "SUCCESS")]
|
||||
[string]$Level,
|
||||
|
||||
[Parameter(Mandatory=$true)]
|
||||
[string]$Message,
|
||||
|
||||
[string]$Category = "General",
|
||||
[hashtable]$Context = @{},
|
||||
[string]$LogFile = "",
|
||||
[switch]$NoConsole,
|
||||
[switch]$NoFile,
|
||||
[switch]$NoNotification
|
||||
)
|
||||
|
||||
# Criar entrada de log
|
||||
$logEntry = [LogEntry]::new($Level, $Message, $Category, $Context)
|
||||
|
||||
# Log no console
|
||||
if ($script:LogConfig.EnableConsole -and -not $NoConsole) {
|
||||
Write-ConsoleLog -LogEntry $logEntry
|
||||
}
|
||||
|
||||
# Log em arquivo
|
||||
if ($script:LogConfig.EnableFile -and -not $NoFile) {
|
||||
Write-FileLog -LogEntry $logEntry -LogFile $LogFile
|
||||
}
|
||||
|
||||
# Notificações
|
||||
if ($script:LogConfig.EnableNotifications -and -not $NoNotification) {
|
||||
Send-LogNotification -LogEntry $logEntry
|
||||
}
|
||||
|
||||
return $logEntry
|
||||
}
|
||||
|
||||
# Escrever log no console
|
||||
function Write-ConsoleLog {
|
||||
param([LogEntry]$LogEntry)
|
||||
|
||||
$color = $script:LogColors[$LogEntry.Level]
|
||||
$emoji = $script:LogEmojis[$LogEntry.Level]
|
||||
|
||||
$prefix = "$emoji [$($LogEntry.Level)] [$($LogEntry.Category)]"
|
||||
$message = "$prefix $($LogEntry.Message)"
|
||||
|
||||
Write-Host $message -ForegroundColor $color
|
||||
|
||||
# Mostrar contexto se disponível
|
||||
if ($LogEntry.Context.Count -gt 0) {
|
||||
$contextStr = $LogEntry.Context | ConvertTo-Json -Compress
|
||||
Write-Host " Context: $contextStr" -ForegroundColor DarkGray
|
||||
}
|
||||
}
|
||||
|
||||
# Escrever log em arquivo
|
||||
function Write-FileLog {
|
||||
param(
|
||||
[LogEntry]$LogEntry,
|
||||
[string]$LogFile = ""
|
||||
)
|
||||
|
||||
# Determinar arquivo de log
|
||||
if (-not $LogFile) {
|
||||
$date = Get-Date -Format "yyyy-MM-dd"
|
||||
$LogFile = Join-Path $script:LogConfig.LogDirectory "rdo-auto-sync-$date.log"
|
||||
} else {
|
||||
$LogFile = Join-Path $script:LogConfig.LogDirectory $LogFile
|
||||
}
|
||||
|
||||
try {
|
||||
# Verificar rotação de logs
|
||||
if (Test-Path $LogFile) {
|
||||
$fileInfo = Get-Item $LogFile
|
||||
if ($fileInfo.Length -gt $script:LogConfig.MaxLogSize) {
|
||||
Rotate-LogFile -LogFile $LogFile
|
||||
}
|
||||
}
|
||||
|
||||
# Escrever entrada
|
||||
$logLine = $LogEntry.ToString()
|
||||
Add-Content -Path $LogFile -Value $logLine -Encoding UTF8
|
||||
|
||||
} catch {
|
||||
Write-Host "❌ Erro ao escrever log: $($_.Exception.Message)" -ForegroundColor Red
|
||||
}
|
||||
}
|
||||
|
||||
# Rotacionar arquivos de log
|
||||
function Rotate-LogFile {
|
||||
param([string]$LogFile)
|
||||
|
||||
$directory = Split-Path $LogFile -Parent
|
||||
$baseName = [System.IO.Path]::GetFileNameWithoutExtension($LogFile)
|
||||
$extension = [System.IO.Path]::GetExtension($LogFile)
|
||||
|
||||
# Mover arquivos existentes
|
||||
for ($i = $script:LogConfig.MaxLogFiles - 1; $i -gt 0; $i--) {
|
||||
$oldFile = Join-Path $directory "$baseName.$i$extension"
|
||||
$newFile = Join-Path $directory "$baseName.$($i + 1)$extension"
|
||||
|
||||
if (Test-Path $oldFile) {
|
||||
if ($i -eq ($script:LogConfig.MaxLogFiles - 1)) {
|
||||
Remove-Item $oldFile -Force
|
||||
} else {
|
||||
Move-Item $oldFile $newFile -Force
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Mover arquivo atual
|
||||
$rotatedFile = Join-Path $directory "$baseName.1$extension"
|
||||
Move-Item $LogFile $rotatedFile -Force
|
||||
|
||||
Write-LogEntry "INFO" "Log rotacionado: $LogFile" "System"
|
||||
}
|
||||
|
||||
# Enviar notificações
|
||||
function Send-LogNotification {
|
||||
param([LogEntry]$LogEntry)
|
||||
|
||||
# Filtrar por nível
|
||||
$shouldNotify = switch ($LogEntry.Level) {
|
||||
"SUCCESS" { $script:NotificationConfig.Desktop.ShowSuccess }
|
||||
"WARN" { $script:NotificationConfig.Desktop.ShowWarning }
|
||||
"ERROR" { $script:NotificationConfig.Desktop.ShowError }
|
||||
"FATAL" { $true }
|
||||
default { $false }
|
||||
}
|
||||
|
||||
if (-not $shouldNotify) { return }
|
||||
|
||||
# Notificação desktop
|
||||
if ($script:NotificationConfig.Desktop.Enabled) {
|
||||
Send-DesktopNotification -LogEntry $LogEntry
|
||||
}
|
||||
|
||||
# Notificação por email
|
||||
if ($script:NotificationConfig.Email.Enabled) {
|
||||
Send-EmailNotification -LogEntry $LogEntry
|
||||
}
|
||||
|
||||
# Webhook genérico
|
||||
if ($script:NotificationConfig.Webhook.Enabled) {
|
||||
Send-WebhookNotification -LogEntry $LogEntry
|
||||
}
|
||||
|
||||
# Slack
|
||||
if ($script:NotificationConfig.Slack.Enabled) {
|
||||
Send-SlackNotification -LogEntry $LogEntry
|
||||
}
|
||||
|
||||
# Discord
|
||||
if ($script:NotificationConfig.Discord.Enabled) {
|
||||
Send-DiscordNotification -LogEntry $LogEntry
|
||||
}
|
||||
}
|
||||
|
||||
# Notificação desktop (Windows)
|
||||
function Send-DesktopNotification {
|
||||
param([LogEntry]$LogEntry)
|
||||
|
||||
try {
|
||||
$emoji = $script:LogEmojis[$LogEntry.Level]
|
||||
$title = "RDO-C Auto-Sync $emoji"
|
||||
$message = "[$($LogEntry.Category)] $($LogEntry.Message)"
|
||||
|
||||
# Usar Windows Toast Notification
|
||||
Add-Type -AssemblyName System.Windows.Forms
|
||||
|
||||
$notification = New-Object System.Windows.Forms.NotifyIcon
|
||||
$notification.Icon = [System.Drawing.SystemIcons]::Information
|
||||
$notification.BalloonTipTitle = $title
|
||||
$notification.BalloonTipText = $message
|
||||
$notification.Visible = $true
|
||||
|
||||
$notification.ShowBalloonTip($script:NotificationConfig.Desktop.Duration)
|
||||
|
||||
# Limpar após exibição
|
||||
Start-Sleep -Milliseconds 100
|
||||
$notification.Dispose()
|
||||
|
||||
} catch {
|
||||
Write-Host "⚠️ Falha na notificação desktop: $($_.Exception.Message)" -ForegroundColor Yellow
|
||||
}
|
||||
}
|
||||
|
||||
# Notificação Slack
|
||||
function Send-SlackNotification {
|
||||
param([LogEntry]$LogEntry)
|
||||
|
||||
try {
|
||||
$emoji = $script:LogEmojis[$LogEntry.Level]
|
||||
$color = switch ($LogEntry.Level) {
|
||||
"SUCCESS" { "good" }
|
||||
"WARN" { "warning" }
|
||||
"ERROR" { "danger" }
|
||||
"FATAL" { "danger" }
|
||||
default { "#36a64f" }
|
||||
}
|
||||
|
||||
$payload = @{
|
||||
channel = $script:NotificationConfig.Slack.Channel
|
||||
username = $script:NotificationConfig.Slack.Username
|
||||
icon_emoji = $script:NotificationConfig.Slack.IconEmoji
|
||||
attachments = @(
|
||||
@{
|
||||
color = $color
|
||||
title = "$emoji RDO-C Auto-Sync - $($LogEntry.Level)"
|
||||
text = $LogEntry.Message
|
||||
fields = @(
|
||||
@{
|
||||
title = "Categoria"
|
||||
value = $LogEntry.Category
|
||||
short = $true
|
||||
},
|
||||
@{
|
||||
title = "Timestamp"
|
||||
value = $LogEntry.Timestamp
|
||||
short = $true
|
||||
}
|
||||
)
|
||||
footer = "RDO-C Auto-Sync"
|
||||
ts = [int][double]::Parse((Get-Date -UFormat %s))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
$json = $payload | ConvertTo-Json -Depth 4
|
||||
Invoke-RestMethod -Uri $script:NotificationConfig.Slack.WebhookUrl -Method POST -Body $json -ContentType "application/json"
|
||||
|
||||
} catch {
|
||||
Write-Host "⚠️ Falha na notificação Slack: $($_.Exception.Message)" -ForegroundColor Yellow
|
||||
}
|
||||
}
|
||||
|
||||
# Notificação Discord
|
||||
function Send-DiscordNotification {
|
||||
param([LogEntry]$LogEntry)
|
||||
|
||||
try {
|
||||
$emoji = $script:LogEmojis[$LogEntry.Level]
|
||||
$color = switch ($LogEntry.Level) {
|
||||
"SUCCESS" { 65280 } # Verde
|
||||
"WARN" { 16776960 } # Amarelo
|
||||
"ERROR" { 16711680 } # Vermelho
|
||||
"FATAL" { 8388736 } # Roxo
|
||||
default { 3447003 } # Azul
|
||||
}
|
||||
|
||||
$payload = @{
|
||||
username = $script:NotificationConfig.Discord.Username
|
||||
avatar_url = $script:NotificationConfig.Discord.AvatarUrl
|
||||
embeds = @(
|
||||
@{
|
||||
title = "$emoji RDO-C Auto-Sync - $($LogEntry.Level)"
|
||||
description = $LogEntry.Message
|
||||
color = $color
|
||||
fields = @(
|
||||
@{
|
||||
name = "Categoria"
|
||||
value = $LogEntry.Category
|
||||
inline = $true
|
||||
},
|
||||
@{
|
||||
name = "Fonte"
|
||||
value = $LogEntry.Source
|
||||
inline = $true
|
||||
}
|
||||
)
|
||||
timestamp = (Get-Date).ToString("yyyy-MM-ddTHH:mm:ss.fffZ")
|
||||
footer = @{
|
||||
text = "Sessão: $($LogEntry.SessionId)"
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
$json = $payload | ConvertTo-Json -Depth 4
|
||||
Invoke-RestMethod -Uri $script:NotificationConfig.Discord.WebhookUrl -Method POST -Body $json -ContentType "application/json"
|
||||
|
||||
} catch {
|
||||
Write-Host "⚠️ Falha na notificação Discord: $($_.Exception.Message)" -ForegroundColor Yellow
|
||||
}
|
||||
}
|
||||
|
||||
# Funções de conveniência para diferentes categorias
|
||||
function Write-GitLog {
|
||||
param([string]$Level, [string]$Message, [hashtable]$Context = @{})
|
||||
Write-LogEntry -Level $Level -Message $Message -Category "Git" -Context $Context
|
||||
}
|
||||
|
||||
|
||||
|
||||
function Write-SyncLog {
|
||||
param([string]$Level, [string]$Message, [hashtable]$Context = @{})
|
||||
Write-LogEntry -Level $Level -Message $Message -Category "Sync" -Context $Context
|
||||
}
|
||||
|
||||
function Write-DeployLog {
|
||||
param([string]$Level, [string]$Message, [hashtable]$Context = @{})
|
||||
Write-LogEntry -Level $Level -Message $Message -Category "Deploy" -Context $Context
|
||||
}
|
||||
|
||||
function Write-WatchLog {
|
||||
param([string]$Level, [string]$Message, [hashtable]$Context = @{})
|
||||
Write-LogEntry -Level $Level -Message $Message -Category "FileWatch" -Context $Context
|
||||
}
|
||||
|
||||
# Análise de logs
|
||||
function Get-LogAnalysis {
|
||||
param(
|
||||
[string]$LogFile = "",
|
||||
[int]$LastHours = 24,
|
||||
[string[]]$Levels = @(),
|
||||
[string[]]$Categories = @()
|
||||
)
|
||||
|
||||
if (-not $LogFile) {
|
||||
$date = Get-Date -Format "yyyy-MM-dd"
|
||||
$LogFile = Join-Path $script:LogConfig.LogDirectory "rdo-auto-sync-$date.log"
|
||||
}
|
||||
|
||||
if (-not (Test-Path $LogFile)) {
|
||||
Write-Host "❌ Arquivo de log não encontrado: $LogFile" -ForegroundColor Red
|
||||
return
|
||||
}
|
||||
|
||||
$cutoffTime = (Get-Date).AddHours(-$LastHours)
|
||||
$logs = Get-Content $LogFile | ForEach-Object {
|
||||
if ($_ -match '\[(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\.\d{3})\] \[([A-Z]+)\] \[([^\]]+)\]') {
|
||||
$timestamp = [DateTime]::ParseExact($matches[1], $script:LogConfig.DateFormat, $null)
|
||||
|
||||
if ($timestamp -gt $cutoffTime) {
|
||||
@{
|
||||
Timestamp = $timestamp
|
||||
Level = $matches[2]
|
||||
Category = $matches[3]
|
||||
FullLine = $_
|
||||
}
|
||||
}
|
||||
}
|
||||
} | Where-Object { $_ -ne $null }
|
||||
|
||||
# Filtrar por níveis e categorias
|
||||
if ($Levels.Count -gt 0) {
|
||||
$logs = $logs | Where-Object { $_.Level -in $Levels }
|
||||
}
|
||||
|
||||
if ($Categories.Count -gt 0) {
|
||||
$logs = $logs | Where-Object { $_.Category -in $Categories }
|
||||
}
|
||||
|
||||
# Estatísticas
|
||||
$stats = @{
|
||||
Total = $logs.Count
|
||||
ByLevel = $logs | Group-Object Level | ForEach-Object { @{ $_.Name = $_.Count } }
|
||||
ByCategory = $logs | Group-Object Category | ForEach-Object { @{ $_.Name = $_.Count } }
|
||||
TimeRange = @{
|
||||
Start = ($logs | Sort-Object Timestamp | Select-Object -First 1).Timestamp
|
||||
End = ($logs | Sort-Object Timestamp | Select-Object -Last 1).Timestamp
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "📊 Análise de Logs - Últimas $LastHours horas" -ForegroundColor Cyan
|
||||
Write-Host "============================================" -ForegroundColor Cyan
|
||||
Write-Host "Total de entradas: $($stats.Total)" -ForegroundColor White
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "Por Nível:" -ForegroundColor Yellow
|
||||
$stats.ByLevel | ForEach-Object {
|
||||
$_.GetEnumerator() | ForEach-Object {
|
||||
$emoji = $script:LogEmojis[$_.Key]
|
||||
Write-Host " $emoji $($_.Key): $($_.Value)" -ForegroundColor White
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "Por Categoria:" -ForegroundColor Yellow
|
||||
$stats.ByCategory | ForEach-Object {
|
||||
$_.GetEnumerator() | ForEach-Object {
|
||||
Write-Host " 📁 $($_.Key): $($_.Value)" -ForegroundColor White
|
||||
}
|
||||
}
|
||||
|
||||
return $stats
|
||||
}
|
||||
|
||||
# Exportar funções
|
||||
Export-ModuleMember -Function @(
|
||||
'Initialize-LoggingSystem',
|
||||
'Write-LogEntry',
|
||||
'Write-GitLog',
|
||||
'Write-SyncLog',
|
||||
'Write-DeployLog',
|
||||
'Write-WatchLog',
|
||||
'Get-LogAnalysis',
|
||||
'Send-LogNotification'
|
||||
)
|
||||
|
||||
# Inicializar automaticamente se importado
|
||||
if ($MyInvocation.InvocationName -eq 'Import-Module' -or $MyInvocation.InvocationName -eq '.') {
|
||||
Initialize-LoggingSystem
|
||||
}
|
||||
Reference in New Issue
Block a user