Winston là một giải pháp ghi nhật ký (logging) đa năng cho Node.js, hỗ trợ nhiều tùy chọn lưu trữ, cấp độ logging, truy vấn log và có profiler tích hợp sẵn. Bài viết này sẽ hướng dẫn cách sử dụng Winston để log các ứng dụng Node.js trên Ubuntu 20.04.
Bước 1: Tạo một ứng dụng Node/Express đơn giản
Sự hiệu quả của các ứng dụng phụ thuộc rất lớn vào phương pháp ghi nhật ký (logging). Winston nổi tiếng là một thư viện logging đa năng, có sẵn cho các ứng dụng Node.js. Qua bài viết dưới đây, bạn đọc có thể biết được cách sử dụng Winston cơ bản, đồng thời kết hợp Winston với Morgan – một logger trung gian cho các HTTP request. Trước khi bắt đầu, bạn đọc cần cài sẵn Ubuntu 20.04, user non-root có quyền sudo và Node.js cài bằng PPA chính thức. (Hướng dẫn chi tiết về cách cài đặt Node.js trên Ubuntu 20.04 tại đây)
Winston thường dc dùng để logging sự kiện từ các ứng dụng web được xây dựng bằng Node.js. Ở bước đầu tiên, bạn sẽ tạo một ứng dụng web Node.js đơn giản bằng framework Express thông qua lệnh express-generator
.
Nếu đã cài sẵn Node Package Manager thì bạn có thể dùng lệnh npm
để cài đặt express-generator
:
sudo npm install express-generator -g
Trong đó, flag -p
dùng để cài đặt package trên toàn cục, vì vậy có thể được dùng như một công cụ command-line bên ngoài một module Node.
Tiếp theo, tạo một ứng dụng bằng lệnh express
, theo sau là tên thư mục muốn sử dụng cho project (trong ví dụ này sẽ lấy tên là myApp
):
express myApp
Sau đó bạn cần cài đặt Nodemon, dùng để tự động reload lại ứng dụng mỗi khi có thay đổi. Các ứng dụng Node.js luôn cần được restart sau mỗi lần chỉnh sửa mã nguồn để áp dụng các thay đổi này. Ngoài ra bạn cũng sẽ sử dụng nodemon
như một công cụ command-line, vì thế hãy dùng flag -g
trong lệnh cài đặt như sau:
sudo npm install nodemon -g
Để hoàn tất quá trình cài đặt, chuyển sang thư mục của ứng dụng rồi cài đặt các dependency:
cd myApp
npm install
Theo mặc định, các ứng dụng được tạo bằng lệnh express-generator
sẽ chạy trên cổng 3000
, do đó hãy đảm bảo rằng firewall đang không chặn cổng này bằng cách chạy lệnh sau:
sudo ufw allow 3000
Bây giờ bạn đã sẵn sàng khởi động ứng dụng web:
nodemon bin/www
Lệnh này sẽ khởi động một ứng dụng trên cổng 3000
. Bạn có thể kiểm tra bằng cách trỏ trình duyệt đến http://your_server_ip:3000
. Khi đó màn hình sẽ hiển thị như sau:
Trong phần còn lại của hướng dẫn, bạn có thể mở một phiên SSH thứ hai đến server, không động chạm đến ứng dụng web vừa mở ở trên. Phiên SSH ban đầu sẽ được tạm gọi là phiên A, các lệnh trong phiên này sẽ có tên “session-A
” ở trên:
nodemon bin/www
Bây giờ bạn sử dụng phiên SSH mới (tạm gọi là phiên B) để chạy các lệnh và chỉnh sửa file. Các câu lệnh không có tên phiên ở phía trên sẽ mặc định được hiểu là chạy trong phiên B:
cd ~/myApp
Bước 2: Tùy chỉnh biến logging
Cấu hình mặc định được tạo bởi express-generator
vẫn đủ cho các mục đích sử dụng thông thường, tuy nhiên bạn vẫn nên tùy chỉnh ứng dụng để gọi đúng logger khi cần thiết. express-generator
bao gồm nhiều middleware HTTP logging của Morgan, dùng để log dữ liệu liên quan đến HTTP request. Vì Morgan hỗ trợ các luồng output nên có thể kết hợp tốt với khả năng hỗ trợ luồng của Winston. Từ đó người dùng có thể kết hợp các logging HTTP request với bất kỳ thành phần nào trong Winston.
express-generator
sử dụng biến logger
khi tham chiếu đến package morgan
. Bạn sử dụng cả morgan
lẫn winston
, cả hai đều là các gói logging, nên việc dùng biến logger
sẽ dễ gây nhầm lẫn. Để chỉ định biến cụ thể muốn sử dụng, bạn có thể thay đổi cách khai báo biến trong file app.js.
Mở file app.js
bằng một text editor bất kỳ:
nano ~/myApp/app.js
Tìm dòng sau đây:
...
var logger = require('morgan');
...
Sau đó đổi tên biến logger
thành morgan
:
...
var morgan = require('morgan');
...
Bây giờ, biến morgan
sẽ gọi phương thức require()
được liên kết đến request logger của Morgan. Tương tự, tìm các tham chiếu đến logger có trong file rồi đổi tất cả thành morgan
. Bên cạnh đó, bạn cũng cần đổi định dạng log dùng bởi package morgan
thành combined
. Đây là định dạng log tiêu chuẩn của Apache, chứa nhiều thông tin hữu ích như địa chỉ IP remote hay header của HTTP request từ user-agent.
...
app.use(logger('dev'));
...
Đổi thành như dưới đây:
...
app.use(morgan('combined'));
...
Các thay đổi này sẽ giúp ta biết được gói logging nào đang được tham chiếu.
Cuối cùng, lưu rồi đóng file lại.
Bước 3: Cài đặt và cấu hình Winston
Cài đặt winston
bằng lệnh sau:
cd ~/myApp
npm install winston
Tạo một folder config
để chứa các cấu hình của winston
:
mkdir ~/myApp/config
Sau đó tạo một folder chứa file log:
mkdir ~/myApp/logs
Cuối cùng là cài đặt app-root-path
:
npm install app-root-path --save
Gói app-root-path
mặc dù không liên quan trực tiếp đến Winston, nhưng rất hiệu quả khi cần xác định đường dẫn đến file trong Node.js. Bạn sẽ chủ yếu dùng package này để chỉ định vị trí của file log Winston từ root của project để tránh các cú pháp đường dẫn phức tạp.
Tiép theo, định nghĩa các thiết lập bằng cách tạo và mở file ~/myApp/config/winston.js
để edit:
nano ~/myApp/config/winston.js
File winston.js
chứa cấu hình của winston
.
Tiếp theo, thêm đoạn code sau để yêu cầu các gói app-root-path
và winston
:
const appRoot = require('app-root-path');
const winston = require('winston');
Bây giờ bạn có thể định nghĩa các thiết lập cấu hình cho transport. Transport là một khái niệm được định nghĩa bởi Winston, chỉ cơ chế storage/out dùng cho log. Winston có bốn lại transport chính: Console, File, HTTP và Stream.
Trong hướng dẫn này, bạn sẽ chủ yếu tập trung vào console và file trong transport. Console sẽ log các thông tin vào console, và tương tự thì file sẽ log thông tin vào một file cụ thể. Mỗi định nghĩa của transport có thể chứa các thiết lập cấu hình nhất định, chẳng hạn như kích thước file, cấp độ log hay đinh dạng log.
Dưới đây là bảng tóm tắt một số thiết lập thường sử dụng:
⭐️ level | ✓ Mức độ log |
⭐️ filename | ✓ File ghi dữ liệu log |
⭐️ handleExceptions | ✓ Bắt và ghi log ngoại lệ không được xử lý |
⭐️ maxsize | ✓ Kích thước file log tối đa (đơn vị: byte), trước khi một file mới được tạo |
⭐️ maxFiles | ✓ Giới hạn số lượng file được tạo khi vượt quá kích thước file log |
⭐️ format | ✓ Định dạng độ dài output của log |
Trong đó, cấp độ logging cho biết mức độ ưu tiên của thông báo, được biểu thị bằng một số nguyên. Winston sử dụng thang đo từ 0
đến 6
(theo thứ tự cao nhất đến thấp nhất):
- 0: error
- 1: warn
- 2: info
- 3: http
- 4: verbose
- 5: debug
- 6: silly
Khi chỉ định cấp độ logging cho một transport cụ thể, mọi thứ ở cấp độ ngang bằng hoặc cao hơn đều sẽ được log. Giả sử bạn đặt mức độ là info
, thì các dữ liệu ở cấp error
, warn
và info
đều được log.
Các mức độ log được chỉ định khi gọi logger, tức là bạn có thể dùng lệnh như sau để ghi lại lỗi: ogger.error('test error message')
.
Ở trong file cấu hình, thêm đoạn code sau để định nghĩa thiết lập cấu hình cho các transport file và console:
...
// define the custom settings for each transport (file, console)
const options = {
file: {
level: "info",
filename: `${appRoot}/logs/app.log`,
handleExceptions: true,
maxsize: 5242880, // 5MB
maxFiles: 5,
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json()
),
},
console: {
level: "debug",
handleExceptions: true,
format: winston.format.combine(
winston.format.colorize(),
winston.format.simple()
),
},
};
Tiếp theo, thêm đoạn code này để khởi tạo một logger winston
mới với các transport file và console, dùng các thuộc tính được định nghĩa trong biến options
:
...
// instantiate a new Winston Logger with the settings defined above
const logger = winston.createLogger({
transports: [
new winston.transports.File(options.file),
new winston.transports.Console(options.console),
],
exitOnError: false, // do not exit on handled exceptions
});
Theo mặc định thì morgan
chỉ output ra console, do đó bạn cần định nghĩa một hàm stream để viết output vào file log. Ở đây bạn dùng mức độ logging là info để lấy output từ cả hai transport (file và console). Tương tự, thêm đoạn code này vào file config:
...
// create a stream object with a 'write' function that will be used by `morgan`
logger.stream = {
write: function(message, encoding) {
// use the 'info' log level so the output will be picked up by both
// transports (file and console)
logger.info(message);
},
};
Cuối cùng là thêm đoạn code này để export logger, sau đó có thể được dùng cho các phần khác của ứng dụng:
...
module.exports = logger;
Bây giờ file config của winston
sẽ có dạng như dưới đây:
const appRoot = require("app-root-path");
const winston = require("winston");
// define the custom settings for each transport (file, console)
const options = {
file: {
level: "info",
filename: `${appRoot}/logs/app.log`,
handleExceptions: true,
maxsize: 5242880, // 5MB
maxFiles: 5,
format: winston.format.combine(
winston.format.timestamp(),
winston.format.json()
),
},
console: {
level: "debug",
handleExceptions: true,
format: winston.format.combine(
winston.format.colorize(),
winston.format.simple()
),
},
};
// instantiate a new Winston Logger with the settings defined above
const logger = winston.createLogger({
transports: [
new winston.transports.File(options.file),
new winston.transports.Console(options.console),
],
exitOnError: false, // do not exit on handled exceptions
});
// create a stream object with a 'write' function that will be used by `morgan`
logger.stream = {
write: function (message, encoding) {
// use the 'info' log level so the output will be picked up by both
// transports (file and console)
logger.info(message);
},
};
module.exports = logger;
Cuối cùng là lưu rồi đóng lại file.
Sử dụng Winston để log các ứng dụng Node.js trên Ubuntu 20.04 giúp bạn quản lý log hiệu quả, tùy chỉnh linh hoạt và định vị sự cố một cách nhanh chóng. Bên cạnh đó, những yếu tố như tài nguyên phần cứng, băng thông mạng, hỗ trợ kỹ thuật và khả năng tích hợp/mở rộng của VPS cũng sẽ ảnh hưởng đến quá trình phát triển và duy trì ứng dụng của bạn rất nhiều.
Hiện tại Vietnix đang cung cấp các gói VPS tốc độ cao, đa dạng cấu hình, dễ dàng mở rộng, bảo mật cao gồm: VPS NVMe, VPS Giá Rẻ, VPS Phổ Thông, VPS Cao Cấp và VPS GPU với nhiều mức giá khác nhau giúp bạn tạo ra trải nghiệm tốt hơn cho người dùng.
Nhanh tay liên hệ Vietnix để được tư vấn gói VPS tốc độ cao phù hợp với nhu cầu ngay hôm nay.
Bước 4: Tích hợp Winston vào ứng dụng
Sau khi cấu hình xong logger, ứng dụng vẫn chưa hề biết đến sự tồn tại của nó. Do đó ở bước này bạn cần tích hợp logger với ứng dụng.
Ở bước 2, cấu hình express nằm trong app.js
, do đó bạn có thể import logger vào file này. Trước tiên, mở file để edit:
nano ~/myApp/app.js
Sau đó khai báo biến winston
ở phần trên file:
...
var winston = require('./config/winston');
...
Tìm dòng app.use(morgan('combined'));
trong file rồi thêm option stream
vào:
...
app.use(morgan('combined', { stream: winston.stream }));
...
Cuối cùng là lưu rồi đóng file lại.
Bước 5: Truy cập dữ liệu log và ghi lại các thông báo log tùy chỉnh
Sau khi cấu hình xong ứng dụng, bạn có thể bắt đầu quan sát các dữ liệu log. Ở bước cuối cùng này, bạn sẽ review một số log và cập nhật thiết lập bằng một thông báo log tùy chỉnh.
Nếu reload trang trong trình duyệt, bạn sẽ thấy output như sau trong console của SSH phiên A:
Output
[nodemon] restarting due to changes...
[nodemon] starting `node bin/www`
info: ::1 - - [25/Apr/2022:18:10:55 +0000] "GET / HTTP/1.1" 200 170 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36"
info: ::1 - - [25/Apr/2022:18:10:55 +0000] "GET /stylesheets/style.css HTTP/1.1" 304 - "http://localhost:3000/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36"
Có hai entry của log: Đầu tiên là cho request đến trang HTML; thứ hai là cho stylesheet liên quan. Mỗi transport được cấu hình để xử lý dữ liệu log ở cấp độ info, do đó bạn nên xem thông tin tương tự trong file transport, nằm ở ~/myApp/logs/app.log
.
Chạy lệnh sau để xem nội dung file log:
tail ~/myApp/logs/app.log
Trong đó tail sẽ hiển thị phần cuối của file ra terminal.
Output:
{"level":"info","message":"::1 - - [25/Apr/2022:18:10:55 +0000] \"GET / HTTP/1.1\" 304 - \"-\" \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36\"\n","timestamp":"2022-04-25T18:10:55.573Z"}
{"level":"info","message":"::1 - - [25/Apr/2022:18:10:55 +0000] \"GET /stylesheets/style.css HTTP/1.1\" 304 - \"http://localhost:3000/\" \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36\"\n","timestamp":"2022-04-25T18:10:55.588Z"}
Output của transport file sẽ được viết dưới dạng một đối tượng JSON vì bạn đã dùng winston.format.json()
trong option format
.
Tính đến bây giờ thì logger chỉ đang ghi lại các HTTP request và những dữ liệu liên quan. Trong tương lai, bạn có thể sẽ cần ghi lại cả các thông báo log tùy chỉnh, ví dụ như ghi lại lỗi hay ghi đánh giá hiệu suất truy vấn cơ sở dữ liệu. Khi đó bạn có thể gọi logger từ tuyến xử lý lỗi. Theo mặc định thì package express-generator
có sẵn hai tuyến là 404
và 500
. Vì vậy bạn sẽ làm việc với hai tuyến này.
Mở file ~/myApp/app.js
:
nano ~/myApp/app.js
Tìm ở phần dưới cùng của file có đoạn code như sau:
...
// error handler
app.use(function(err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
// render the error page
res.status(err.status || 500);
res.render('error');
});
...
Phần này là tuyến xử lý lỗi cuối cùng, sẽ gửi tin phản hồi về cho client. Mọi lỗi ở phía server đều sẽ chạy qua tuyến này, vì vậy bạn nên thêm một logger winston
ở đây.
Cả hai transport đều sẽ được cấu hình để dùng thông báo log ở cấp độ error. Một số thông tin khác có thể đi đi kèm trong log gồm có:
err.status
: Mã trạng thái lỗi HTTP, nếu không có thì mặc định là500
.err.message
: Chi tiết lỗi.req.originalUrl
: URL được request.req.path
: Phần đường dẫn của request URL.req.method
: Phương thức HTTP của request (GET, POST, PUT,…)req.ip
: Địa chỉ IP remote của request.
Cập nhật tuyến xử lý lỗi để bao gồm cả logging winston:
...
// error handler
app.use(function(err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
// include winston logging
winston.error(
`${err.status || 500} - ${err.message} - ${req.originalUrl} - ${req.method} - ${req.ip}`
);
// render the error page
res.status(err.status || 500);
res.render('error');
});
...
Sau đó lưu rồi đóng file lại.
Để kiểm tra quá trình, bây giờ bạn có thể truy cập thử một trang không tồn tại trong project. Việc này sẽ dẫn đến lỗi 404.
Trong trình duyệt web, load URL http://your_server_ip:3000/foo
. Khi đó hệ thống sẽ trả về lỗi như sau:
Nếu kiểm tra console trong SSH của phiên A sẽ quan sát thất một entry cho log của lỗi, được in màu nhờ định dạng colorize
.
Output
[nodemon] starting `node bin/www`
error: 404 - Not Found - /foo - GET - ::1
info: ::1 - - [25/Apr/2022:18:08:33 +0000] "GET /foo HTTP/1.1" 404 982 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36"
info: ::1 - - [25/Apr/2022:18:08:33 +0000] "GET /stylesheets/style.css HTTP/1.1" 304 - "http://localhost:3000/foo" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36"
Dùng lệnh tail
để kiểm tra các bản ghi mới:
tail ~/myApp/logs/app.log
Output:
{"level":"error","message":"404 - Not Found - /foo - GET - ::1","timestamp":"2022-04-25T18:08:33.508Z"}
Thông báo này có tất cả dữ liệu bạn đã yêu cầu winston thực hiện logging: trạng thái lỗi (404 – Not Found), URL được yêu cầu (localhost/foo
), phương thức request (GET
), địa chỉ IP thực hiện request và thời gian thực hiện request.
Qua hơn 10 năm hoạt động, Vietnix hiện đang là một trong những nhà cung cấp dịch vụ VPS tốc độ cao chất lượng, uy tín hàng đầu Việt Nam. Với sự đầu tư liên tục về hạ tầng và nhân sự để có thể nhanh chóng đáp ứng được các tiêu chuẩn khắt khe của thị trường hosting, VPS,… quốc tế, Vietnix hiện đã được hơn 50.000+ khách hàng trong lẫn ngoài nước tin tưởng trong đó có thể kể đến như: iVIVU.com, GTV, Vietnamwork, UBGroup, KINGFOOD,…
Năm 2022 vừa qua, Vietnix vinh dự nhận được giải thưởng “Thương hiệu Việt Nam xuất sắc 2022”, hạng mục “Sản phẩm dịch vụ xuất sắc”.
Nhanh tay liên hệ Vietnix để được tư vấn chi tiết về dịch vụ VPS tốc độ cao ngay hôm nay!
- Địa chỉ: 265 Hồng Lạc, Phường 10, Quận Tân Bình, Thành Phố Hồ Chí Minh.
- Hotline: 1800 1093 – 07 088 44444
- Email: sales@vietnix.com.vn
Qua bài viết về cách sử dụng Winston để log các ứng dụng Node.js trên Ubuntu 20.04 này, bạn đã xây dựng một ứng dụng web Node.js đơn giản và tích hợp nó với một giải pháp logging vô cùng phổ biến hiện nay là Winston. Nếu có bất kỳ thắc mắc nào khác, hãy để lại ở phần comment bên dưới để được Vietnix hỗ trợ nhanh nhất nhé.