Search by

hocvt / log-viewer-remote

vuthaihoc

Mở rộng opcodesio/log-viewer: auth shared-secret giữa các host, tải file log của host ở xa qua host đang xem, và log query chậm / N+1.

Package info

github.com/vuthaihoc/opcodesio-log-viewer-ext

pkg:composer/hocvt/log-viewer-remote

Statistics

Installs: 239

Dependents: 0

Suggesters: 0

Stars: 0

Open Issues: 0

v1.4.5 2026-10-14 08:09 UTC

This package is auto-updated.

Last update: 2026-10-14 08:09:46 UTC


README

tests

Mở rộng opcodesio/log-viewer cho mô hình "một Log Viewer xem log của nhiều host":

  1. Auth thống nhất. Request server-to-server giữa các host đi bằng Authorization: Bearer <shared secret>. Người dùng thật thì hỏi Gate viewLogViewer.
  2. Tải file log của host ở xa. Package gốc không tải được: nút Download gọi XHR sang origin khác (CORS chặn), và route download bên đó vẫn có AuthorizeLogViewer nên trình duyệt không có session cũng 403. Package viết lại download_url về origin hiện tại rồi tải hộ server-to-server, stream về client.
  3. Bớt cấu hình tay. Hosts khai bằng env, api_stateful_domains mặc định theo APP_URL.
  4. Slow query log. Log query chậm và cảnh báo request/job có quá nhiều query (N+1), che sạch bí mật trong SQL lẫn URL, truy vết về đúng file Blade / component Livewire. Mặc định tắt — xem Slow query log.
  5. Agent phân tích log từ xa. Lệnh log-viewer-remote:aggregate / :entries gom số liệu (lỗi, trang chậm, N+1, job tốn DB…) ngay trên host có file, không tải file về; token chỉ đọc, giới hạn theo channel — xem Agent.

Cài

Trên mọi host (host xem và host bị xem):

composer require hocvt/log-viewer-remote
php artisan log-viewer-remote:secret     # sinh LOG_VIEWER_SHARED_SECRET, copy sang các host khác

Trên host xem log, thêm vào .env:

LOG_VIEWER_HOSTS="web=https://app.example.com/log-viewer,api=https://api.example.com/log-viewer"

Trên host chạy production, mở quyền xem trong AppServiceProvider::boot():

Gate::define('viewLogViewer', fn (?User $user): bool => (bool) $user?->isAdmin());

Mặc định của package: local cho qua, môi trường khác chặn hết.

App không dùng Gate được

Có app mà mọi lần gọi Gate với user đã đăng nhập đều ném TypeError: cài spatie/laravel-permission (nó đăng ký một Gate::before nhận Illuminate\Contracts\Auth\Access\Authorizable) trong khi model User không implement contract đó. Thường gặp ở codebase cũ tự làm tầng phân quyền riêng và đã có sẵn User::can() với ngữ nghĩa khác, nên không thể implement Authorizable mà không làm gãy chỗ khác.

Những app đó bỏ qua tầng Gate bằng cách cắm callback riêng, đường shared secret giữa các host vẫn giữ nguyên:

use HocVT\LogViewerRemote\LogViewerRemote;

LogViewerRemote::authorizeUsing(
    fn (Request $request): bool => app()->isLocal() || access()->hasRoles(['Administrator'])
);

Cắm callback thì Gate viewLogViewer không còn được hỏi tới.

Kiểm tra kết nối:

php artisan log-viewer-remote:check

Đổi prefix mặc định

Nên đổi. Mặc định Log Viewer nằm ở /log-viewer — ai cũng đoán được, và trang này phơi toàn bộ log ứng dụng. Gate chặn người lạ, nhưng một URL không đoán được sẽ cắt luôn lượt dò của bot trước khi chạm tới tầng xác thực. Publish config rồi đổi:

php artisan vendor:publish --tag=log-viewer-config
// config/log-viewer.php
'route_path' => 'sys/x9f2logs',   // nhiều đoạn cũng được

route_path không đọc env, nên bắt buộc publish config mới đổi được. Đổi xong nhớ php artisan optimize:clear (hoặc route:cache lại nếu đang cache route).

Toàn bộ route đi theo prefix mới, kể cả hai route của package này, và thứ tự vẫn đúng — chúng vẫn đứng trước route bắt-tất {prefix}/{view?}.

