Dịch từ nguồn
Xử lí lỗi không chỉ đơn thuần là giảm thời gian tìm bug cho dev mà còn là xây dựng một codebase với quy mô tương xứng với hệ thống của bạn
Các loại lỗi trong NodeJS
Trong Node.js có 2 loại errors chính:
- Operational errors: runtime error, một số ví dụ: “Out of memory”, “An invalid input for an API endpoint”
- Programmer errors: unexpected bugs, bản thân code có những vấn đề cần phải giải quyết. Ví dụ tiêu biểu: đọc property của “undefined” object. Các bug này thường do dev tạo nên chứ không liên quan đến operation.
Xử lí lỗi
Với các lỗi đã được định nghĩa sẵn bởi Node.js, ta sẽ dễ dàng theo dõi thông tin xung quanh nó nhờ Stacktrace
→ từ đó ta có thể tìm ra nguyên nhân gốc rễ của lỗi.
Ngoài ra việc extends từ Error class
cũng như bổ sung các thuộc tính khác như HTTP status code
cũng sẽ giúp cho thông tin về lỗi trở nên chi tiết hơn
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 | <span class="token keyword">class</span> <span class="token class-name">BaseError</span> <span class="token keyword">extends</span> <span class="token class-name">Error</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">readonly</span> name<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">readonly</span> httpCode<span class="token operator">:</span> HttpStatusCode<span class="token punctuation">;</span> <span class="token keyword">public</span> <span class="token keyword">readonly</span> isOperational<span class="token operator">:</span> <span class="token builtin">boolean</span><span class="token punctuation">;</span> <span class="token function">constructor</span><span class="token punctuation">(</span> name<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">,</span> httpCode<span class="token operator">:</span> HttpStatusCode<span class="token punctuation">,</span> description<span class="token operator">:</span> <span class="token builtin">string</span><span class="token punctuation">,</span> isOperational<span class="token operator">:</span> <span class="token builtin">boolean</span><span class="token punctuation">,</span> <span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">super</span><span class="token punctuation">(</span>description<span class="token punctuation">)</span><span class="token punctuation">;</span> Object<span class="token punctuation">.</span><span class="token function">setPrototypeOf</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">,</span> <span class="token keyword">new</span><span class="token punctuation">.</span>target<span class="token punctuation">.</span>prototype<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>name <span class="token operator">=</span> name<span class="token punctuation">;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>httpCode <span class="token operator">=</span> httpCode<span class="token punctuation">;</span> <span class="token keyword">this</span><span class="token punctuation">.</span>isOperational <span class="token operator">=</span> isOperational<span class="token punctuation">;</span> Error<span class="token punctuation">.</span><span class="token function">captureStackTrace</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> <span class="token comment">// free to extend from BaseError</span> <span class="token keyword">class</span> <span class="token class-name">APIError</span> <span class="token keyword">extends</span> <span class="token class-name">BaseError</span> <span class="token punctuation">{</span> <span class="token function">constructor</span><span class="token punctuation">(</span>name<span class="token punctuation">,</span> httpCode <span class="token operator">=</span> HttpStatusCode<span class="token punctuation">.</span><span class="token constant">INTERNAL_SERVER</span><span class="token punctuation">,</span> isOperational <span class="token operator">=</span> <span class="token boolean">true</span><span class="token punctuation">,</span> description <span class="token operator">=</span> <span class="token string">'Internal Server Error'</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">super</span><span class="token punctuation">(</span>name<span class="token punctuation">,</span> httpCode<span class="token punctuation">,</span> isOperational<span class="token punctuation">,</span> descritpion<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
Một vài httpStatusCode
cơ bản có thể thêm ở đây:
1 2 3 4 5 6 7 | <span class="token keyword">export</span> <span class="token keyword">enum</span> HttpStatusCode <span class="token punctuation">{</span> <span class="token constant">OK</span> <span class="token operator">=</span> <span class="token number">200</span><span class="token punctuation">,</span> <span class="token constant">BAD_REQUEST</span> <span class="token operator">=</span> <span class="token number">400</span><span class="token punctuation">,</span> <span class="token constant">NOT_FOUND</span> <span class="token operator">=</span> <span class="token number">404</span><span class="token punctuation">,</span> <span class="token constant">INTERNAL_SERVER</span> <span class="token operator">=</span> <span class="token number">500</span><span class="token punctuation">,</span> <span class="token punctuation">}</span> |
Cách sử dụng như sau:
1 2 3 4 5 6 7 8 9 10 11 | <span class="token keyword">const</span> user <span class="token operator">=</span> <span class="token keyword">await</span> User<span class="token punctuation">.</span><span class="token function">getById</span><span class="token punctuation">(</span><span class="token number">1</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>user<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">throw</span> <span class="token keyword">new</span> <span class="token class-name">APIError</span><span class="token punctuation">(</span> <span class="token string">'NOT FOUND'</span><span class="token punctuation">,</span> HttpStatusCode<span class="token punctuation">.</span><span class="token constant">NOT_FOUND</span><span class="token punctuation">,</span> <span class="token boolean">true</span><span class="token punctuation">,</span> <span class="token string">'detailed explanation'</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> |
Xử lí lỗi bằng NodeJS một cách tập trung
Việc xây dụng một component với chức năng để xử lí lỗi sẽ giúp giảm thiểu đi việc trùng lặp code xử lí lỗi trong project. Component này chịu trách nhiệm cho việc giúp cho lỗi bắt được trở nên dễ hiểu hơn ví dụ như:
- Gửi thông báo đến system admin
- Chuyển event error đến monitoring service như Sentry.io và log chúng ra
Trước khi được gửi đến error-handling centralized thì lỗi sẽ được gửi đến error-handling middleware để tiến hành phân biệt giữa các error types
.
1 2 3 4 5 6 7 8 | <span class="token keyword">try</span> <span class="token punctuation">{</span> userSerivce<span class="token punctuation">.</span><span class="token function">addNewUser</span><span class="token punctuation">(</span>req<span class="token punctuation">.</span>body<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">then</span><span class="token punctuation">(</span><span class="token punctuation">(</span>newUser<span class="token operator">:</span> User<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> res<span class="token punctuation">.</span><span class="token function">status</span><span class="token punctuation">(</span><span class="token number">200</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">json</span><span class="token punctuation">(</span>newUser<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 function">catch</span> <span class="token punctuation">(</span><span class="token punctuation">(</span>error<span class="token operator">:</span> Error<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token function">next</span><span class="token punctuation">(</span>error<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 punctuation">}</span> |
Và Error-handling centralized sẽ trông như sau:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | <span class="token keyword">export</span> <span class="token keyword">class</span> <span class="token class-name">ErrorHandler</span> <span class="token punctuation">{</span> <span class="token keyword">public</span> <span class="token keyword">async</span> <span class="token function">handleError</span><span class="token punctuation">(</span>err<span class="token operator">:</span> Error<span class="token punctuation">)</span><span class="token operator">:</span> <span class="token builtin">Promise</span><span class="token operator"><</span><span class="token keyword">void</span><span class="token operator">></span> <span class="token punctuation">{</span> <span class="token keyword">await</span> logger<span class="token punctuation">.</span><span class="token function">error</span><span class="token punctuation">(</span> <span class="token string">'Error message from the centralized error-handling component'</span><span class="token punctuation">,</span> err<span class="token punctuation">,</span> <span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> <span class="token function">sendToSlack</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">await</span> <span class="token function">sendEventsToSentry</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">public</span> <span class="token function">isTrustedError</span><span class="token punctuation">(</span>error<span class="token operator">:</span> Error<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">if</span> <span class="token punctuation">(</span>error instanceOf BaseError<span class="token punctuation">)</span> <span class="token punctuation">{</span> <span class="token keyword">return</span> error<span class="token punctuation">.</span>isOperational<span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token keyword">return</span> <span class="token boolean">false</span><span class="token punctuation">;</span> <span class="token punctuation">}</span> <span class="token punctuation">}</span> |
Để dev có thể theo dõi bug một cách dễ dàng hơn, hãy tiến hành log error ra theo một format dễ nhìn nhất.
Một vài logger formatter tiêu biểu như:
Hai thư viện này sẽ giúp cung cấp log ở các format level khác nhau tuỳ theo level của error.
Với các Programmer errors
, cách giải quyết tốt nhất đó là
B1. Crash app ngay lập tức
B2. Restart lại app với các tool như pm2
Nguyên nhân là bởi Programmer errors
thường sẽ làm cho app kết thúc với một state không như mong muốn.
1 2 3 4 5 6 7 | process<span class="token punctuation">.</span><span class="token function">on</span><span class="token punctuation">(</span><span class="token string">'uncaughtException'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span>error<span class="token operator">:</span> Error<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> errorHandler<span class="token punctuation">.</span><span class="token function">handleError</span><span class="token punctuation">(</span>error<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>errorHandler<span class="token punctuation">.</span><span class="token function">isTrustedError</span><span class="token punctuation">(</span>error<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> process<span class="token punctuation">.</span><span class="token function">exit</span><span class="token punctuation">(</span><span class="token number">1</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 punctuation">;</span> |
Với promies rejection ta có thể làm như sau:
1 2 3 4 5 6 7 8 9 10 11 | <span class="token comment">// get the unhandled rejection and throw it to another fallback handler we already have.</span> process<span class="token punctuation">.</span><span class="token function">on</span><span class="token punctuation">(</span><span class="token string">'unhandledRejection'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span>reason<span class="token operator">:</span> Error<span class="token punctuation">,</span> promise<span class="token operator">:</span> <span class="token builtin">Promise</span><span class="token operator"><</span><span class="token builtin">any</span><span class="token operator">></span><span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> <span class="token keyword">throw</span> reason<span class="token punctuation">;</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span> process<span class="token punctuation">.</span><span class="token function">on</span><span class="token punctuation">(</span><span class="token string">'uncaughtException'</span><span class="token punctuation">,</span> <span class="token punctuation">(</span>error<span class="token operator">:</span> Error<span class="token punctuation">)</span> <span class="token operator">=></span> <span class="token punctuation">{</span> errorHandler<span class="token punctuation">.</span><span class="token function">handleError</span><span class="token punctuation">(</span>error<span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token keyword">if</span> <span class="token punctuation">(</span><span class="token operator">!</span>errorHandler<span class="token punctuation">.</span><span class="token function">isTrustedError</span><span class="token punctuation">(</span>error<span class="token punctuation">)</span><span class="token punctuation">)</span> <span class="token punctuation">{</span> process<span class="token punctuation">.</span><span class="token function">exit</span><span class="token punctuation">(</span><span class="token number">1</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 punctuation">;</span> |