Compare commits

..

2 Commits

17 changed files with 466 additions and 32 deletions

25
Docker/Dev/config.alloy Normal file
View File

@ -0,0 +1,25 @@
logging {
level = "info"
format = "logfmt"
}
// Descubrir archivos de log
local.file_match "laravel_logs" {
path_targets = [
{ __path__ = "/var/log/repuve/padron-estatal.log", job = "padron_estatal", env = "dev" },
{ __path__ = "/var/log/repuve/repuve-nacional.log", job = "repuve_nacional", env = "dev" },
]
}
// Leer los archivos
loki.source.file "laravel_reader" {
targets = local.file_match.laravel_logs.targets
forward_to = [loki.write.local.receiver]
}
// Enviar a Loki
loki.write "local" {
endpoint {
url = "http://loki:3100/loki/api/v1/push"
}
}

View File

@ -65,9 +65,59 @@ services:
timeout: 15s
retries: 10
alloy:
image: grafana/alloy:latest
command:
- run
- /etc/alloy/config.alloy
- --server.http.listen-addr=0.0.0.0:12345
ports:
- "12345:12345"
volumes:
- ./config.alloy:/etc/alloy/config.alloy
- ../../storage/logs:/var/log/repuve:ro
networks:
- repuve-network
restart: unless-stopped
depends_on:
- repuve-backend
- loki
loki:
image: grafana/loki:latest
ports:
- "3100:3100"
volumes:
- ./loki-config.yml:/etc/loki/local-config.yaml
- loki_data:/loki
command: -config.file=/etc/loki/local-config.yaml
networks:
- repuve-network
restart: unless-stopped
grafana:
image: grafana/grafana:latest
ports:
- "3000:3000"
environment:
- GF_AUTH_ANONYMOUS_ENABLED=true
- GF_AUTH_ANONYMOUS_ORG_ROLE=Viewer
volumes:
- grafana_data:/var/lib/grafana
- ./grafana/provisioning:/etc/grafana/provisioning
networks:
- repuve-network
restart: unless-stopped
depends_on:
- loki
volumes:
mysql_data:
driver: local
loki_data:
driver: local
grafana_data:
driver: local
networks:
repuve-network:

View File

@ -0,0 +1,10 @@
apiVersion: 1
datasources:
- name: Loki
type: loki
access: proxy
url: http://loki:3100
isDefault: true
jsonData:
maxLines: 1000

View File

@ -0,0 +1,29 @@
auth_enabled: false
server:
http_listen_port: 3100
common:
path_prefix: /loki
storage:
filesystem:
chunks_directory: /loki/chunks
rules_directory: /loki/rules
replication_factor: 1
ring:
instance_addr: 127.0.0.1
kvstore:
store: inmemory
schema_config:
configs:
- from: 2020-10-24
store: tsdb
object_store: filesystem
schema: v13
index:
prefix: index_
period: 24h
limits_config:
reject_old_samples: false

25
Docker/Prod/config.alloy Normal file
View File

@ -0,0 +1,25 @@
logging {
level = "warn"
format = "logfmt"
}
// Descubrir archivos de log
local.file_match "laravel_logs" {
path_targets = [
{ __path__ = "/var/log/repuve/padron-estatal.log", job = "padron_estatal", env = "prod" },
{ __path__ = "/var/log/repuve/repuve-nacional.log", job = "repuve_nacional", env = "prod" },
]
}
// Leer los archivos
loki.source.file "laravel_reader" {
targets = local.file_match.laravel_logs.targets
forward_to = [loki.write.local.receiver]
}
// Enviar a Loki
loki.write "local" {
endpoint {
url = "http://loki:3100/loki/api/v1/push"
}
}

View File

@ -63,9 +63,56 @@ services:
timeout: 15s
retries: 10
alloy:
image: grafana/alloy:latest
command:
- run
- /etc/alloy/config.alloy
- --server.http.listen-addr=0.0.0.0:12345
ports:
- "127.0.0.1:12345:12345"
volumes:
- ./config.alloy:/etc/alloy/config.alloy
- ../../storage/logs:/var/log/repuve:ro
networks:
- repuve-network
restart: unless-stopped
depends_on:
- repuve-backend
- loki
loki:
image: grafana/loki:latest
ports:
- "127.0.0.1:3100:3100"
volumes:
- ./loki-config.yml:/etc/loki/local-config.yaml
- loki_data:/loki
command: -config.file=/etc/loki/local-config.yaml
networks:
- repuve-network
restart: unless-stopped
grafana:
image: grafana/grafana:latest
ports:
- "127.0.0.1:3000:3000"
volumes:
- grafana_data:/var/lib/grafana
- ./grafana/provisioning:/etc/grafana/provisioning
networks:
- repuve-network
restart: unless-stopped
depends_on:
- loki
volumes:
mysql_data:
driver: local
loki_data:
driver: local
grafana_data:
driver: local
networks:
repuve-network:

