Next.js 如何增加 ELK Log 功能
為什麼要在 Next.js 中增加 ELK Log 功能?
在現代的 web 應用開發中,追蹤和記錄應用程式的活動是非常重要的一環。這不僅有助於除錯,還能讓我們更了解應用程式的運行狀況,提升用戶體驗。ELK Stack(Elasticsearch, Logstash, Kibana)是一個強大的日誌管理工具,能夠幫助我們收集、處理和展示日誌數據。那麼,該如何在 Next.js 專案中增加 ELK Log 功能呢?讓我們一步步來了解。
ELK Stack 簡介
在進入實作之前,讓我們先來快速了解一下 ELK Stack 的組成部分:
- Elasticsearch:一個強大的搜索引擎,用於儲存和搜索日誌數據。
- Logstash:用於收集和處理日誌數據,並將其傳送至 Elasticsearch。
- Kibana:一個視覺化工具,用於展示和分析 Elasticsearch 中的數據。
這三個工具結合在一起,提供了一個完整的日誌管理解決方案。
在 Next.js 中增加 ELK Log 功能的步驟
1. 安裝所需的套件
首先,我們需要安裝一些 Node.js 套件來幫助我們實現日誌功能。這裡我們會使用 winston
來記錄日誌,並使用 node-fetch
將日誌傳送至 ELK。
npm install winston node-fetch
2. 設定 Logger
接下來,我們需要設定一個 Logger。這個 Logger 會負責記錄日誌並將其傳送至 ELK。
伺服器端 Logger 設定
// lib/serverLogger.ts
import fs from 'fs';
import path from 'path';
import fetch from 'node-fetch';
import winston from 'winston';
const { combine, timestamp, printf } = winston.format;
const logFormat = printf(({ level, message, timestamp }) => {
return `${timestamp} ${level}: ${message}`;
});
const logger = winston.createLogger({
level: 'info',
format: combine(
timestamp(),
logFormat
),
transports: [
new winston.transports.File({ filename: 'logs/app.log', level: 'info' }),
new winston.transports.Console()
],
});
const elkUrl = process.env.ELK_URL || null;
if (elkUrl) {
logger.add(new winston.transports.Http({
level: 'info',
format: winston.format.json(),
host: new URL(elkUrl).hostname,
path: new URL(elkUrl).pathname,
}));
}
export default logger;
客戶端 Logger 設定
// lib/clientLogger.ts
import winston from 'winston';
const { combine, timestamp, printf } = winston.format;
const logFormat = printf(({ level, message, timestamp }) => {
return `${timestamp} ${level}: ${message}`;
});
const logger = winston.createLogger({
level: 'info',
format: combine(
timestamp(),
logFormat
),
transports: [
new winston.transports.Console()
],
});
const elkUrl = process.env.NEXT_PUBLIC_ELK_URL || null;
if (elkUrl) {
logger.add(new winston.transports.Http({
level: 'info',
format: winston.format.json(),
host: new URL(elkUrl).hostname,
path: new URL(elkUrl).pathname,
}));
}
export default logger;
3. 在 Middleware 中使用 Logger
在 Next.js 中,我們可以使用 Middleware 來攔截請求,並在這裡使用 Logger 來記錄請求日誌。
// middleware.ts
import { NextRequest, NextResponse } from 'next/server';
import { getToken } from 'next-auth/jwt';
import logger from '@/lib/serverLogger';
const secret = process.env.NEXTAUTH_SECRET;
export default async function middleware(req: NextRequest) {
logger.info(`Request received: ${req.url}`);
const token = await getToken({ req, secret });
if (token) {
logger.info(`User authenticated: ${token.email}`);
} else {
logger.warn('Unauthenticated request');
}
return NextResponse.next();
}
export const config = {
matcher: ['/api/:path*'],
};
4. 在 API 路由中使用 Logger
我們也可以在 API 路由中使用 Logger 來記錄各種操作。例如:
// pages/api/user.ts
import { NextApiRequest, NextApiResponse } from 'next';
import logger from '@/lib/serverLogger';
export default function handler(req: NextApiRequest, res: NextApiResponse) {
logger.info('User API endpoint hit');
if (req.method === 'GET') {
logger.info('Fetching user data');
res.status(200).json({ name: 'John Doe' });
} else {
logger.warn('Unsupported request method');
res.status(405).end();
}
}
5. 在客戶端使用 Logger
客戶端的日誌可以用來記錄用戶行為及其他前端事件。例如:
// pages/index.tsx
import React, { useEffect } from 'react';
import logger from '@/lib/clientLogger';
const HomePage = () => {
useEffect(() => {
logger.info('HomePage component mounted');
}, []);
const handleClick = () => {
logger.info('Button clicked');
};
return (
<div>
<h1>Welcome to Next.js with ELK Logging!</h1>
<button onClick={handleClick}>Click me</button>
</div>
);
};
export default HomePage;
6. 視覺化日誌數據
完成上述步驟後,我們可以在 Kibana 中配置並視覺化我們的日誌數據。這將有助於我們更直觀地了解應用程式的運行狀況。
完整實作範例
以下是一個完整的實作範例,包含伺服器端和客戶端的日誌設定、Middleware 和 API 路由中的日誌記錄:
伺服器端日誌設定
// lib/serverLogger.ts
import fs from 'fs';
import path from 'path';
import fetch from 'node-fetch';
import winston from 'winston';
const { combine, timestamp, printf } = winston.format;
const logFormat = printf(({ level, message, timestamp }) => {
return `${timestamp} ${level}: ${message}`;
});
const logger = winston.createLogger({
level: 'info',
format: combine(
timestamp(),
logFormat
),
transports: [
new winston.transports.File({ filename: 'logs/app.log', level: 'info' }),
new winston.transports.Console()
],
});
const elkUrl = process.env.ELK_URL || null;
if (elkUrl) {
logger.add(new winston.transports.Http({
level: 'info',
format: winston.format.json(),
host: new URL(elkUrl).hostname,
path: new URL(elkUrl).pathname,
}));
}
export default logger;
客戶端日誌設定
// lib/clientLogger.ts
import winston from 'winston';
const { combine, timestamp, printf } = winston.format;
const logFormat = printf(({ level, message, timestamp }) => {
return `${timestamp} ${level}: ${message}`;
});
const logger = winston.createLogger({
level: 'info',
format: combine(
timestamp(),
logFormat
),
transports: [
new winston.transports.Console()
],
});
const elkUrl = process.env.NEXT_PUBLIC_ELK_URL || null;
if (elkUrl) {
logger.add(new winston.transports.Http({
level: 'info',
format: winston.format.json(),
host: new URL(elkUrl).hostname,
path: new URL(elkUrl).pathname,
}));
}
export default logger;
Middleware 日誌記錄
// middleware.ts
import { NextRequest, NextResponse } from 'next/server';
import { getToken } from 'next-auth/jwt';
import logger from '@/lib/serverLogger';
const secret = process.env.NEXTAUTH_SECRET;
export default async function
middleware(req: NextRequest) {
logger.info(`Request received: ${req.url}`);
const token = await getToken({ req, secret });
if (token) {
logger.info(`User authenticated: ${token.email}`);
} else {
logger.warn('Unauthenticated request');
}
return NextResponse.next();
}
export const config = {
matcher: ['/api/:path*'],
};
API 路由日誌記錄
// pages/api/user.ts
import { NextApiRequest, NextApiResponse } from 'next';
import logger from '@/lib/serverLogger';
export default function handler(req: NextApiRequest, res: NextApiResponse) {
logger.info('User API endpoint hit');
if (req.method === 'GET') {
logger.info('Fetching user data');
res.status(200).json({ name: 'John Doe' });
} else {
logger.warn('Unsupported request method');
res.status(405).end();
}
}
客戶端日誌記錄
// pages/index.tsx
import React, { useEffect } from 'react';
import logger from '@/lib/clientLogger';
const HomePage = () => {
useEffect(() => {
logger.info('HomePage component mounted');
}, []);
const handleClick = () => {
logger.info('Button clicked');
};
return (
<div>
<h1>Welcome to Next.js with ELK Logging!</h1>
<button onClick={handleClick}>Click me</button>
</div>
);
};
export default HomePage;
結論
在 Next.js 專案中增加 ELK Log 功能,可以大幅提升應用程式的可觀察性和除錯效率。通過 ELK Stack,我們可以輕鬆收集、處理和視覺化日誌數據,讓開發和運維工作更加高效。如果你還沒有在專案中使用 ELK Stack,現在就開始吧,讓你的應用程式變得更健壯!
希望這篇文章能夠幫助你在 Next.js 專案中順利增加 ELK Log 功能,並為你的開發工作帶來更多便利。如果你有任何問題或建議,歡迎在下方留言與我們分享。Happy coding!