From f063a2ec8c40e1fe6546ab54a9743433bca023ed Mon Sep 17 00:00:00 2001 From: Emanuel Almeida Date: Wed, 28 Oct 2026 16:01:09 +0000 Subject: [PATCH] =?UTF-8?q?feat(monitorizacao):=20scripts=20de=20sa=C3=BAd?= =?UTF-8?q?e=20e=20telemetria=20do=20Gemini=20CLI?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- monitorizacao/gemini-health.sh | 27 ++++++++++++ monitorizacao/gemini-pre-telemetry.py | 30 +++++++++++++ monitorizacao/gemini-telemetry.py | 63 +++++++++++++++++++++++++++ 3 files changed, 120 insertions(+) create mode 100644 monitorizacao/gemini-health.sh create mode 100644 monitorizacao/gemini-pre-telemetry.py create mode 100644 monitorizacao/gemini-telemetry.py diff --git a/monitorizacao/gemini-health.sh b/monitorizacao/gemini-health.sh new file mode 100644 index 0000000..bbbc329 --- /dev/null +++ b/monitorizacao/gemini-health.sh @@ -0,0 +1,27 @@ +#!/bin/bash +# Gemini CLI Health Check - Integração com Observabilidade +# Este script verifica o estado dos servidores MCP e regista métricas. + +LOG_FILE="/media/ealmeida/Dados/Hub/05-Projectos/Observabilidade/logs/gemini/health.log" +TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S') + +echo "[$TIMESTAMP] A iniciar verificação de saúde do Gemini CLI..." >> "$LOG_FILE" + +# Verifica se o gemini está no PATH e responde +if ! command -v gemini &> /dev/null; then + echo "[$TIMESTAMP] ERRO: Gemini CLI não encontrado no PATH." >> "$LOG_FILE" + exit 1 +fi + +# Lista servidores MCP e extrai estado +MCP_STATUS=$(gemini mcp list) + +# Procura por servidores desconectados (marcados com ✗ no gemini mcp list) +if echo "$MCP_STATUS" | grep -q "✗"; then + echo "[$TIMESTAMP] ALERTA: Um ou mais servidores MCP estão desconectados!" >> "$LOG_FILE" + echo "$MCP_STATUS" | grep "✗" >> "$LOG_FILE" +else + echo "[$TIMESTAMP] SUCESSO: Todos os servidores MCP estão conectados." >> "$LOG_FILE" +fi + +echo "------------------------------------------------" >> "$LOG_FILE" diff --git a/monitorizacao/gemini-pre-telemetry.py b/monitorizacao/gemini-pre-telemetry.py new file mode 100644 index 0000000..a0616d3 --- /dev/null +++ b/monitorizacao/gemini-pre-telemetry.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python3 +import sys +import json +import os +import time + +def pre_tool_hook(): + try: + # Lê o input do Gemini + hook_input = json.load(sys.stdin) + session_id = hook_input.get("session_id", "unknown") + + # Cria um ficheiro temporário para guardar o tempo de início desta sessão + # Nota: O Gemini CLI executa ferramentas de forma sequencial ou paralela, + # mas os hooks AfterTool recebem o contexto necessário. + temp_dir = os.path.expanduser("~/.gemini/tmp/telemetry") + os.makedirs(temp_dir, exist_ok=True) + + # Guardamos o tempo de início num ficheiro nomeado pela sessão + # Para maior precisão em chamadas paralelas, o ideal seria um tool_call_id, + # mas o session_id é a base comum. + with open(os.path.join(temp_dir, f"{session_id}.start"), "w") as f: + f.write(str(time.time())) + + print(json.dumps({})) + except Exception: + print(json.dumps({})) + +if __name__ == "__main__": + pre_tool_hook() diff --git a/monitorizacao/gemini-telemetry.py b/monitorizacao/gemini-telemetry.py new file mode 100644 index 0000000..7194f97 --- /dev/null +++ b/monitorizacao/gemini-telemetry.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python3 +import sys +import json +import os +from datetime import datetime + +def log_telemetry(): + try: + # Lê o JSON do stdin enviado pelo Gemini CLI + hook_input = json.load(sys.stdin) + + # Extrai dados básicos + session_id = hook_input.get("session_id", "unknown") + tool_name = hook_input.get("tool_name", "unknown") + timestamp = hook_input.get("timestamp", datetime.now().isoformat()) + + # Determina o sucesso/erro a partir da resposta da ferramenta + tool_response = hook_input.get("tool_response", {}) + error = tool_response.get("error", "") + + # No formato do Gemini, o sucesso pode ser inferido pela ausência de erro + # ou por flags específicas em certas ferramentas. + success = "true" if not error else "false" + + # Prepara o log no formato compatível com o Claude (mas com engine: gemini) + # O ficheiro de destino é o mesmo usado pelo Claude para métricas unificadas + log_path = os.path.expanduser("~/.claude-work/tool-invocations.jsonl") + + # Tenta calcular a duração + duration = 0 + start_file = os.path.expanduser(f"~/.gemini/tmp/telemetry/{session_id}.start") + if os.path.exists(start_file): + with open(start_file, "r") as f: + start_time = float(f.read()) + duration = round(time.time() - start_time, 2) + + log_entry = { + "ts": timestamp, + "session": session_id, + "tool_name": tool_name, + "success": success == "true", + "error": error, + "duration": duration, # Nova métrica de tempo + "engine": "gemini" + } + + # Garante que a pasta existe + os.makedirs(os.path.dirname(log_path), exist_ok=True) + + # Escreve no log central + with open(log_path, "a") as f: + f.write(json.dumps(log_entry) + "\n") + + # O Gemini exige um JSON válido no stdout para hooks de AfterTool + print(json.dumps({})) + + except Exception as e: + # Erros vão para o stderr para não quebrar o CLI + sys.stderr.write(f"Erro no Gemini Telemetry Hook: {str(e)}\n") + print(json.dumps({})) + +if __name__ == "__main__": + log_telemetry()