node錯誤處理與日誌

李諾發表於2019-02-16

node專案中的錯誤處理

node中Error物件的使用

使用captureStackTrace方法加入自帶的錯誤資訊

// Error物件自帶的屬性
Error.captureStackTrace

// 如何使用captureStackTrace
var obj = {
    message: `something is wrong`
}

Error.captureStackTrace(obj)

throw obj    // 此時會丟擲obj物件的message內資訊

使用try catch捕獲錯誤

直接把程式碼寫在try catch中即可捕獲錯誤資訊

try{
    throw new Error(`oh no`)
}catch(e){
    console.log(e)
}

非同步程式碼中,直接try catch是無法捕獲錯誤資訊的,可以使用如下方法

function foo(params, cb){
    const error = new Error(`something is wrong`)
    if(error) cb(error)
}

以上使用callback方式來做錯誤處理比較容易麻煩,容易出錯,現在node已經支援async await所以儘量使用它們準沒錯

async function foo(){
    try{
        await bar()
    }catch(e){
        console.log(e)
    }
}

async function bar(){
    throw new Error(`async function got wrong)
}

foo()

基本錯誤型別

在專案會有多個地方對錯誤資訊進行處理,所以先寫一個基本錯誤型別,方便使用

// 基本錯誤型別
class HttpBaseError extends Error {
  constructor(httpStatusCode, httpMsg, errCode, msg) {
    super(`HTTP ERROR: ${msg}`);
    this.httpStatusCode = httpStatusCode;
    this.httpMsg = httpMsg;
    this.errCode = errCode;
  }
}

try {
// 直接丟擲定義好的錯誤即可
  throw new HttpBaseError(404, `資源不存在`, 10000, `resouse is not found`);
} catch (e) {
  console.log(e.message);
  console.log(e.httpStatusCode);
  console.log(e.httpMsg);
  console.log(e.errCode);
}

特定錯誤型別

除了基本型別,不同情況下會有不同錯誤資訊,需要用一個特定的錯誤型別來處理特定的錯誤資訊

// 一個引數錯誤型別
const ERROR_CODE = 40000    // 錯誤碼
class HttpRequestParamError extends HttpBaseError {
    constructor(paramName, desc, msg) {
        super(200, desc, ERROR_CODE, `${paramName} wrong: ${msg}`)
    }
}

這樣,在引數錯誤的地方就能非常方便的呼叫這個錯誤型別來返回錯誤

拋錯的邏輯

錯誤處理中,model,controller中的錯誤,有些是不能直接返回給使用者的,應該只返回給model或controller的呼叫者。

使用錯誤處理

正常介面,controller,model的錯誤,使用設定好的錯誤型別進行處理,例如前面寫的HttpRequestParamError,在所有所有路由的最後,需要使用一個error handler來對所有的錯誤進行集中處理

// error handler
function handler(options) {
    return function (err, req, res, next) {
        if (err instanceof HttpRequestParamError) {    // 這裡對不同的錯誤做不同的處理
            console.log(`http request error`)
            res.statusCode = err.httpStatusCode
            res.json({
                code: err.errCode,
                msg: err.httpMsg
            })
        } else {
            // 設定之外的錯誤,把管理權向外移交
            next(err)
        }
    }
}

除了可預知的錯誤,還有未知的型別的錯誤,此時需要一個unknow error handler進行剩餘錯誤的處理

function unKnowErrorHandler(options) {
    return function (err, req, res, next) {
        console.log(err)
        res.json({
            code: 99999,
            msg: `unKnow error`
        })
    }
}

node中的日誌

平時使用console來debug是沒有問題的,但是線上上環境,我們並不能有效的看到console,使用日誌系統可以更好的方便線上的debug,記錄資訊等

winston的使用

winston是node中常用的日誌外掛

const winston = require(`winston`)

const logger = winston.createLogger({
    transports: [
        new winston.transports.Console(),
        new winston.transports.File({
            name: `info_logger`,    // log名稱
            filename: `logs/info.log`,    // 日誌記錄檔案地址
            level: `info` // 設定log的型別
        }),
        // 第二個logger,記錄error級別的log
        new winston.transports.File({
            name: `error_logger`,
            filename: `logs/error.log`,
            level: `error`
        })
    ]
});

// error級別比info要高,error.log檔案只會記錄error日誌
logger.error(`first error log with winston`)
// info檔案內會記錄info級別的log和比info級別高的log,比如error
logger.info(`first info log with winston`)

日誌滾動(log rotation)

在產生大量資料的應用當中,日誌的輸出是大量的,這是就需要對日誌進行拆分處理,例如按照每天的頻率來分別記錄日誌。

winston並不自帶log rotation,需要引入winston-daily-rotate-file

const {
    createLogger,
    format,
    transports
} = require(`winston`);
const {
    combine,
    timestamp,
    label,
    prettyPrint
} = format;
require(`winston-daily-rotate-file`)


var transport = new(transports.DailyRotateFile)({
    filename: `./logs/app-%DATE%.log`,
    datePattern: `YYYY-MM-DD-HH`,
    maxSize: `20m`,
    maxFiles: `14d`,
    format: combine(
        label({
            label: `right meow!`
        }),
        timestamp(),
        prettyPrint()
    ),
});
transport.on(`rotate`, function (oldFilename, newFilename) {});

var logger = createLogger({
    transports: [
        transport
    ]
});

logger.info(`Hello World!`);

執行日誌檔案,此時在logs目錄下就生成了今天的日誌
歡迎訪問我的部落格

相關文章