Trong quá trình phát triển và vận hành ứng dụng, việc ghi log đóng vai trò như một hộp đen ghi lại toàn bộ hoạt động, giúp lập trình viên theo dõi, phát hiện và xử lý sự cố một cách nhanh chóng. Đối với các ứng dụng Node.js là gì, việc lựa chọn một thư viện ghi log hiệu quả là vô cùng quan trọng. Tuy nhiên, nhiều nhà phát triển, đặc biệt là những người mới, thường gặp khó khăn trong việc thiết lập và quản lý hệ thống log một cách bài bản trên môi trường máy chủ như Ubuntu. Đây là lúc Winston, một thư viện ghi log linh hoạt và mạnh mẽ cho Node.js, thể hiện vai trò của mình. Bài viết này sẽ là kim chỉ nam chi tiết, hướng dẫn bạn từ A-Z cách cài đặt, cấu hình, và sử dụng Winston để xây dựng một hệ thống ghi log chuyên nghiệp cho ứng dụng Node.js của bạn trên Ubuntu 20.04.
Giới thiệu về Winston và vai trò trong ghi log Node.js
Bạn đã bao giờ rơi vào tình huống ứng dụng Node.js của mình gặp lỗi trên môi trường production mà không có bất kỳ dấu vết nào để tìm ra nguyên nhân chưa? Việc ghi log (Debug là gì) chính là bước không thể thiếu để theo dõi và gỡ lỗi trong mọi dự án phần mềm chuyên nghiệp. Nó cung cấp một cái nhìn chi tiết về những gì đang xảy ra bên trong ứng dụng, từ các yêu cầu truy cập, các tiến trình xử lý, cho đến những lỗi phát sinh đột ngột.
Tuy nhiên, nhiều developer, đặc biệt khi làm việc trên môi trường server như Ubuntu, thường gặp khó khăn trong việc quản lý và phân tích log một cách hiệu quả. Log được in ra console có thể bị mất khi server khởi động lại, và việc ghi log vào một file duy nhất có thể khiến file đó phình to, khó đọc và khó quản lý. Đây chính là lúc Winston xuất hiện như một giải pháp toàn diện. Winston là một thư viện ghi log đa năng, có khả năng tùy chỉnh cao, được thiết kế để xử lý hầu hết các nhu cầu ghi log trong Node.js. Nó cho phép bạn gửi log đến nhiều nơi khác nhau cùng lúc (console, file, database), định dạng log theo ý muốn và phân loại log theo các cấp độ quan trọng khác nhau. Bài viết này sẽ hướng dẫn bạn từ bước cài đặt, cấu hình cơ bản, tạo logger đa mức độ, ghi log vào file và phân tích log một cách thực tế trên Ubuntu 20.04.

Cài đặt và cấu hình Winston cho ứng dụng Node.js
Để bắt đầu sử dụng Winston, chúng ta cần chuẩn bị môi trường và thực hiện các bước cài đặt, cấu hình ban đầu. Quá trình này khá đơn giản nhưng là nền tảng quan trọng để xây dựng một hệ thống log mạnh mẽ sau này.
Cài đặt Winston trên Ubuntu 20.04
Trước hết, bạn cần đảm bảo rằng Node.js và npm (Node Package Manager) đã được cài đặt trên máy chủ Ubuntu 20.04 của bạn. Nếu chưa có, bạn có thể cài đặt chúng bằng các lệnh sau trong terminal:
sudo apt update
sudo apt install nodejs npm
Sau khi đã có Node.js và npm, hãy di chuyển đến thư mục dự án Node.js của bạn và chạy lệnh sau để cài đặt Winston:
npm install winston
Lệnh này sẽ tải xuống thư viện Winston và thêm nó vào danh sách các gói phụ thuộc trong file package.json của dự án. Quá trình cài đặt thường diễn ra rất nhanh chóng.
Cấu hình cơ bản và tùy chỉnh Winston
Sau khi cài đặt thành công, bước tiếp theo là tạo một file cấu hình logger đơn giản. Bạn có thể tạo một file riêng, ví dụ logger.js, để quản lý tất cả các cấu hình liên quan đến Winston. Điều này giúp mã nguồn của bạn sạch sẽ và dễ bảo trì hơn.
Trong file logger.js, chúng ta sẽ bắt đầu với một cấu hình cơ bản để in log ra console. Winston sử dụng hai khái niệm chính là format (định dạng) và transports (phương tiện vận chuyển). Format quyết định cấu trúc của log (ví dụ: có timestamp, level, message), trong khi transports quyết định nơi log sẽ được gửi đến (ví dụ: console, file).
const winston = require('winston');
const logger = winston.createLogger({
level: 'info',
format: winston.format.combine(
winston.format.timestamp(),
winston.format.printf(info => `${info.timestamp} ${info.level}: ${info.message}`)
),
transports: [
new winston.transports.Console(),
],
});
module.exports = logger;
Trong ví dụ trên, chúng ta đã kết hợp timestamp và một định dạng in tùy chỉnh để log trở nên dễ đọc hơn. Giờ đây, bạn có thể import logger này vào bất kỳ đâu trong ứng dụng và bắt đầu ghi log.