View File

@ -0,0 +1,10 @@
apiVersion: 1
datasources:
- name: Loki
type: loki
access: proxy
url: http://loki:3100
isDefault: true
jsonData:
maxLines: 1000

View File

@ -0,0 +1,29 @@
auth_enabled: false
server:
http_listen_port: 3100
common:
path_prefix: /loki
storage:
filesystem:
chunks_directory: /loki/chunks
rules_directory: /loki/rules
replication_factor: 1
ring:
instance_addr: 127.0.0.1
kvstore:
store: inmemory
schema_config:
configs:
- from: 2020-10-24
store: tsdb
object_store: filesystem
schema: v13
index:
prefix: index_
period: 24h
limits_config:
reject_old_samples: false

View File

@ -0,0 +1,54 @@
<?php namespace App\Http\Controllers\Repuve;
use App\Http\Controllers\Controller;
use App\Services\LogsService;
use Illuminate\Http\Request;
use Illuminate\Routing\Controllers\HasMiddleware;
use Notsoweb\ApiResponse\Enums\ApiResponse;
/**
* Descripción
*/
class LogsController extends Controller implements HasMiddleware
{
public function __construct(private LogsService $logsService)
{
}
public static function middleware(): array
{
return [];
}
public function repuveLogs(Request $request)
{
$filters = $this->filters($request);
$logs = $this->logsService->readRepuve($filters);
return ApiResponse::OK->response([
'source' => 'repuve',
'filters' => $filters,
'logs' => $logs,
]);
}
public function padronEstatalLogs(Request $request)
{
$filters = $this->filters($request);
$logs = $this->logsService->readPadronEstatal($filters);
return ApiResponse::OK->response([
'source' => 'padron-estatal',
'filters' => $filters,
'logs' => $logs,
]);
}
private function filters(Request $request): array
{
return $request->validate([
'start_date' => ['nullable', 'date'],
'end_date' => ['nullable', 'date', 'after_or_equal:start_date'],
]);
}
}

View File

@ -29,7 +29,7 @@ public function rules(): array
'vehicle.rfv' => 'nullable|string|max:50',
'vehicle.rfc' => 'nullable|string|max:13',
'vehicle.ofcexpedicion' => 'nullable|string|max:100',
'vehicle.fechaexpedicion' => 'nullable|date',
'vehicle.fechaexpedicion' => ['nullable', 'date_format:d/m/Y'],
'vehicle.tipo_veh' => 'nullable|string|max:50',
'vehicle.numptas' => 'nullable|string',
'vehicle.observac' => 'nullable|string|max:500',

View File

@ -36,7 +36,6 @@ class Vehicle extends Model
];
protected $casts = [
'fechaexpedicion' => 'date',
'reporte_robo' => 'boolean',
];

View File

@ -0,0 +1,139 @@
<?php namespace App\Services;
use Carbon\Carbon;
use Illuminate\Support\Arr;
use SplFileObject;
class LogsService
{
private array $sources = [
'repuve' => 'logs/repuve-nacional.log',
'padron-estatal' => 'logs/padron-estatal.log',
];
public function readRepuve(array $filters): array
{
return $this->read('repuve', $filters);
}
public function readPadronEstatal(array $filters): array
{
return $this->read('padron-estatal', $filters);
}
private function read(string $source, array $filters): array
{
$path = storage_path($this->sources[$source]);
if (!is_file($path) || !is_readable($path)) {
return [];
}
$entries = $this->parseFile($path);
return $this->applyFilters($entries, $filters);
}
private function parseFile(string $path): array
{
$file = new SplFileObject($path, 'r');
$entries = [];
$current = null;
while (!$file->eof()) {
$line = rtrim((string) $file->fgets(), "\r\n");
if ($line === '') {
continue;
}
$header = $this->parseHeader($line);
if ($header) {
if ($current) {
$entries[] = $this->sanitize($current);
}
$current = $header;
continue;
}
if ($current) {
$current['message'] .= "\n" . $line;
}
}
if ($current) {
$entries[] = $this->sanitize($current);
}
usort($entries, fn($a, $b) => strcmp($b['timestamp'], $a['timestamp']));
return $entries;
}
private function parseHeader(string $line): ?array
{
$pattern = '/^\[(?<dt>[^\]]+)\]\s[^\.\s]+\.(?<level>[A-Z]+):\s(?<message>.*)$/';
if (!preg_match($pattern, $line, $m)) {
return null;
}
try {
$date = Carbon::parse($m['dt']);
} catch (\Throwable $e) {
return null;
}
return [
'timestamp' => $date->toDateTimeString(),
'level' => strtolower($m['level']),
'message' => $m['message'],
];
}
private function applyFilters(array $entries, array $filters): array
{
$start = !empty($filters['start_date']) ? Carbon::parse($filters['start_date'])->startOfDay() : null;
$end = !empty($filters['end_date']) ? Carbon::parse($filters['end_date'])->endOfDay() : null;
$level = $filters['level'] ?? null;
return array_values(array_filter($entries, function (array $entry) use ($start, $end, $level) {
$dt = Carbon::parse($entry['timestamp']);
if ($start && $dt->lt($start)) {
return false;
}
if ($end && $dt->gt($end)) {
return false;
}
if ($level && $entry['level'] !== strtolower($level)) {
return false;
}
return true;
}));
}
private function sanitize(array $entry): array
{
$text = $entry['message'];
$patterns = [
'/("password"\s*:\s*")[^"]*(")/i' => '$1***$2',
'/("token"\s*:\s*")[^"]*(")/i' => '$1***$2',
'/(authorization)\s*[:=]\s*([^,\s]+)/i' => '$1=***',
'/(<arg0>)(.*?)(<\/arg0>)/i' => '$1***$3',
'/(<arg1>)(.*?)(<\/arg1>)/i' => '$1***$3',
];
foreach ($patterns as $pattern => $replacement) {
$text = preg_replace($pattern, $replacement, $text);
}
$entry['message'] = $text;
return $entry;
}
}

