teste / src /ui /log_viewer.py
torxyton's picture
feat: Implementar sistema de logging avançado com SQLite3
b94122a
import gradio as gr
import pandas as pd
import plotly.express as px
import plotly.graph_objects as go
from datetime import datetime, timedelta
from typing import List, Dict, Any, Tuple
import json
from ..core.database_logger import get_logger, LogLevel, LogCategory
class LogViewer:
"""Interface para visualização de logs"""
def __init__(self):
self.logger = get_logger()
def get_log_data(self, level_filter: str = "ALL", category_filter: str = "ALL",
hours_back: int = 24, limit: int = 1000) -> pd.DataFrame:
"""Recupera dados de log filtrados"""
# Calcular tempo de início
start_time = (datetime.now() - timedelta(hours=hours_back)).isoformat()
# Aplicar filtros
level = None if level_filter == "ALL" else level_filter
category = None if category_filter == "ALL" else category_filter
# Buscar logs
logs = self.logger.get_logs(
level=level,
category=category,
start_time=start_time,
limit=limit
)
if not logs:
return pd.DataFrame()
# Converter para DataFrame
df = pd.DataFrame(logs)
df['timestamp'] = pd.to_datetime(df['timestamp'])
return df
def create_log_table(self, level_filter: str, category_filter: str,
hours_back: int, limit: int) -> str:
"""Cria tabela HTML com os logs"""
df = self.get_log_data(level_filter, category_filter, hours_back, limit)
if df.empty:
return "<p>Nenhum log encontrado com os filtros aplicados.</p>"
# Preparar dados para exibição
display_df = df[['timestamp', 'level', 'category', 'message', 'module', 'function']].copy()
display_df['timestamp'] = display_df['timestamp'].dt.strftime('%Y-%m-%d %H:%M:%S')
# Aplicar cores baseadas no nível
def style_level(level):
colors = {
'DEBUG': '#6c757d',
'INFO': '#17a2b8',
'WARNING': '#ffc107',
'ERROR': '#dc3545',
'CRITICAL': '#6f42c1'
}
return f'<span style="color: {colors.get(level, "#000")}; font-weight: bold;">{level}</span>'
display_df['level'] = display_df['level'].apply(style_level)
# Converter para HTML
html_table = display_df.to_html(escape=False, index=False, classes='table table-striped')
return f"""
<div style="max-height: 600px; overflow-y: auto;">
{html_table}
</div>
"""
def create_log_statistics(self, hours_back: int) -> str:
"""Cria estatísticas dos logs"""
stats = self.logger.get_statistics()
# Logs das últimas N horas
start_time = (datetime.now() - timedelta(hours=hours_back)).isoformat()
recent_logs = self.logger.get_logs(start_time=start_time, limit=10000)
recent_df = pd.DataFrame(recent_logs) if recent_logs else pd.DataFrame()
html = f"""
<div class="row">
<div class="col-md-6">
<h4>📊 Estatísticas Gerais</h4>
<ul>
<li><strong>Total de Logs:</strong> {stats['total_logs']:,}</li>
<li><strong>Logs (últimas 24h):</strong> {stats['logs_last_24h']:,}</li>
<li><strong>Logs (últimas {hours_back}h):</strong> {len(recent_df):,}</li>
</ul>
</div>
<div class="col-md-6">
<h4>📈 Distribuição por Nível</h4>
<ul>
"""
for level, count in stats['logs_by_level'].items():
html += f"<li><strong>{level}:</strong> {count:,}</li>"
html += """
</ul>
</div>
</div>
<div class="row mt-3">
<div class="col-12">
<h4>🏷️ Distribuição por Categoria</h4>
<ul>
"""
for category, count in stats['logs_by_category'].items():
html += f"<li><strong>{category}:</strong> {count:,}</li>"
html += """
</ul>
</div>
</div>
"""
return html
def create_performance_chart(self, hours_back: int) -> go.Figure:
"""Cria gráfico de métricas de performance"""
start_time = (datetime.now() - timedelta(hours=hours_back)).isoformat()
metrics = self.logger.get_performance_metrics(start_time=start_time, limit=1000)
if not metrics:
fig = go.Figure()
fig.add_annotation(
text="Nenhuma métrica de performance encontrada",
xref="paper", yref="paper",
x=0.5, y=0.5, showarrow=False
)
return fig
df = pd.DataFrame(metrics)
df['timestamp'] = pd.to_datetime(df['timestamp'])
# Agrupar por nome da métrica
fig = go.Figure()
for metric_name in df['metric_name'].unique():
metric_data = df[df['metric_name'] == metric_name]
fig.add_trace(go.Scatter(
x=metric_data['timestamp'],
y=metric_data['metric_value'],
mode='lines+markers',
name=metric_name,
hovertemplate='<b>%{fullData.name}</b><br>' +
'Tempo: %{x}<br>' +
'Valor: %{y}<br>' +
'<extra></extra>'
))
fig.update_layout(
title="Métricas de Performance ao Longo do Tempo",
xaxis_title="Timestamp",
yaxis_title="Valor",
hovermode='closest'
)
return fig
def create_log_timeline_chart(self, hours_back: int) -> go.Figure:
"""Cria gráfico de timeline dos logs"""
start_time = (datetime.now() - timedelta(hours=hours_back)).isoformat()
logs = self.logger.get_logs(start_time=start_time, limit=5000)
if not logs:
fig = go.Figure()
fig.add_annotation(
text="Nenhum log encontrado no período",
xref="paper", yref="paper",
x=0.5, y=0.5, showarrow=False
)
return fig
df = pd.DataFrame(logs)
df['timestamp'] = pd.to_datetime(df['timestamp'])
# Contar logs por hora e nível
df['hour'] = df['timestamp'].dt.floor('H')
log_counts = df.groupby(['hour', 'level']).size().reset_index(name='count')
fig = go.Figure()
colors = {
'DEBUG': '#6c757d',
'INFO': '#17a2b8',
'WARNING': '#ffc107',
'ERROR': '#dc3545',
'CRITICAL': '#6f42c1'
}
for level in log_counts['level'].unique():
level_data = log_counts[log_counts['level'] == level]
fig.add_trace(go.Bar(
x=level_data['hour'],
y=level_data['count'],
name=level,
marker_color=colors.get(level, '#000000')
))
fig.update_layout(
title="Distribuição de Logs por Hora e Nível",
xaxis_title="Hora",
yaxis_title="Quantidade de Logs",
barmode='stack'
)
return fig
def search_logs(self, search_term: str, hours_back: int) -> str:
"""Busca logs por termo específico"""
start_time = (datetime.now() - timedelta(hours=hours_back)).isoformat()
all_logs = self.logger.get_logs(start_time=start_time, limit=10000)
if not all_logs:
return "<p>Nenhum log encontrado.</p>"
# Filtrar logs que contêm o termo de busca
filtered_logs = [
log for log in all_logs
if search_term.lower() in log['message'].lower() or
search_term.lower() in log['module'].lower() or
search_term.lower() in log['function'].lower()
]
if not filtered_logs:
return f"<p>Nenhum log encontrado com o termo '{search_term}'.</p>"
df = pd.DataFrame(filtered_logs)
df['timestamp'] = pd.to_datetime(df['timestamp'])
# Preparar dados para exibição
display_df = df[['timestamp', 'level', 'category', 'message', 'module', 'function']].copy()
display_df['timestamp'] = display_df['timestamp'].dt.strftime('%Y-%m-%d %H:%M:%S')
# Destacar termo de busca
def highlight_term(text, term):
if pd.isna(text):
return text
return str(text).replace(term, f'<mark>{term}</mark>')
for col in ['message', 'module', 'function']:
display_df[col] = display_df[col].apply(lambda x: highlight_term(x, search_term))
html_table = display_df.to_html(escape=False, index=False, classes='table table-striped')
return f"""
<div style="max-height: 600px; overflow-y: auto;">
<p><strong>Encontrados {len(filtered_logs)} logs com o termo '{search_term}'</strong></p>
{html_table}
</div>
"""
def export_logs(self, level_filter: str, category_filter: str,
hours_back: int, format_type: str) -> str:
"""Exporta logs em diferentes formatos"""
df = self.get_log_data(level_filter, category_filter, hours_back, 10000)
if df.empty:
return "Nenhum log para exportar."
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
if format_type == "CSV":
filename = f"logs_export_{timestamp}.csv"
df.to_csv(filename, index=False)
return f"Logs exportados para {filename}"
elif format_type == "JSON":
filename = f"logs_export_{timestamp}.json"
df.to_json(filename, orient='records', date_format='iso')
return f"Logs exportados para {filename}"
elif format_type == "HTML":
filename = f"logs_export_{timestamp}.html"
html_content = f"""
<!DOCTYPE html>
<html>
<head>
<title>Relatório de Logs - {timestamp}</title>
<style>
body {{ font-family: Arial, sans-serif; margin: 20px; }}
table {{ border-collapse: collapse; width: 100%; }}
th, td {{ border: 1px solid #ddd; padding: 8px; text-align: left; }}
th {{ background-color: #f2f2f2; }}
.debug {{ color: #6c757d; }}
.info {{ color: #17a2b8; }}
.warning {{ color: #ffc107; }}
.error {{ color: #dc3545; }}
.critical {{ color: #6f42c1; }}
</style>
</head>
<body>
<h1>Relatório de Logs</h1>
<p>Gerado em: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}</p>
<p>Total de registros: {len(df)}</p>
{df.to_html(escape=False, index=False, classes='table')}
</body>
</html>
"""
with open(filename, 'w', encoding='utf-8') as f:
f.write(html_content)
return f"Relatório HTML exportado para {filename}"
return "Formato não suportado."
def create_log_viewer_interface() -> gr.Blocks:
"""Cria a interface do visualizador de logs"""
viewer = LogViewer()
with gr.Blocks(title="📊 Visualizador de Logs") as interface:
gr.Markdown("# 📊 Sistema de Visualização de Logs")
with gr.Tab("📋 Logs Recentes"):
with gr.Row():
with gr.Column(scale=1):
level_filter = gr.Dropdown(
choices=["ALL"] + [level.value for level in LogLevel],
value="ALL",
label="Filtrar por Nível"
)
category_filter = gr.Dropdown(
choices=["ALL"] + [cat.value for cat in LogCategory],
value="ALL",
label="Filtrar por Categoria"
)
hours_back = gr.Slider(
minimum=1, maximum=168, value=24, step=1,
label="Horas Anteriores"
)
limit = gr.Slider(
minimum=10, maximum=5000, value=100, step=10,
label="Limite de Registros"
)
refresh_btn = gr.Button("🔄 Atualizar", variant="primary")
with gr.Column(scale=3):
log_table = gr.HTML(label="Logs")
refresh_btn.click(
fn=viewer.create_log_table,
inputs=[level_filter, category_filter, hours_back, limit],
outputs=log_table
)
with gr.Tab("📊 Estatísticas"):
with gr.Row():
stats_hours = gr.Slider(
minimum=1, maximum=168, value=24, step=1,
label="Período (horas)"
)
stats_refresh_btn = gr.Button("🔄 Atualizar Estatísticas", variant="primary")
stats_html = gr.HTML(label="Estatísticas")
stats_refresh_btn.click(
fn=viewer.create_log_statistics,
inputs=[stats_hours],
outputs=stats_html
)
with gr.Tab("📈 Gráficos"):
with gr.Row():
chart_hours = gr.Slider(
minimum=1, maximum=168, value=24, step=1,
label="Período (horas)"
)
chart_refresh_btn = gr.Button("🔄 Atualizar Gráficos", variant="primary")
with gr.Row():
timeline_chart = gr.Plot(label="Timeline de Logs")
performance_chart = gr.Plot(label="Métricas de Performance")
chart_refresh_btn.click(
fn=lambda hours: (viewer.create_log_timeline_chart(hours), viewer.create_performance_chart(hours)),
inputs=[chart_hours],
outputs=[timeline_chart, performance_chart]
)
with gr.Tab("🔍 Busca"):
with gr.Row():
search_term = gr.Textbox(label="Termo de Busca", placeholder="Digite o termo para buscar...")
search_hours = gr.Slider(
minimum=1, maximum=168, value=24, step=1,
label="Período (horas)"
)
search_btn = gr.Button("🔍 Buscar", variant="primary")
search_results = gr.HTML(label="Resultados da Busca")
search_btn.click(
fn=viewer.search_logs,
inputs=[search_term, search_hours],
outputs=search_results
)
with gr.Tab("📤 Exportar"):
with gr.Row():
export_level = gr.Dropdown(
choices=["ALL"] + [level.value for level in LogLevel],
value="ALL",
label="Filtrar por Nível"
)
export_category = gr.Dropdown(
choices=["ALL"] + [cat.value for cat in LogCategory],
value="ALL",
label="Filtrar por Categoria"
)
export_hours = gr.Slider(
minimum=1, maximum=168, value=24, step=1,
label="Período (horas)"
)
export_format = gr.Dropdown(
choices=["CSV", "JSON", "HTML"],
value="CSV",
label="Formato"
)
export_btn = gr.Button("📤 Exportar", variant="primary")
export_result = gr.Textbox(label="Resultado da Exportação")
export_btn.click(
fn=viewer.export_logs,
inputs=[export_level, export_category, export_hours, export_format],
outputs=export_result
)
# Carregar dados iniciais
interface.load(
fn=lambda: (viewer.create_log_table("ALL", "ALL", 24, 100), viewer.create_log_statistics(24)),
outputs=[log_table, stats_html]
)
return interface