Tạo logger với các mức log khác nhau
Một trong những tính năng mạnh mẽ nhất của Winston là khả năng phân loại log theo các mức độ (levels) khác nhau. Điều này giúp bạn kiểm soát lượng thông tin được ghi lại và dễ dàng lọc ra những thông tin quan trọng khi cần thiết.
Khái niệm về các mức log trong Winston
Winston tuân theo các mức log tiêu chuẩn được định nghĩa trong npm, được sắp xếp theo mức độ ưu tiên từ cao đến thấp. Các mức log phổ biến nhất bao gồm:
error: (mức 0) Lỗi nghiêm trọng khiến ứng dụng không thể hoạt động bình thường. Cần được xử lý ngay lập tức.warn: (mức 1) Cảnh báo về những vấn đề không mong muốn nhưng không làm sập ứng dụng.info: (mức 2) Thông tin chung về các hoạt động của ứng dụng, ví dụ: “Server started on port 3000”.http: (mức 3) Log liên quan đến các yêu cầu HTTP.verbose: (mức 4) Thông tin chi tiết hơninfo.debug: (mức 5) Thông tin gỡ lỗi chỉ dành cho môi trường phát triển.silly: (mức 6) Mức log chi tiết nhất, thường dùng cho các trường hợp gỡ lỗi rất phức tạp.
Vai trò của việc phân loại này là giúp bạn có thể cấu hình logger chỉ ghi lại các log từ một mức độ nhất định trở lên. Ví dụ, trên môi trường production, bạn có thể chỉ muốn ghi lại log từ info trở lên để tránh làm đầy ổ đĩa với các log debug không cần thiết.
Tạo logger đa mức và sử dụng trong code
Việc sử dụng các mức log khác nhau trong code rất trực quan. Sau khi đã tạo instance logger, bạn chỉ cần gọi phương thức tương ứng với mức log bạn muốn.
Dưới đây là ví dụ về cách sử dụng logger đã cấu hình ở phần trước trong một file ứng dụng chính (app.js):
const logger = require('./logger'); // Import logger đã cấu hình
logger.error('Đây là một lỗi nghiêm trọng!');
logger.warn('Đây là một cảnh báo.');
logger.info('Server đang khởi chạy thành công.');
logger.debug('Giá trị của biến x là 5.');
Khi bạn chạy ứng dụng này, vì chúng ta đã cấu hình level: 'info' trong logger, chỉ các log error, warn, và info mới được hiển thị trên console. Log debug sẽ bị bỏ qua. Để thấy log debug, bạn cần thay đổi cấu hình level thành 'debug'.
Cách tổ chức tốt nhất là tạo một module logger duy nhất và import nó vào tất cả các file khác cần ghi log. Điều này đảm bảo tính nhất quán và giúp bạn dễ dàng thay đổi cấu hình log cho toàn bộ ứng dụng chỉ ở một nơi.

