因為有多個國家的網站,原本使用 Seq 作為 log server,但因為同時間只能有一個人登入,每個國家都需要各自的容器服務,為了簡化運維,打算將 log server 換成 Grafana,並在學習 K8s 時發現 Grafana、Loki 和 Prometheus 成為常見的標配。
Grafana 是一款開源的資料視覺化和監控平台,讓你可以輕鬆建立互動式儀表板,從多種數據來源即時監控系統狀態,主要是由以下三大服務組成。
聽了幾位網友分享,純Log服務的話,如果是地端可以考慮使用Graylog,也是一套不錯的Log 軟體,但Loki的優勢在於輕量化,上雲可以節省一些資源,Loki在和Grafana系列的產品整合比較容易。
docker run -d --name=loki -p 3100:3100 grafana/loki:latest
# const createdAt = Date.now() * 1_000_000; // js 將計算後的結果貼到這裡 createdAt @createdAt = 1693022700000000 POST http://localhost:3100/loki/api/v1/push Content-Type: application/json { "streams": [ { "stream": { "app":"nextjs-app" // 可以將你的動態資料放在這,可用來分類、篩選、繪製報表 }, "values": [ ["{{createdAt}}", "write your logs in here"] ] } ] }
GET http://localhost:3100/loki/api/v1/query?query={app="nextjs-app"}&limit=10
docker run -d --name=grafana -p 7777:3000 grafana/grafana
接下來,在 Next.js 後端安裝所需的套件:
npm install winston winston-loki --save
import winston from 'winston'; import LokiTransport from 'winston-loki'; const logger = winston.createLogger({ transports: [ new LokiTransport({ host: 'http://localhost:3100', labels: { app: 'nextjs-app' }, json: true, }), ], }); export default logger;
在頁面中使用時:
import logger from '@/lib/log/loki-log'; logger.info('API request received', { endpoint: 'https://www.abc.com' });
const LogLevel = { Information: "info", Debug: "debug", Warning: "warn", Error: "error", }; async function clientLog(message, level = LogLevel.Information, extraLabels = {}) { if (!message) return; const timestamp = `${Date.now()}000000`; // 將毫秒級時間轉為納秒級 const logData = { streams: [ { stream: { app: "frontend-app", level: level, ...extraLabels, // 額外的標籤,例如用於區分不同頁面或功能 }, values: [ [timestamp, message] ] } ] }; try { const response = await fetch(LOKI_URL, { method: "POST", headers: { "Content-Type": "application/json", // "Authorization": "Bearer YOUR_TOKEN" // 如果 Loki 設置了 Token 驗證,啟用此行 }, body: JSON.stringify(logData), }); if (!response.ok) { console.error("Failed to send log to Loki:", response.statusText); } } catch (error) { console.error("Error sending log to Loki:", error); } }
// 發送信息級別的日誌 clientLog("This is an informational message", LogLevel.Information, { page: "home" }); // 發送錯誤級別的日誌 clientLog("An error occurred", LogLevel.Error, { page: "checkout", userId: 12345 });
server { listen 80; location /loki/ { proxy_pass http://localhost:3100; # Loki Url proxy_set_header Authorization "Bearer YOUR_TOKEN"; # 令牌 # 可根据需求限制访问的 IP、路径等 if ($http_authorization != "Bearer YOUR_TOKEN") { return 403; # 拒绝访问 } } }