575 lines
18 KiB
PowerShell
575 lines
18 KiB
PowerShell
# 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
|
||
} |