Ghi log vào file và quản lý nhật ký trên Ubuntu 20.04
Trong môi trường production, việc chỉ ghi log ra console là không đủ vì thông tin sẽ mất khi terminal đóng hoặc server khởi động lại. Ghi log vào file là một yêu cầu bắt buộc để lưu trữ và phân tích lịch sử hoạt động của ứng dụng.
Cách Winston ghi log vào file chi tiết
Winston giúp việc ghi log vào file trở nên cực kỳ đơn giản thông qua File transport. Bạn chỉ cần thêm một transport mới vào cấu hình logger.
// Trong file logger.js
const logger = winston.createLogger({
// ... các cấu hình khác
transports: [
new winston.transports.Console(),
new winston.transports.File({ filename: 'app.log' })
],
});
Tuy nhiên, việc ghi log vào một file duy nhất sẽ khiến file này ngày càng lớn. Để giải quyết vấn đề này, chúng ta nên sử dụng log xoay vòng (log rotation). Thư viện winston-daily-rotate-file là một lựa chọn tuyệt vời cho việc này.
Đầu tiên, hãy cài đặt nó:
npm install winston-daily-rotate-file
Sau đó, cập nhật cấu hình logger của bạn để sử dụng transport này. Bạn có thể cấu hình xoay log theo ngày hoặc theo kích thước file.
const winston = require('winston');
require('winston-daily-rotate-file');
const transport = new winston.transports.DailyRotateFile({
filename: 'application-%DATE%.log',
dirname: 'logs', // Thư mục lưu log
datePattern: 'YYYY-MM-DD',
zippedArchive: true, // Nén file log cũ
maxSize: '20m', // Xoay log nếu file lớn hơn 20MB
maxFiles: '14d' // Giữ lại log trong 14 ngày
});
const logger = winston.createLogger({
// ...
transports: [
new winston.transports.Console(),
transport
],
});
Cấu hình này sẽ tự động tạo một file log mới mỗi ngày và nén các file log cũ, giúp bạn quản lý dung lượng ổ đĩa hiệu quả.
Quản lý và bảo trì log trên hệ điều hành Ubuntu
Khi ghi log vào file trên Ubuntu, bạn cần đảm bảo rằng tiến trình Node.js có quyền ghi vào thư mục và file log. Nếu gặp lỗi, hãy kiểm tra quyền truy cập bằng lệnh ls -l và sử dụng chmod hoặc chown để cấp quyền cần thiết.
Ngoài ra, Ubuntu cung cấp các công cụ dòng lệnh mạnh mẽ để làm việc với file log.
tail -f logs/application-2023-10-27.log: Xem log được ghi vào file theo thời gian thực.grep "ERROR" logs/application-2023-10-27.log: Tìm kiếm tất cả các dòng chứa chuỗi “ERROR” trong file log.logrotate: Một tiện ích hệ thống của Linux, cũng có thể được dùng để quản lý việc xoay vòng log như một phương án thay thế chowinston-daily-rotate-file.

Theo dõi và phân tích log hiệu quả với Winston
Sau khi đã thiết lập hệ thống ghi log, bước tiếp theo là làm thế nào để theo dõi và trích xuất thông tin hữu ích từ chúng. Việc phân tích log giúp bạn chủ động phát hiện vấn đề, tối ưu hiệu suất và hiểu rõ hơn về hành vi của người dùng.
Công cụ và kỹ thuật giúp theo dõi log trên Ubuntu
Với các ứng dụng nhỏ, việc sử dụng các lệnh Linux như tail và grep có thể là đủ. Tuy nhiên, khi hệ thống phát triển lớn hơn, bạn sẽ cần đến các công cụ chuyên dụng hơn.
- PM2: Nếu bạn đang sử dụng PM2 để quản lý tiến trình Node.js, nó đã tích hợp sẵn tính năng quản lý log rất tiện lợi. Bạn có thể dùng lệnh
pm2 logsđể xem log của tất cả các ứng dụng trong thời gian thực. - ELK Stack (Elasticsearch, Logstash, Kibana): Đây là một bộ công cụ mã nguồn mở mạnh mẽ cho việc tập trung hóa, tìm kiếm và trực quan hóa log. Winston có thể được cấu hình để gửi log ở định dạng JSON, giúp Logstash dễ dàng phân tích và đẩy vào Elasticsearch.
- Grafana và Loki: Một giải pháp hiện đại khác, tập trung vào việc thu thập log một cách hiệu quả và tích hợp chặt chẽ với hệ thống giám sát Grafana.
Để tích hợp Winston với các hệ thống này, bạn thường chỉ cần cấu hình format của logger để output ra định dạng JSON.
const logger = winston.createLogger({
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json() // Định dạng log thành JSON
),
// ...
});
Log ở định dạng JSON có cấu trúc rõ ràng, giúp các công cụ phân tích tự động “hiểu” và lập chỉ mục các trường dữ liệu như timestamp, level, message.
Lợi ích của việc phân tích log cho phát triển và vận hành
Việc đầu tư vào một hệ thống phân tích log bài bản mang lại nhiều lợi ích to lớn. Đầu tiên, nó giúp bạn phát hiện và chẩn đoán lỗi nhanh hơn rất nhiều. Thay vì phải SSH vào từng server để đọc file, bạn có thể tìm kiếm và lọc log từ tất cả các nguồn tại một giao diện duy nhất.
Thứ hai, phân tích log giúp tối ưu hóa hiệu suất ứng dụng. Bằng cách ghi lại thời gian xử lý của các tác vụ quan trọng, bạn có thể xác định các “nút thắt cổ chai” và cải thiện chúng. Cuối cùng, một hệ thống log tốt giúp cải thiện khảability bảo trì và mở rộng dự án. Khi một thành viên mới tham gia, họ có thể nhanh chóng hiểu được luồng hoạt động của ứng dụng thông qua việc đọc log.

