diff --git a/frontend/i18n/locales/fr.json b/frontend/i18n/locales/fr.json
index 82b9d14..88aa979 100644
--- a/frontend/i18n/locales/fr.json
+++ b/frontend/i18n/locales/fr.json
@@ -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"
diff --git a/frontend/pages/applications/[slug].vue b/frontend/pages/applications/[slug].vue
index 1fa3499..a241b0e 100644
--- a/frontend/pages/applications/[slug].vue
+++ b/frontend/pages/applications/[slug].vue
@@ -579,7 +579,7 @@ onMounted(loadApplication)
-
+
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) {
diff --git a/src/State/SymfonyLogProvider.php b/src/State/SymfonyLogProvider.php
index 27f903e..4754547 100644
--- a/src/State/SymfonyLogProvider.php
+++ b/src/State/SymfonyLogProvider.php
@@ -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,