View File

@ -172,11 +172,7 @@ private function parsearRespuesta(string $soapResponse): array
*/
public function extraerDatosVehiculo(array $datos): array
{
// Convertir fecha de DD/MM/YYYY a YYYY-MM-DD
$fechaexpedicion = null;
if (isset($datos['fechaexp']) && $datos['fechaexp']) {
$fechaexpedicion = $this->convertirFecha($datos['fechaexp']);
}
$fechaexpedicion = $datos['fechaexp'] ?? null;
return [
'placa' => $datos['placa'] ?? null,
@ -201,29 +197,6 @@ public function extraerDatosVehiculo(array $datos): array
];
}
/**
* Convierte fecha de DD/MM/YYYY a YYYY-MM-DD
*/
private function convertirFecha(?string $fecha): ?string
{
if (!$fecha) {
return null;
}
// Si ya está en formato YYYY-MM-DD, retornar tal cual
if (preg_match('/^\d{4}-\d{2}-\d{2}$/', $fecha)) {
return $fecha;
}
// Convertir de DD/MM/YYYY a YYYY-MM-DD
if (preg_match('/^(\d{2})\/(\d{2})\/(\d{4})$/', $fecha, $matches)) {
return "{$matches[3]}-{$matches[2]}-{$matches[1]}";
}
// Si no coincide con ningún formato esperado, retornar null
return null;
}
/**
* Extrae los datos del propietario del resultado
*/

View File

@ -236,7 +236,9 @@ private function parseVehicleResponse(string $soapResponse, string $niv)
'pedimento',
'fecha_pedimento',
'clave_importador',
'observaciones'
'folio_CI',
'identificador_CI',
'observaciones',
];
$jsonResponse = [];

View File

@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('vehicle', function (Blueprint $table) {
$table->string('fechaexpedicion', 10)->nullable()->change();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('vehicle', function (Blueprint $table) {
$table->date('fechaexpedicion')->nullable()->change();
});
}
};

View File

@ -153,6 +153,11 @@ public function run(): void
$tagAssignToModule = $this->onPermission('tags.assign_to_module', 'Asignar etiquetas a módulo', $tags, 'api');
// === LOGS DE SERVICIOS ===
$logsServices = PermissionType::updateOrCreate(['name' => 'Logs de Servicios']);
$logsServicesIndex = $this->onPermission('logs.services.index', 'Ver logs de servicios externos', $logsServices, 'api');
// =========================================================
// ROLES
// =========================================================
@ -194,7 +199,9 @@ public function run(): void
// Constancias
$tagIndex, $tagCreate, $tagEdit, $tagDestroy,
//app
$apkIndex
$apkIndex,
// Logs
$logsServicesIndex
);
// Encargado
@ -219,6 +226,8 @@ public function run(): void
$packageIndex, $packageCreate, $packageEdit, $packageDestroy, $packageBoxTags,
// Constancias
$tagIndex, $tagCreate, $tagEdit, $tagDestroy, $tagAssignToModule,
// Logs
$logsServicesIndex
);
// Perito

View File

@ -15,6 +15,7 @@
use App\Http\Controllers\Repuve\PackageController;
use App\Http\Controllers\Repuve\TagsController;
use App\Http\Controllers\Repuve\AppController;
use App\Http\Controllers\Repuve\LogsController;
use App\Http\Controllers\System\SettingsController;
/**
@ -100,6 +101,10 @@
Route::delete('app/{app}', [AppController::class, 'destroy']);
Route::get('app/download', [AppController::class, 'download']);
// Rutas de logs
Route::get('logs/repuve', [LogsController::class, 'repuveLogs']);
Route::get('logs/padron-estatal', [LogsController::class, 'padronEstatalLogs']);
});
/** Rutas públicas */