Ba điều cần nhớ khi đã đổi:

  1. Prefix của mỗi host là độc lập. Host A xem log host B thì chỉ cần URL trong LOG_VIEWER_HOSTS trỏ đúng gốc Log Viewer của B. Hai bên đặt prefix khác nhau vẫn chạy.

  2. Khai host phải kèm prefix. Chỉ URL trần mới được tự bù, và giá trị bù là route_path của chính host đang khai — tức đoán cả cụm dùng chung quy ước:

    LOG_VIEWER_HOSTS="a=https://a.example.com/sys/x9f2logs,b=https://b.example.com"
    

    b sẽ thành https://b.example.com/<route_path của host này>. Nếu b đặt prefix khác, phải viết đủ. Sai prefix thì log-viewer-remote:check báo 404 — sai route_path.

  3. Đừng hardcode /log-viewer trong test hay link. Dùng config('log-viewer.route_path'), không thì đổi prefix là gãy hàng loạt.

Cấu hình

Cả package dùng một file config/log-viewer-ext.php (key log-viewer-ext): auth / host xa ở gốc, nhóm agent, nhóm slow_log. Publish: php artisan vendor:publish --tag=log-viewer-ext-config. Provider gộp đệ quy với file của app: app chỉ ghi key mình đổi, kể cả trong nhóm lồng nhau; danh sách thì app thay hẳn.

Bản ≤ 1.2 dùng hai file config/log-viewer-remote.php + config/slow-log.php: app còn giữ thì vẫn được đọc (xếp dưới file mới), log-viewer-remote:check nhắc chuyển. Env giữ nguyên tên.

Các key ở gốc:

Key Env Ý nghĩa
shared_secret LOG_VIEWER_SHARED_SECRET (fallback LOG_VIEWER_PRODUCTION_TOKEN) Bearer token giữa các host
hosts LOG_VIEWER_HOSTS id=url,id2=url2; merge vào log-viewer.hosts, không ghi đè host đã khai tay
timeout.request / timeout.download — Giây, gọi sang host xa

Host cần tên đẹp, basic auth hay header riêng thì khai trong config/log-viewer.php như bình thường; package chỉ điền những id chưa có.

Slow query log

SLOW_LOG_ENABLED=true

Tuỳ chọn log thêm request xử lý quá lâu và response quá lớn theo prefix path (SLOW_LOG_REQUEST_TIME_PREFIXES, SLOW_LOG_RESPONSE_SIZE_PREFIXES, mặc định tắt).

Không cần đăng ký thêm provider. Mặc định ghi ra channel riêng slow-log (storage/logs/slow-log-YYYY-MM-DD.log). Truy vết về ⚡component Livewire 4 và bản vá Debugbar mặc định tắt — app Livewire bật bằng SLOW_LOG_TRACE_LIVEWIRE=true, SLOW_LOG_DEBUGBAR_LIVEWIRE=true. Config ở nhóm slow_log của log-viewer-ext.

Tài liệu đầy đủ (tích hợp, ngưỡng, cách che dữ liệu, đọc log): docs/slow-log.md.

Agent phân tích log từ xa

php artisan log-viewer-remote:secret --agent --show  # một lần / project, dán LOG_VIEWER_AGENT_TOKEN vào .env mọi host
php artisan log-viewer-remote:check                   # cột Agent: phiên bản + channel cho phép của từng host
php artisan log-viewer-remote:aggregate --host=m1 --channel=slow-log --date=2026-10-05
php artisan log-viewer-remote:entries --host=m1 --at=slow-log-2026-10-05.log@1124892

