Giới thiệu
Trong các ứng dụng Backend việc xây dựng logger và xử lý exception tập trung rất quan trọng cho việc xử lý và điều tra lỗi.
NestJS đã cung cấp sẵn một số module, chúng ta có thể implement tùy ý tùy theo logic mình mong muốn.
Logger
NestJS đã cung cấp sẵn một bộ text-base logger, được sử dụng trong suốt quá trình bootstraping, một số trường hợp khác như hiển thị exception.
Basic
Trong một số trường hợp và tùy vào dự án chúng ta sử dụng một số bộ log khác như Winston:
Bạn có thể tắt, hoặc xác định log level ngay từ quá trình bootstraping ứng dụng
1 2 3 4 5 | <span class="token keyword">const</span> app <span class="token operator">=</span> <span class="token keyword">await</span> NestFactory<span class="token punctuation">.</span><span class="token function">create</span><span class="token punctuation">(</span>ApplicationModule<span class="token punctuation">,</span> <span class="token punctuation">{</span> logger<span class="token punctuation">:</span> <span class="token boolean">false</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> app<span class="token punctuation">.</span><span class="token function">listen</span><span class="token punctuation">(</span><span class="token number">3000</span><span class="token punctuation">)</span><span class="token punctuation">;</span> |
Xác định log level tại thời điểm bootstraping:
1 2 3 4 5 | <span class="token keyword">const</span> app <span class="token operator">=</span> <span class="token keyword">await</span> NestFactory<span class="token punctuation">.</span><span class="token function">create</span><span class="token punctuation">(</span>ApplicationModule<span class="token punctuation">,</span> <span class="token punctuation">{</span> logger<span class="token punctuation">:</span> <span class="token punctuation">[</span><span class="token string">'error'</span><span class="token punctuation">,</span> <span class="token string">'warn'</span><span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> app<span class="token punctuation">.</span><span class="token function">listen</span><span class="token punctuation">(</span><span class="token number">3000</span><span class="token punctuation">)</span><span class="token punctuation">;</span> |
Các log level được support: log
, error
, warn
, debug
, verbose
Custom
NestJS cung cấp trước class base là LoggerService
và Logger
, chúng ta có thể custom lại một số log level.
Ví dụ
1 2 3 4 5 6 7 8 9 | <span class="token keyword">import</span> <span class="token punctuation">{</span> Logger <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@nestjs/common'</span><span class="token punctuation">;</span> <span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">MyLogger</span> <span class="token keyword">extends</span> <span class="token class-name">Logger</span> <span class="token punctuation">{</span> <span class="token function">error</span><span class="token punctuation">(</span>message<span class="token punctuation">:</span> string<span class="token punctuation">,</span> trace<span class="token punctuation">:</span> string<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// Send stack trace to chatwork, or some others logic</span> <span class="token keyword">super</span><span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span>message<span class="token punctuation">,</span> trace<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
Xây dựng log module
DI (Dependency injection)
Như các bạn đã biết, NestJS được xây dựng theo mô hình DDD, project sẽ được chia thành các module nhỏ. Mình sẽ xây dựng một module chuyên xử lý các vấn đề về log, từ đó có thể Inject vào các module khác.
Tạo class LoggerService
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | <span class="token keyword">import</span> <span class="token punctuation">{</span> Injectable<span class="token punctuation">,</span> Logger<span class="token punctuation">,</span> Scope <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@nestjs/common'</span> @<span class="token function">Injectable</span><span class="token punctuation">(</span><span class="token punctuation">{</span> scope<span class="token punctuation">:</span> Scope<span class="token punctuation">.</span><span class="token constant">TRANSIENT</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">LoggerService</span> <span class="token keyword">extends</span> <span class="token class-name">Logger</span> <span class="token punctuation">{</span> <span class="token function">error</span><span class="token punctuation">(</span>message<span class="token punctuation">:</span> any<span class="token punctuation">,</span> trace<span class="token operator">?</span><span class="token punctuation">:</span> string<span class="token punctuation">,</span> context<span class="token operator">?</span><span class="token punctuation">:</span> string<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// TO DO</span> <span class="token keyword">super</span><span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span>message<span class="token punctuation">,</span> trace<span class="token punctuation">,</span> context<span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token function">warn</span><span class="token punctuation">(</span>message<span class="token punctuation">:</span> any<span class="token punctuation">,</span> context<span class="token operator">?</span><span class="token punctuation">:</span> string<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// TO DO</span> <span class="token keyword">super</span><span class="token punctuation">.</span><span class="token function">warn</span><span class="token punctuation">(</span>message<span class="token punctuation">,</span> context<span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token function">log</span><span class="token punctuation">(</span>message<span class="token punctuation">:</span> any<span class="token punctuation">,</span> context<span class="token operator">?</span><span class="token punctuation">:</span> string<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// TO DO</span> <span class="token keyword">super</span><span class="token punctuation">.</span><span class="token function">log</span><span class="token punctuation">(</span>message<span class="token punctuation">,</span> context<span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token function">debug</span><span class="token punctuation">(</span>message<span class="token punctuation">:</span> any<span class="token punctuation">,</span> context<span class="token operator">?</span><span class="token punctuation">:</span> string<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// TO DO</span> <span class="token keyword">super</span><span class="token punctuation">.</span><span class="token function">debug</span><span class="token punctuation">(</span>message<span class="token punctuation">,</span> context<span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token function">verbose</span><span class="token punctuation">(</span>message<span class="token punctuation">:</span> any<span class="token punctuation">,</span> context<span class="token operator">?</span><span class="token punctuation">:</span> string<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token comment">// TO DO</span> <span class="token keyword">super</span><span class="token punctuation">.</span><span class="token function">verbose</span><span class="token punctuation">(</span>message<span class="token punctuation">,</span> context<span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
Tạo module logger
1 2 3 4 5 6 7 8 9 | <span class="token keyword">import</span> <span class="token punctuation">{</span> Module <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@nestjs/common'</span> <span class="token keyword">import</span> <span class="token punctuation">{</span> LoggerService <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./logger.service'</span> @<span class="token function">Module</span><span class="token punctuation">(</span><span class="token punctuation">{</span> providers<span class="token punctuation">:</span> <span class="token punctuation">[</span>LoggerService<span class="token punctuation">]</span><span class="token punctuation">,</span> exports<span class="token punctuation">:</span> <span class="token punctuation">[</span>LoggerService<span class="token punctuation">]</span><span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">LoggerModule</span> <span class="token punctuation">{</span><span class="token punctuation">}</span> |
Application logging
Sau khi xây dựng xong Log Module, để sử dụng chúng ta chỉ cần Inject module vào bất kỳ context, controller nào
Ví dụ Inject logger vào controller
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | <span class="token keyword">import</span> <span class="token punctuation">{</span> Injectable <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@nestjs/common'</span><span class="token punctuation">;</span> <span class="token keyword">import</span> <span class="token punctuation">{</span> MyLogger <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'./my-logger.service'</span><span class="token punctuation">;</span> @<span class="token function">Injectable</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">CatsService</span> <span class="token punctuation">{</span> <span class="token keyword">private</span> readonly cats<span class="token punctuation">:</span> Cat<span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token punctuation">[</span><span class="token punctuation">]</span><span class="token punctuation">;</span> <span class="token function">constructor</span><span class="token punctuation">(</span><span class="token keyword">private</span> myLogger<span class="token punctuation">:</span> MyLogger<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span>myLogger<span class="token punctuation">.</span><span class="token function">setContext</span><span class="token punctuation">(</span><span class="token string">'CatsService'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token function">findAll</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">:</span> Cat<span class="token punctuation">[</span><span class="token punctuation">]</span> <span class="token punctuation">{</span> <span class="token keyword">this</span><span class="token punctuation">.</span>myLogger<span class="token punctuation">.</span><span class="token function">warn</span><span class="token punctuation">(</span><span class="token string">'About to return cats!'</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">return</span> <span class="token keyword">this</span><span class="token punctuation">.</span>cats<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
Exception
Sau khi đã xây dựng xong bộ logger, lúc này tiếp đến là phần Exception Handling.
Về cơ bản, tất cả những exception không được catch trong code sẽ được catch tập trung tại layer này
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | <span class="token keyword">import</span> <span class="token punctuation">{</span> ArgumentsHost<span class="token punctuation">,</span> Catch<span class="token punctuation">,</span> ExceptionFilter<span class="token punctuation">,</span> HttpException<span class="token punctuation">,</span> HttpStatus <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'@nestjs/common'</span> <span class="token keyword">import</span> <span class="token punctuation">{</span> LoggerService <span class="token punctuation">}</span> <span class="token keyword">from</span> <span class="token string">'../logger/logger.service'</span> @<span class="token function">Catch</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">AllExceptionFilter</span> <span class="token keyword">implements</span> <span class="token class-name">ExceptionFilter</span> <span class="token punctuation">{</span> <span class="token function">constructor</span><span class="token punctuation">(</span><span class="token keyword">private</span> logger<span class="token punctuation">:</span> LoggerService<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token punctuation">}</span> <span class="token keyword">catch</span><span class="token punctuation">(</span>exception<span class="token punctuation">:</span> unknown<span class="token punctuation">,</span> host<span class="token punctuation">:</span> ArgumentsHost<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">const</span> ctx <span class="token operator">=</span> host<span class="token punctuation">.</span><span class="token function">switchToHttp</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">const</span> response <span class="token operator">=</span> ctx<span class="token punctuation">.</span><span class="token function">getResponse</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">const</span> request <span class="token operator">=</span> ctx<span class="token punctuation">.</span><span class="token function">getRequest</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token keyword">let</span> message<span class="token punctuation">:</span> string <span class="token operator">|</span> Object <span class="token operator">=</span> <span class="token string">'Internal Server Error'</span> <span class="token keyword">const</span> status <span class="token operator">=</span> exception <span class="token keyword">instanceof</span> <span class="token class-name">HttpException</span> <span class="token operator">?</span> exception<span class="token punctuation">.</span><span class="token function">getStatus</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">:</span> HttpStatus<span class="token punctuation">.</span><span class="token constant">INTERNAL_SERVER_ERROR</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>exception <span class="token keyword">instanceof</span> <span class="token class-name">HttpException</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> message <span class="token operator">=</span> exception<span class="token punctuation">.</span><span class="token function">getResponse</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token keyword">else</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>exception <span class="token keyword">instanceof</span> <span class="token class-name">Error</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> message <span class="token operator">=</span> exception<span class="token punctuation">.</span>stack<span class="token punctuation">.</span><span class="token function">toString</span><span class="token punctuation">(</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token keyword">this</span><span class="token punctuation">.</span>logger<span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span>message<span class="token punctuation">)</span> response<span class="token punctuation">.</span><span class="token function">status</span><span class="token punctuation">(</span>status<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">json</span><span class="token punctuation">(</span><span class="token punctuation">{</span> statusCode<span class="token punctuation">:</span> status<span class="token punctuation">,</span> occurAt<span class="token punctuation">:</span> <span class="token keyword">new</span> <span class="token class-name">Date</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">toISOString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> path<span class="token punctuation">:</span> request<span class="token punctuation">.</span>url<span class="token punctuation">,</span> <span class="token punctuation">}</span><span class="token punctuation">)</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
Tại đây mình sẽ phân biệt 2 loại exception chính, đó là Built-in Error và HttpException
Sau khi catch được exception, sẽ tiến hành lấy error message, status code.
Ở đây mình đã inject Logger service vào class AllExceptionFilter, khi catch được exception ngoài repsone về cho phía client thì sẽ tiến hành ghi vào log.
Trên đây mình có vắn tắt lại quá trình xây dựng bộ log và xử lý exception tập trung, chi tiết các bạn có thể tham khảo tại repo:
https://github.com/hoangtm1601/nest-base