Ví dụ thực tế áp dụng Winston trong ứng dụng Node.js
Lý thuyết sẽ trở nên dễ hiểu hơn khi được áp dụng vào một ví dụ cụ thể. Chúng ta sẽ cùng nhau xây dựng một server API đơn giản bằng Express.js và tích hợp hệ thống ghi log Winston vào đó.
Đầu tiên, hãy tạo một dự án Node.js mới và cài đặt các thư viện cần thiết:
npm init -y
npm install express winston winston-daily-rotate-file
Tiếp theo, tạo file logger.js với cấu hình ghi log ra cả console và file xoay vòng hàng ngày như đã tìm hiểu ở các phần trước.
Bây giờ, hãy tạo file app.js để xây dựng server API. Chúng ta sẽ viết log cho các sự kiện quan trọng như server khởi động, có request mới đến, và khi có lỗi xảy ra.
const express = require('express');
const logger = require('./logger'); // Import logger
const app = express();
const PORT = 3000;
// Middleware để ghi log mọi request đến
app.use((req, res, next) => {
logger.info(`Request received: ${req.method} ${req.url}`);
next();
});
app.get('/', (req, res) => {
res.send('Chào mừng đến với API!');
});
app.get('/error', (req, res) => {
try {
// Giả lập một lỗi
throw new Error('Đây là một lỗi thử nghiệm!');
} catch (e) {
logger.error(`Lỗi tại endpoint /error: ${e.message}`);
res.status(500).send('Đã có lỗi xảy ra.');
}
});
app.listen(PORT, () => {
logger.info(`Server đang chạy tại http://localhost:${PORT}`);
});
Sau khi chạy ứng dụng bằng lệnh node app.js, bạn sẽ thấy log server khởi động trên console. Bây giờ, hãy thử truy cập vào http://localhost:3000 và http://localhost:3000/error từ trình duyệt.
Để kiểm tra và đọc file log, bạn có thể mở thư mục logs trong dự án. Bạn sẽ thấy một file có tên dạng application-YYYY-MM-DD.log. Sử dụng lệnh cat hoặc tail để xem nội dung file, bạn sẽ thấy tất cả các log đã được ghi lại một cách chi tiết, bao gồm cả thông tin request và thông báo lỗi. Ví dụ này cho thấy việc tích hợp Winston vào một ứng dụng thực tế đơn giản và hiệu quả như thế nào.
![]()
Các vấn đề thường gặp và cách khắc phục
Trong quá trình triển khai Winston, bạn có thể gặp phải một số vấn đề phổ biến. Dưới đây là cách chẩn đoán và khắc phục chúng một cách hiệu quả.
Logger không ghi log ra file như mong muốn
Đây là vấn đề phổ biến nhất, đặc biệt khi triển khai lên server Ubuntu. Nguyên nhân thường gặp nhất là do quyền truy cập file hoặc sai đường dẫn.
- Nguyên nhân:
- Sai quyền truy cập: Tiến trình Node.js không có quyền ghi vào thư mục được chỉ định trong cấu hình transport.
- Sai đường dẫn: Đường dẫn đến thư mục log không tồn tại hoặc bị khai báo sai (ví dụ, nhầm lẫn giữa đường dẫn tương đối và tuyệt đối).
- Cách kiểm tra và sửa lỗi:
- Kiểm tra quyền: Dùng lệnh
ls -ld /path/to/your/logs. Đảm bảo rằng người dùng chạy tiến trình Node.js (ví dụwww-datahoặc người dùng hiện tại) có quyền ghi (w) vào thư mục này. Nếu không, hãy dùngsudo chown -R user:group /path/to/your/logsvàsudo chmod -R 755 /path/to/your/logs. - Kiểm tra đường dẫn: Hãy sử dụng đường dẫn tuyệt đối bằng cách kết hợp với
path.join(__dirname, 'logs')trong Node.js để đảm bảo đường dẫn luôn chính xác, bất kể bạn chạy ứng dụng từ đâu. - Kiểm tra lỗi: Bọc đoạn code khởi tạo logger trong một khối
try...catchđể bắt các lỗi có thể phát sinh trong quá trình thiết lập transport.
- Kiểm tra quyền: Dùng lệnh
Log quá nhiều hoặc quá ít thông tin
Đôi khi, bạn thấy log của mình quá “ồn ào” với các thông tin gỡ lỗi không cần thiết trên production, hoặc ngược lại, quá “im lặng” và không cung cấp đủ thông tin khi cần gỡ lỗi.
- Điều chỉnh mức log phù hợp: Đây là cách đơn giản nhất. Trong file cấu hình logger, hãy đặt thuộc tính
level. Một thực hành tốt là sử dụng biến môi trường để kiểm soát mức log.
const logger = winston.createLogger({
level: process.env.NODE_ENV === 'production' ? 'info' : 'debug',
// ...
});
Bằng cách này, khi ở môi trường phát triển (development), bạn sẽ thấy tất cả log debug. Khi ở môi trường sản phẩm (production), chỉ các log từ info trở lên được ghi lại.
- Sử dụng filter và format để tối ưu: Winston cho phép bạn tạo các bộ lọc (filter) tùy chỉnh. Ví dụ, bạn có thể tạo một bộ lọc để loại bỏ các log liên quan đến việc kiểm tra sức khỏe (health check) của hệ thống, vốn thường được gọi liên tục và tạo ra nhiều log không cần thiết. Bạn cũng có thể tùy chỉnh
formatđể chỉ hiển thị những trường thông tin thực sự quan trọng, giúp log gọn gàng hơn.

