description 17. Logs e Auditoria - Rastreamento de Ações Críticas
Logs são essenciais para debug, segurança e compliance. Quando algo dá errado (e sempre dá), logs são a única forma de descobrir o que aconteceu. Nesta seção, você vai implementar um sistema completo de logs e auditoria para rastrear ações críticas dos usuários.
info
LGPD/GDPR: Logs de auditoria são obrigatórios para compliance. Você precisa provar
quem acessou dados pessoais e quando.
storage Estrutura de Banco de Dados
-- Tabela de Logs de Auditoria
CREATE TABLE audit_log (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
user_id INT NULL,
ip_address VARCHAR(45) NOT NULL,
user_agent TEXT,
action VARCHAR(100) NOT NULL COMMENT 'login, logout, purchase, data_access, etc',
resource_type VARCHAR(50) COMMENT 'user, course, order',
resource_id INT NULL,
details JSON COMMENT 'Dados adicionais da ação',
severity ENUM('info', 'warning', 'error', 'critical') DEFAULT 'info',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_user_id (user_id),
INDEX idx_action (action),
INDEX idx_created_at (created_at),
INDEX idx_severity (severity)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Tabela de Erros
CREATE TABLE error_log (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
user_id INT NULL,
endpoint VARCHAR(255) NOT NULL,
error_type VARCHAR(100),
error_message TEXT NOT NULL,
stack_trace TEXT,
request_data JSON,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_created_at (created_at),
INDEX idx_endpoint (endpoint)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
code Backend PHP - Logger Service
// services/Logger.php
db = $db;
}
/**
* Registrar ação de auditoria
*/
public function audit(
string $action,
?int $userId = null,
?string $resourceType = null,
?int $resourceId = null,
array $details = [],
string $severity = 'info'
): void {
$sql = "
INSERT INTO audit_log (
user_id, ip_address, user_agent,
action, resource_type, resource_id,
details, severity
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
";
$stmt = $this->db->prepare($sql);
$stmt->execute([
$userId,
$this->getIpAddress(),
$_SERVER['HTTP_USER_AGENT'] ?? 'Unknown',
$action,
$resourceType,
$resourceId,
json_encode($details),
$severity
]);
}
/**
* Registrar erro
*/
public function error(
string $message,
?string $errorType = null,
?string $stackTrace = null,
?int $userId = null,
array $requestData = []
): void {
$sql = "
INSERT INTO error_log (
user_id, endpoint, error_type,
error_message, stack_trace, request_data
) VALUES (?, ?, ?, ?, ?, ?)
";
$stmt = $this->db->prepare($sql);
$stmt->execute([
$userId,
$_SERVER['REQUEST_URI'] ?? 'Unknown',
$errorType,
$message,
$stackTrace,
json_encode($requestData)
]);
}
/**
* Buscar logs de um usuário
*/
public function getUserAudit(int $userId, int $limit = 50): array {
$sql = "
SELECT *
FROM audit_log
WHERE user_id = ?
ORDER BY created_at DESC
LIMIT ?
";
$stmt = $this->db->prepare($sql);
$stmt->execute([$userId, $limit]);
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
/**
* Buscar logs críticos recentes
*/
public function getCriticalLogs(int $hours = 24): array {
$sql = "
SELECT *
FROM audit_log
WHERE severity = 'critical'
AND created_at >= DATE_SUB(NOW(), INTERVAL ? HOUR)
ORDER BY created_at DESC
";
$stmt = $this->db->prepare($sql);
$stmt->execute([$hours]);
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
/**
* Obter IP do usuário (considerando proxies)
*/
private function getIpAddress(): string {
$headers = [
'HTTP_CF_CONNECTING_IP', // Cloudflare
'HTTP_X_FORWARDED_FOR',
'HTTP_X_REAL_IP',
'REMOTE_ADDR'
];
foreach ($headers as $header) {
if (isset($_SERVER[$header])) {
$ip = $_SERVER[$header];
// X-Forwarded-For pode ter múltiplos IPs
if (str_contains($ip, ',')) {
$ip = trim(explode(',', $ip)[0]);
}
if (filter_var($ip, FILTER_VALIDATE_IP)) {
return $ip;
}
}
}
return 'Unknown';
}
}
// ===== EXEMPLOS DE USO =====
// Login bem-sucedido
$logger->audit(
action: 'user_login',
userId: $user['id'],
severity: 'info'
);
// Compra realizada
$logger->audit(
action: 'course_purchased',
userId: $userId,
resourceType: 'course',
resourceId: $courseId,
details: [
'amount' => $amount,
'payment_method' => 'pix'
],
severity: 'info'
);
// Tentativa de acesso não autorizado
$logger->audit(
action: 'unauthorized_access_attempt',
userId: $userId,
resourceType: 'course',
resourceId: $courseId,
severity: 'warning'
);
// Mudança de permissões
$logger->audit(
action: 'role_changed',
userId: $adminId,
resourceType: 'user',
resourceId: $targetUserId,
details: [
'old_role' => 'user',
'new_role' => 'moderator',
'changed_by' => $adminId
],
severity: 'critical'
);
api API de Consulta de Logs
// api/admin/logs.php
hasRole($_SESSION['user_id'], 'admin')) {
http_response_code(403);
echo json_encode(['error' => 'Acesso negado']);
exit;
}
$db = getDBConnection();
$logger = new App\Services\Logger($db);
$action = $_GET['action'] ?? 'all';
$userId = $_GET['user_id'] ?? null;
$severity = $_GET['severity'] ?? null;
$limit = min((int)($_GET['limit'] ?? 100), 1000);
try {
$conditions = [];
$params = [];
if ($userId) {
$conditions[] = "user_id = ?";
$params[] = $userId;
}
if ($severity) {
$conditions[] = "severity = ?";
$params[] = $severity;
}
if ($action !== 'all') {
$conditions[] = "action = ?";
$params[] = $action;
}
$where = !empty($conditions) ? "WHERE " . implode(' AND ', $conditions) : '';
$sql = "
SELECT
al.*,
u.name as user_name,
u.email as user_email
FROM audit_log al
LEFT JOIN users u ON al.user_id = u.id
{$where}
ORDER BY al.created_at DESC
LIMIT ?
";
$params[] = $limit;
$stmt = $db->prepare($sql);
$stmt->execute($params);
$logs = $stmt->fetchAll(PDO::FETCH_ASSOC);
echo json_encode([
'success' => true,
'logs' => $logs,
'total' => count($logs)
]);
} catch (Exception $e) {
error_log("Erro ao buscar logs: " . $e->getMessage());
http_response_code(500);
echo json_encode(['error' => 'Erro ao buscar logs']);
}
monitor Monitoramento em Tempo Real
// Frontend React - Painel de Logs (Admin)
import { useState, useEffect } from 'react';
import axios from 'axios';
const API_URL = import.meta.env.VITE_API_URL;
export const LogsPanel = () => {
const [logs, setLogs] = useState([]);
const [loading, setLoading] = useState(true);
const [filter, setFilter] = useState({
severity: 'all',
action: 'all',
limit: 100
});
useEffect(() => {
fetchLogs();
// Auto-refresh a cada 30 segundos
const interval = setInterval(fetchLogs, 30000);
return () => clearInterval(interval);
}, [filter]);
const fetchLogs = async () => {
try {
const params = new URLSearchParams();
if (filter.severity !== 'all') params.append('severity', filter.severity);
if (filter.action !== 'all') params.append('action', filter.action);
params.append('limit', filter.limit);
const response = await axios.get(
`${API_URL}/admin/logs?${params}`,
{ withCredentials: true }
);
setLogs(response.data.logs);
} catch (error) {
console.error('Erro ao buscar logs:', error);
} finally {
setLoading(false);
}
};
const getSeverityColor = (severity) => {
const colors = {
info: 'blue',
warning: 'orange',
error: 'red',
critical: 'purple'
};
return colors[severity] || 'gray';
};
return (
Logs de Auditoria
{loading ? (
Carregando logs...
) : (
Data/Hora
Usuário
Ação
Detalhes
Severidade
IP
{logs.map(log => (
{new Date(log.created_at).toLocaleString()}
{log.user_name || 'Sistema'}
{log.user_email}
{log.action}
{log.details && (
{JSON.stringify(JSON.parse(log.details), null, 2)}
)}
{log.severity}
{log.ip_address}
))}
)}
);
};
check_circle O que Logar?
✅ Sempre Logue
- Login/Logout
- Compras e transações
- Mudanças de permissões
- Acesso a dados sensíveis
- Tentativas de acesso não autorizado
- Erros críticos
- Mudanças em configurações
❌ Nunca Logue
- Senhas (nem hasheadas)
- Tokens de autenticação
- Dados de cartão de crédito
- Informações muito sensíveis (CPF, etc)
- Dados desnecessários
rocket_launch
Próximo Passo: Logs implementados! Agora vamos otimizar performance com cache
para aplicação ficar ainda mais rápida.