Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8fb71e6370 | ||
| e128b45caa |
@@ -1,2 +1,2 @@
|
||||
parameters:
|
||||
app.version: '0.1.21'
|
||||
app.version: '0.1.22'
|
||||
|
||||
@@ -119,6 +119,7 @@
|
||||
"deployScriptPath": "Chemin du script de deploiement",
|
||||
"maintenanceFilePath": "Chemin du fichier de maintenance",
|
||||
"pathHint": "Prefixe automatique : /mnt/apps",
|
||||
"pathHintLog": "Chemin dans le container, ex : var/log/prod.log",
|
||||
"appUrl": "URL de l'application",
|
||||
"save": "Enregistrer",
|
||||
"cancel": "Annuler"
|
||||
|
||||
@@ -579,7 +579,7 @@ onMounted(loadApplication)
|
||||
<MalioInputText v-model="lf.label" :label="t('environments.logFiles.label')" groupClass="mt-0" inputClass="flex-1" required />
|
||||
</div>
|
||||
<div class="w-2/3">
|
||||
<MalioInputText v-model="lf.path" :label="t('environments.logFiles.path')" groupClass="mt-0" inputClass="flex-[2]" :hint="t('environments.form.pathHint')" required />
|
||||
<MalioInputText v-model="lf.path" :label="t('environments.logFiles.path')" groupClass="mt-0" inputClass="flex-[2]" :hint="`${t('environments.form.pathHint')}/${application?.slug ?? ''}`" required />
|
||||
</div>
|
||||
<div class="h-[46px] flex items-center">
|
||||
<MalioButtonIcon
|
||||
|
||||
@@ -35,25 +35,91 @@ final readonly class LogService
|
||||
$process->setTimeout(10);
|
||||
$process->run();
|
||||
|
||||
return $process->getOutput() . $process->getErrorOutput();
|
||||
$output = $process->getOutput() . $process->getErrorOutput();
|
||||
|
||||
return $this->formatDockerOutput($output);
|
||||
}
|
||||
|
||||
public function getSymfonyLog(string $relativePath, int $lines = 100, ?string $level = null): string
|
||||
private function formatDockerOutput(string $output): string
|
||||
{
|
||||
$path = $this->pathResolver->resolve($relativePath);
|
||||
$rawLines = explode("\n", trim($output));
|
||||
$formatted = [];
|
||||
|
||||
if (!file_exists($path)) {
|
||||
return sprintf('Log file not found: %s', $path);
|
||||
foreach ($rawLines as $line) {
|
||||
if ('' === $line) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Try parsing as Symfony monolog JSON format
|
||||
$parsed = $this->parseSymfonyLogLine($line);
|
||||
|
||||
if (null !== $parsed) {
|
||||
if ('doctrine' === $parsed['channel']) {
|
||||
continue;
|
||||
}
|
||||
$formatted[] = sprintf('[%s] %s.%s: %s', $parsed['date'], $parsed['channel'], $parsed['level'], $parsed['message']);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Try parsing as JSON log ({"message":"...","level":...})
|
||||
$json = json_decode($line, true);
|
||||
if (\is_array($json) && isset($json['message'])) {
|
||||
$date = isset($json['datetime']) ? substr($json['datetime'], 0, 19) : '';
|
||||
$date = str_replace('T', ' ', $date);
|
||||
$channel = $json['channel'] ?? 'app';
|
||||
$level = $json['level_name'] ?? 'INFO';
|
||||
|
||||
if ('doctrine' === $channel) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$formatted[] = sprintf('[%s] %s.%s: %s', $date, $channel, $level, $json['message']);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Keep raw lines that don't match any format (nginx access logs, etc.)
|
||||
$formatted[] = $line;
|
||||
}
|
||||
|
||||
return implode("\n", $formatted);
|
||||
}
|
||||
|
||||
public function getSymfonyLog(string $containerName, string $logPath, int $lines = 100, ?string $level = null): string
|
||||
{
|
||||
$check = new Process(['which', 'docker']);
|
||||
$check->setTimeout(5);
|
||||
$check->run();
|
||||
|
||||
if (!$check->isSuccessful()) {
|
||||
// Fallback: try reading from filesystem (dev mode)
|
||||
$localPath = $this->pathResolver->resolve($logPath);
|
||||
if (!file_exists($localPath)) {
|
||||
return sprintf('Log file not found: %s (Docker CLI unavailable, local path: %s)', $logPath, $localPath);
|
||||
}
|
||||
$readLines = (null !== $level && '' !== $level) ? $lines * 5 : $lines;
|
||||
$process = new Process(['tail', '-n', (string) $readLines, $localPath]);
|
||||
$process->setTimeout(10);
|
||||
$process->run();
|
||||
return $this->formatSymfonyOutput($process->getOutput(), $lines, $level);
|
||||
}
|
||||
|
||||
// Read more lines than requested to compensate for filtering
|
||||
$readLines = (null !== $level && '' !== $level) ? $lines * 5 : $lines;
|
||||
|
||||
$process = new Process(['tail', '-n', (string) $readLines, $path]);
|
||||
$process = new Process(['docker', 'exec', $containerName, 'tail', '-n', (string) $readLines, $logPath]);
|
||||
$process->setTimeout(10);
|
||||
$process->run();
|
||||
|
||||
$rawLines = explode("\n", trim($process->getOutput()));
|
||||
if (!$process->isSuccessful()) {
|
||||
return sprintf('Error reading log: %s', trim($process->getErrorOutput()));
|
||||
}
|
||||
|
||||
return $this->formatSymfonyOutput($process->getOutput(), $lines, $level);
|
||||
}
|
||||
|
||||
private function formatSymfonyOutput(string $output, int $lines, ?string $level): string
|
||||
{
|
||||
$rawLines = explode("\n", trim($output));
|
||||
$formatted = [];
|
||||
|
||||
foreach ($rawLines as $line) {
|
||||
|
||||
@@ -42,6 +42,7 @@ final readonly class SymfonyLogProvider implements ProviderInterface
|
||||
$level = $request?->query->get('level');
|
||||
|
||||
$content = $this->logService->getSymfonyLog(
|
||||
$environment->getContainerName(),
|
||||
$logFile->getPath(),
|
||||
$lines,
|
||||
$level,
|
||||
|
||||
Reference in New Issue
Block a user