Host bị đọc khai channel agent token được đọc (LOG_VIEWER_AGENT_CHANNELS=slow-log,daily; mặc định slow-log; shared secret không bị giới hạn). Agent token chỉ gọi được api/agent/* — không tải / xoá file, không vào UI. levels / messages đọc mọi log, 4 bảng còn lại chỉ đọc slow log; câu hỏi riêng thì tự gửi --match / --group (regex). Đi vòng qua host đang xem: --via=web --host=m1. Mỗi project một token, máy chạy agent không giữ shared secret của production — đủ khuyến nghị ở docs/agent.md. Skill cho Claude Code: php artisan vendor:publish --tag=log-viewer-remote-skill.

Tài liệu đầy đủ (bảy thao tác hay làm, cách đọc số, HTTP API, cấu hình): docs/agent.md.

Những chỗ dễ làm hỏng khi sửa

  • Host trả ra ngoài không có credential. Vendor đưa nguyên LogViewer::getHosts() vào window.LogViewer và trả cả auth ở /api/hosts — ai xem được Log Viewer cũng đọc được shared secret trong mã nguồn trang. Package bỏ auth / headers qua LogViewer::resolveHostsUsing(); credential thật chỉ đọc qua Support\HostCredentials (thẳng từ config('log-viewer.hosts')), gắn vào request qua Support\RemoteHttp. Vendor chỉ giữ một resolver: app tự gọi resolveHostsUsing() là ghi đè mất bản vá này.

  • Forward tự làm, không gọi vendor. ForwardRequestToHost không gọi parent::handle() vì bản vendor đọc credential từ $host->auth (đã rỗng). Đường forward giữ như vendor (URL, header X-Forwarded-*, không gửi body), thêm timeout timeout.forward và chép Retry-After.

  • Route nạp ở register(), không ở boot(). Package gốc đăng ký route bắt-tất log-viewer/{view?} (where '.*') trong boot(); mọi route dưới /log-viewer khai sau nó đều bị nuốt.

  • Thay middleware vendor bằng container binding. ForwardRequestToHostMiddleware được gắn cứng trong routes/api.php của package gốc; Pipeline resolve middleware bằng container->make() nên bind() là đủ.

  • Không gửi X-Forwarded-Host khi xin link ký. Host xa phải ký URL bằng domain của chính nó, nếu không chữ ký hỏng khi gọi lại.

  • Stream theo chunk, không ->body(). Log production có thể vài trăm MB.

  • Gate mặc định định nghĩa trong boot() của package. App provider boot sau nên Gate::define trong AppServiceProvider ghi đè được.

Gỡ rối

File tải về bị chèn rác ở đầu (vài byte khoảng trắng), md5 không khớp. App có output lọt ra trước response — thường là file PHP có khoảng trắng trước <?php hoặc sau ?>, và file đó được nạp ở mọi request (route file, config, helper). Rác đó chui vào đầu MỌI response tải file của app, không riêng Log Viewer. Tìm bằng:

php -r '$b=file_get_contents($argv[1]); exit(str_starts_with($b,"<?php")?0:1);' <file>

Đi qua proxy thì rác nhân đôi, vì cả host xem lẫn host xa đều thêm phần của mình.

UI 403 toàn bộ API. EnsureFrontendRequestsAreStateful chưa nhận domain hiện tại. Package tự điền theo APP_URL; nếu truy cập bằng domain khác thì khai LOG_VIEWER_API_STATEFUL_DOMAINS.

log-viewer-remote:check báo 403. Token hai bên lệch, hoặc host xa chưa cài package.

Identifier file khác nhau giữa CLI và web. Identifier là hash của đường dẫn tuyệt đối, mà CLI và php-fpm có thể resolve storage_path() khác nhau. Luôn lấy identifier từ cùng tiến trình sẽ tải — luồng UI vốn đã đúng, chỉ sai khi copy tay từ tinker.

Mặc định package gốc quét cả /var/log/nginx/* và vài đường dẫn hệ thống. Nếu không muốn, publish config/log-viewer.php rồi thu hẹp include_files.

Test

composer install && composer test     # ~134 test, vài giây, không cần DB / mạng

CI (.github/workflows/tests.yml) chạy bộ test trên PHP 8.3 và 8.5, với Laravel 11 / 12 / 13. Riêng cặp Laravel 11 × PHP 8.5 bị loại, vì Laravel 11 chỉ hỗ trợ tới PHP 8.4. Code của package gây deprecation (gọi hàm PHP hoặc API vendor đã deprecate) thì test đỏ. Laravel nuốt deprecation vào kênh log, nên tests/Feature/TestCase có lớp chặn riêng cho file trong src/.

  • tests/Unit — phần PHP thuần (bộ đọc log của agent, ChannelFiles). Chạy được cả bằng phpunit của app đang dùng package: vendor/bin/phpunit -c <package>/phpunit.xml --testsuite Unit.
  • tests/Feature — phần cần Laravel trên orchestra/testbench: endpoint agent (token, channel, cursor, forward, link UI, loại log), lệnh, vá lộ secret, tải file host xa, SqlLogger, RequestLogger, gộp config. File log ở thư mục tạm, host xa là Http::fake, người xem là user không lưu DB.

Project dùng package chỉ cần test những gì nó tự quyết (Gate viewLogViewer, giá trị trong config/log-viewer-ext.php) — không chép lại test của package.