Best Practices khi sử dụng Winston trong Node.js
Để khai thác tối đa sức mạnh của Winston và xây dựng một hệ thống logging bền vững, hãy tuân thủ các thực hành tốt nhất sau đây. Việc áp dụng những nguyên tắc này không chỉ giúp ứng dụng của bạn dễ gỡ lỗi hơn mà còn đảm bảo tính bảo mật và hiệu quả vận hành.
- Luôn phân loại mức log rõ ràng theo mục đích: Đừng ghi mọi thứ ở mức
info. Hãy sử dụngerrorcho các lỗi thực sự,warncho các tình huống tiềm ẩn rủi ro,infocho các sự kiện hoạt động quan trọng, vàdebugcho thông tin gỡ lỗi chi tiết. - Không ghi thông tin nhạy cảm trong log: Tuyệt đối không bao giờ ghi các thông tin như mật khẩu, token API, khóa bí mật, hay thông tin cá nhân của người dùng vào log. Kẻ tấn công có thể khai thác các file log này để truy cập vào hệ thống.
- Sử dụng log xoay vòng để tiết kiệm dung lượng ổ đĩa: Luôn sử dụng một transport như
winston-daily-rotate-filetrên môi trường production. Điều này giúp ngăn chặn việc file log trở nên quá lớn, gây đầy ổ đĩa và làm chậm hiệu năng của cả server. - Đặt cấu hình mặc định phù hợp với môi trường: Sử dụng biến môi trường (environment variables) để có các cấu hình logging khác nhau cho môi trường phát triển (development) và sản phẩm (production). Ví dụ, log ra console với định dạng đơn giản ở development và log ra file với định dạng JSON ở production.
- Sử dụng định dạng JSON cho log trên production: Log có cấu trúc (structured logging) như JSON giúp các hệ thống tập trung và phân tích log (như ELK Stack, Splunk) dễ dàng phân tích, tìm kiếm và tạo cảnh báo tự động.
- Sử dụng công cụ hỗ trợ giám sát và phân tích log: Đừng chỉ ghi log rồi để đó. Tích hợp Winston với các công cụ giám sát để bạn có thể tìm kiếm, trực quan hóa dữ liệu và thiết lập cảnh báo khi có sự cố xảy ra.

Kết luận
Qua bài viết này, chúng ta đã cùng nhau khám phá một cách chi tiết về Winston và vai trò không thể thiếu của nó trong việc xây dựng một hệ thống ghi log chuyên nghiệp cho ứng dụng Node.js trên môi trường Ubuntu 20.04. Từ những bước cơ bản như cài đặt và cấu hình, cho đến các kỹ thuật nâng cao như sử dụng log đa mức độ, ghi log xoay vòng, và tích hợp với các công cụ phân tích, Winston đã chứng tỏ mình là một công cụ cực kỳ linh hoạt và mạnh mẽ.
Việc áp dụng một hệ thống logging bài bản không chỉ là một thói quen tốt mà còn là một yêu cầu thiết yếu đối với bất kỳ dự án phần mềm nghiêm túc nào. Nó giúp bạn nhanh chóng xác định nguyên nhân sự cố, theo dõi hiệu suất ứng dụng và đảm bảo hệ thống hoạt động ổn định. AZWEB khuyến khích bạn áp dụng những kiến thức trong bài viết này vào dự án của mình để nâng cao chất lượng sản phẩm và hiệu quả trong quá trình phát triển. Đừng quên khám phá thêm các bài viết khác của chúng tôi về tối ưu hóa ứng dụng Node.js và giám sát hệ thống để hoàn thiện kỹ năng của mình.