Añadir log a archivo
- Opción --log/-l para guardar a archivo (default: service-monitor.log) - Ruta custom opcional: --log archivo.log - Timestamp completo ISO: [YYYY-MM-DD HH:MM:SS] - Log sin colores ANSI (limpio para archivo) - Modo append (no sobreescribe logs anteriores) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
3946f83920
commit
dfcfd31ec3
2 changed files with 78 additions and 7 deletions
7
service-monitor.log
Normal file
7
service-monitor.log
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
[2025-12-07 20:00:34]
|
||||||
|
OK Forgejo (HTTP) (387ms)
|
||||||
|
OK Forgejo (SSH) (57ms)
|
||||||
|
OK Simifactu API (363ms)
|
||||||
|
OK Mundisofa (363ms)
|
||||||
|
OK Menzuri (363ms)
|
||||||
|
|
||||||
78
src/main.zig
78
src/main.zig
|
|
@ -8,6 +8,7 @@
|
||||||
//! zig build run - Verificar todos los servicios una vez
|
//! zig build run - Verificar todos los servicios una vez
|
||||||
//! zig build run -- --watch - Modo continuo (cada 60s por defecto)
|
//! zig build run -- --watch - Modo continuo (cada 60s por defecto)
|
||||||
//! zig build run -- --watch -i 30 - Modo continuo cada 30 segundos
|
//! zig build run -- --watch -i 30 - Modo continuo cada 30 segundos
|
||||||
|
//! zig build run -- --log - Guardar log a archivo
|
||||||
//! zig build run -- --help - Mostrar ayuda
|
//! zig build run -- --help - Mostrar ayuda
|
||||||
|
|
||||||
const std = @import("std");
|
const std = @import("std");
|
||||||
|
|
@ -15,12 +16,19 @@ const http = @import("http.zig");
|
||||||
const tcp = @import("tcp.zig");
|
const tcp = @import("tcp.zig");
|
||||||
const config = @import("config.zig");
|
const config = @import("config.zig");
|
||||||
|
|
||||||
|
/// Nombre del archivo de log por defecto.
|
||||||
|
const DEFAULT_LOG_FILE = "service-monitor.log";
|
||||||
|
|
||||||
/// Opciones de línea de comandos.
|
/// Opciones de línea de comandos.
|
||||||
const Options = struct {
|
const Options = struct {
|
||||||
/// Modo watch: ejecutar continuamente.
|
/// Modo watch: ejecutar continuamente.
|
||||||
watch: bool = false,
|
watch: bool = false,
|
||||||
/// Intervalo entre checks en segundos (solo en modo watch).
|
/// Intervalo entre checks en segundos (solo en modo watch).
|
||||||
interval_seconds: u32 = 60,
|
interval_seconds: u32 = 60,
|
||||||
|
/// Guardar log a archivo.
|
||||||
|
log_to_file: bool = false,
|
||||||
|
/// Ruta del archivo de log.
|
||||||
|
log_file: []const u8 = DEFAULT_LOG_FILE,
|
||||||
/// Mostrar ayuda y salir.
|
/// Mostrar ayuda y salir.
|
||||||
help: bool = false,
|
help: bool = false,
|
||||||
};
|
};
|
||||||
|
|
@ -44,13 +52,29 @@ pub fn main() !void {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Abrir archivo de log si está habilitado
|
||||||
|
var log_file: ?std.fs.File = null;
|
||||||
|
defer if (log_file) |f| f.close();
|
||||||
|
|
||||||
|
if (options.log_to_file) {
|
||||||
|
log_file = std.fs.cwd().createFile(options.log_file, .{
|
||||||
|
.truncate = false,
|
||||||
|
}) catch |err| {
|
||||||
|
try stdout.print("Error abriendo archivo de log '{s}': {}\n", .{ options.log_file, err });
|
||||||
|
std.process.exit(1);
|
||||||
|
};
|
||||||
|
// Posicionar al final para append
|
||||||
|
log_file.?.seekFromEnd(0) catch {};
|
||||||
|
try stdout.print("Log: {s}\n", .{options.log_file});
|
||||||
|
}
|
||||||
|
|
||||||
if (options.watch) {
|
if (options.watch) {
|
||||||
// Modo watch: loop infinito
|
// Modo watch: loop infinito
|
||||||
try stdout.print("\n=== Service Monitor (watch mode, interval: {d}s) ===\n", .{options.interval_seconds});
|
try stdout.print("\n=== Service Monitor (watch mode, interval: {d}s) ===\n", .{options.interval_seconds});
|
||||||
try stdout.print("Presiona Ctrl+C para salir\n\n", .{});
|
try stdout.print("Presiona Ctrl+C para salir\n\n", .{});
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
const had_errors = try runChecks(allocator, stdout);
|
const had_errors = try runChecks(allocator, stdout, log_file);
|
||||||
_ = had_errors; // En modo watch no salimos por errores
|
_ = had_errors; // En modo watch no salimos por errores
|
||||||
|
|
||||||
std.time.sleep(@as(u64, options.interval_seconds) * std.time.ns_per_s);
|
std.time.sleep(@as(u64, options.interval_seconds) * std.time.ns_per_s);
|
||||||
|
|
@ -58,7 +82,7 @@ pub fn main() !void {
|
||||||
} else {
|
} else {
|
||||||
// Modo único
|
// Modo único
|
||||||
try stdout.print("\n=== Service Monitor ===\n\n", .{});
|
try stdout.print("\n=== Service Monitor ===\n\n", .{});
|
||||||
const had_errors = try runChecks(allocator, stdout);
|
const had_errors = try runChecks(allocator, stdout, log_file);
|
||||||
|
|
||||||
if (had_errors) {
|
if (had_errors) {
|
||||||
std.process.exit(1);
|
std.process.exit(1);
|
||||||
|
|
@ -68,8 +92,9 @@ pub fn main() !void {
|
||||||
|
|
||||||
/// Ejecuta verificación de todos los servicios.
|
/// Ejecuta verificación de todos los servicios.
|
||||||
///
|
///
|
||||||
|
/// Escribe resultados a stdout y opcionalmente a archivo de log.
|
||||||
/// Retorna true si hubo algún error, false si todos OK.
|
/// Retorna true si hubo algún error, false si todos OK.
|
||||||
fn runChecks(allocator: std.mem.Allocator, stdout: anytype) !bool {
|
fn runChecks(allocator: std.mem.Allocator, stdout: anytype, log_file: ?std.fs.File) !bool {
|
||||||
const services = config.getServices();
|
const services = config.getServices();
|
||||||
var had_errors = false;
|
var had_errors = false;
|
||||||
|
|
||||||
|
|
@ -77,12 +102,30 @@ fn runChecks(allocator: std.mem.Allocator, stdout: anytype) !bool {
|
||||||
const timestamp = std.time.timestamp();
|
const timestamp = std.time.timestamp();
|
||||||
const epoch_seconds: u64 = @intCast(timestamp);
|
const epoch_seconds: u64 = @intCast(timestamp);
|
||||||
const epoch = std.time.epoch.EpochSeconds{ .secs = epoch_seconds };
|
const epoch = std.time.epoch.EpochSeconds{ .secs = epoch_seconds };
|
||||||
|
const year_day = epoch.getEpochDay().calculateYearDay();
|
||||||
|
const month_day = year_day.calculateMonthDay();
|
||||||
const day_seconds = epoch.getDaySeconds();
|
const day_seconds = epoch.getDaySeconds();
|
||||||
const hours = day_seconds.getHoursIntoDay();
|
const hours = day_seconds.getHoursIntoDay();
|
||||||
const minutes = day_seconds.getMinutesIntoHour();
|
const minutes = day_seconds.getMinutesIntoHour();
|
||||||
const seconds = day_seconds.getSecondsIntoMinute();
|
const seconds = day_seconds.getSecondsIntoMinute();
|
||||||
|
|
||||||
try stdout.print("[{d:0>2}:{d:0>2}:{d:0>2}]\n", .{ hours, minutes, seconds });
|
const timestamp_str = "[{d:0>4}-{d:0>2}-{d:0>2} {d:0>2}:{d:0>2}:{d:0>2}]";
|
||||||
|
const timestamp_args = .{
|
||||||
|
year_day.year,
|
||||||
|
@intFromEnum(month_day.month),
|
||||||
|
month_day.day_index + 1,
|
||||||
|
hours,
|
||||||
|
minutes,
|
||||||
|
seconds,
|
||||||
|
};
|
||||||
|
|
||||||
|
try stdout.print(timestamp_str ++ "\n", timestamp_args);
|
||||||
|
|
||||||
|
// Log writer opcional
|
||||||
|
const log_writer = if (log_file) |f| f.writer() else null;
|
||||||
|
if (log_writer) |lw| {
|
||||||
|
try lw.print(timestamp_str ++ "\n", timestamp_args);
|
||||||
|
}
|
||||||
|
|
||||||
for (services) |service| {
|
for (services) |service| {
|
||||||
const result = switch (service.check_type) {
|
const result = switch (service.check_type) {
|
||||||
|
|
@ -92,13 +135,23 @@ fn runChecks(allocator: std.mem.Allocator, stdout: anytype) !bool {
|
||||||
|
|
||||||
if (result) |time_ms| {
|
if (result) |time_ms| {
|
||||||
try stdout.print("\x1b[32m✓\x1b[0m {s} - OK ({d}ms)\n", .{ service.name, time_ms });
|
try stdout.print("\x1b[32m✓\x1b[0m {s} - OK ({d}ms)\n", .{ service.name, time_ms });
|
||||||
|
if (log_writer) |lw| {
|
||||||
|
try lw.print("OK {s} ({d}ms)\n", .{ service.name, time_ms });
|
||||||
|
}
|
||||||
} else |err| {
|
} else |err| {
|
||||||
had_errors = true;
|
had_errors = true;
|
||||||
try stdout.print("\x1b[31m✗\x1b[0m {s} - ERROR: {}\n", .{ service.name, err });
|
try stdout.print("\x1b[31m✗\x1b[0m {s} - ERROR: {}\n", .{ service.name, err });
|
||||||
|
if (log_writer) |lw| {
|
||||||
|
try lw.print("ERROR {s} - {}\n", .{ service.name, err });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try stdout.print("\n", .{});
|
try stdout.print("\n", .{});
|
||||||
|
if (log_writer) |lw| {
|
||||||
|
try lw.print("\n", .{});
|
||||||
|
}
|
||||||
|
|
||||||
return had_errors;
|
return had_errors;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -117,6 +170,14 @@ fn parseArgs() !Options {
|
||||||
options.interval_seconds = std.fmt.parseInt(u32, interval_str, 10) catch {
|
options.interval_seconds = std.fmt.parseInt(u32, interval_str, 10) catch {
|
||||||
return error.InvalidIntervalValue;
|
return error.InvalidIntervalValue;
|
||||||
};
|
};
|
||||||
|
} else if (std.mem.eql(u8, arg, "--log") or std.mem.eql(u8, arg, "-l")) {
|
||||||
|
options.log_to_file = true;
|
||||||
|
// Comprobar si el siguiente arg es una ruta (no empieza con -)
|
||||||
|
if (args.next()) |next_arg| {
|
||||||
|
if (next_arg.len > 0 and next_arg[0] != '-') {
|
||||||
|
options.log_file = next_arg;
|
||||||
|
}
|
||||||
|
}
|
||||||
} else if (std.mem.eql(u8, arg, "--help") or std.mem.eql(u8, arg, "-h")) {
|
} else if (std.mem.eql(u8, arg, "--help") or std.mem.eql(u8, arg, "-h")) {
|
||||||
options.help = true;
|
options.help = true;
|
||||||
}
|
}
|
||||||
|
|
@ -137,12 +198,15 @@ fn printUsage(stdout: anytype) !void {
|
||||||
\\OPCIONES:
|
\\OPCIONES:
|
||||||
\\ --watch, -w Modo continuo (ejecuta checks en loop)
|
\\ --watch, -w Modo continuo (ejecuta checks en loop)
|
||||||
\\ --interval, -i <N> Intervalo en segundos entre checks (default: 60)
|
\\ --interval, -i <N> Intervalo en segundos entre checks (default: 60)
|
||||||
|
\\ --log, -l [archivo] Guardar log a archivo (default: service-monitor.log)
|
||||||
\\ --help, -h Muestra esta ayuda
|
\\ --help, -h Muestra esta ayuda
|
||||||
\\
|
\\
|
||||||
\\EJEMPLOS:
|
\\EJEMPLOS:
|
||||||
\\ service-monitor Verificar una vez
|
\\ service-monitor Verificar una vez
|
||||||
\\ service-monitor --watch Verificar cada 60 segundos
|
\\ service-monitor --watch Verificar cada 60 segundos
|
||||||
\\ service-monitor -w -i 30 Verificar cada 30 segundos
|
\\ service-monitor -w -i 30 Verificar cada 30 segundos
|
||||||
|
\\ service-monitor --log Guardar a service-monitor.log
|
||||||
|
\\ service-monitor -w -l monitor.log Watch + log a archivo custom
|
||||||
\\
|
\\
|
||||||
\\
|
\\
|
||||||
, .{});
|
, .{});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue