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