
因為有多個國家的網站,原本使用 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; # 拒绝访问
}
}
}