namespace App\Http\Controllers; use Illuminate\Http\Request; use Illuminate\Support\Facades\File; class LogController extends Controller { // 显示前端页面 public function index() { //dd('Reached LogController@index'); // 断点调试 return view('logs.viewer'); } public function tail() { $path = storage_path('logs/laravel.log'); if (!file_exists($path)) { return response()->json(['lines' => []]); } // 使用 shell 命令获取最后 50 行(高效且避免 PHP 内存溢出) $escapedPath = escapeshellarg($path); $output = []; exec("tail -n 50 {$escapedPath}", $output); // 关键步骤:清洗每一行,确保 UTF-8 合法 $cleanedLines = array_map(function ($line) { // mb_convert_encoding: 将字符串从 UTF-8 转为 UTF-8,忽略非法字符 // 'ignore' 参数会丢弃无法转换的字节,防止 json_encode 报错 return mb_convert_encoding($line, 'UTF-8', 'UTF-8'); }, $output); return response()->json([ 'lines' => $cleanedLines ]); } /** * 高效读取文件最后 N 行,不占用大量内存 */ private function tailFile(string $filePath, int $lines = 50): array { $handle = fopen($filePath, "r"); if (!$handle) return []; $linebuffer = []; $pos = -1; $foundLines = 0; $chunkSize = 1024; // 每次读取 1KB // 从文件末尾向前读取 while ($foundLines < $lines && fseek($handle, $pos, SEEK_END) === 0) { // 读取一块数据 $data = fread($handle, $chunkSize); if ($data === false || strlen($data) === 0) break; // 将新读取的数据拼接到缓冲区前面 $linebuffer = array_merge(explode("\n", $data), $linebuffer); // 移动指针 $pos -= $chunkSize; // 计算当前找到的完整行数(减去最后一个可能不完整的片段) $foundLines = count($linebuffer) - 1; } fclose($handle); // 清理空行并截取最后的 $lines 行 $linebuffer = array_filter($linebuffer, function($line) { return $line !== ''; }); return array_slice($linebuffer, -$lines); } }