Обробка винятків з найкращих практик Node.js


755

Я тільки почав випробовувати node.js кілька днів тому. Я зрозумів, що Вузол припиняється всякий раз, коли в моїй програмі є необроблений виняток. Це відрізняється від звичайного контейнера сервера, до якого я потрапив, де тільки Worker Thread гине, коли трапляються необроблені винятки, і контейнер все ще зможе отримати запит. Це викликає кілька питань:

  • Є process.on('uncaughtException') єдиний ефективний спосіб уберегтись від цього?
  • Буде process.on('uncaughtException') вдасться потрапити на необроблений виняток і під час виконання асинхронних процесів?
  • Чи є вже побудований модуль (наприклад, надсилання електронної пошти чи запис у файл), який я міг би використати у випадку невиконання винятків?

Буду вдячний за будь-який вказівник / статтю, яка б показала мені загальні найкращі практики поводження з викривленими винятками в node.js


11
неприховані винятки не повинні відбуватися. Якщо вони все-таки використовують програму, яка перезавантажує всю вашу програму при її збої (nodemon, forever, supervisor)
Raynos,

116
Неперехваченние виключення завжди може статися , якщо ви поклали кожен шматок вашого асинхронного коду try .. catch, і перевірити це також зроблено для всіх ваших LIBS
Dan

13
+1 Ден Спочатку я подумав, що всі ваші лінзи трохи перебільшують, тому що вам потрібно "лише" загорнути всі свої "точки входу в нитку" в коді при спробі / лові. Але думати про це більш ретельно, будь Lib може мати setTimeoutабо setIntervalабо що - то в цьому роді поховані де - то глибоко , який не може бути спійманої вашим кодом.
Євген Бересовський

8
@EugeneBeresovksy Dan має рацію, але це не змінює факту, що при появі uncaughtExceptions єдиним безпечним варіантом є перезапуск програми. Іншими словами, ваш додаток вийшов з ладу, і ви нічого цього не можете зробити або зробити. Якщо ви хочете зробити щось конструктивне, реалізуйте нову і досі експериментальну функцію домену v0.8, щоб ви могли зареєструвати збій та надіслати відповідь 5xx своєму клієнту.
ostergaard

1
@Dan Навіть увімкнення всіх функцій зворотного виклику в спробі .. catch не гарантує помилок вловлювання. У випадку, якщо необхідний модуль використовує власні бінарні файли, вони можуть невдало вийти з ладу. У мене це траплялося з phantomjs-вузлом, не вдається виявити помилки, які неможливо зловити (якщо тільки я не повинен був зробити якусь перевірку процесу на потрібних бінарних файлах, але я цього ніколи не проводив).
Trindaz

Відповіді:


737

Оновлення: Joyent тепер має власний путівник . Наступна інформація є більш підсумковою:

Безпечно «кидаючи» помилки

В ідеалі ми хотіли б максимально уникнути неприхованих помилок, тому як замість того, щоб буквально викидати помилку, ми можемо натомість безпечно "кинути" помилку, використовуючи один із наступних методів залежно від нашої архітектури коду:

  • Для синхронного коду, якщо трапилася помилка, поверніть помилку:

    // Define divider as a syncrhonous function
    var divideSync = function(x,y) {
        // if error condition?
        if ( y === 0 ) {
            // "throw" the error safely by returning it
            return new Error("Can't divide by zero")
        }
        else {
            // no error occured, continue on
            return x/y
        }
    }
    
    // Divide 4/2
    var result = divideSync(4,2)
    // did an error occur?
    if ( result instanceof Error ) {
        // handle the error safely
        console.log('4/2=err', result)
    }
    else {
        // no error occured, continue on
        console.log('4/2='+result)
    }
    
    // Divide 4/0
    result = divideSync(4,0)
    // did an error occur?
    if ( result instanceof Error ) {
        // handle the error safely
        console.log('4/0=err', result)
    }
    else {
        // no error occured, continue on
        console.log('4/0='+result)
    }
  • Для коду, заснованого на зворотному виклику (тобто асинхронного), перший аргумент зворотного виклику - errякщо помилка трапляється err- це помилка, якщо помилка не відбувається, то errє null. Будь-які інші аргументи слідують за errаргументом:

    var divide = function(x,y,next) {
        // if error condition?
        if ( y === 0 ) {
            // "throw" the error safely by calling the completion callback
            // with the first argument being the error
            next(new Error("Can't divide by zero"))
        }
        else {
            // no error occured, continue on
            next(null, x/y)
        }
    }
    
    divide(4,2,function(err,result){
        // did an error occur?
        if ( err ) {
            // handle the error safely
            console.log('4/2=err', err)
        }
        else {
            // no error occured, continue on
            console.log('4/2='+result)
        }
    })
    
    divide(4,0,function(err,result){
        // did an error occur?
        if ( err ) {
            // handle the error safely
            console.log('4/0=err', err)
        }
        else {
            // no error occured, continue on
            console.log('4/0='+result)
        }
    })
  • Для насичених подій коди, де помилка може статися в будь-якому місці, замість того , щоб кидати помилку, вогонь errorподії замість :

    // Definite our Divider Event Emitter
    var events = require('events')
    var Divider = function(){
        events.EventEmitter.call(this)
    }
    require('util').inherits(Divider, events.EventEmitter)
    
    // Add the divide function
    Divider.prototype.divide = function(x,y){
        // if error condition?
        if ( y === 0 ) {
            // "throw" the error safely by emitting it
            var err = new Error("Can't divide by zero")
            this.emit('error', err)
        }
        else {
            // no error occured, continue on
            this.emit('divided', x, y, x/y)
        }
    
        // Chain
        return this;
    }
    
    // Create our divider and listen for errors
    var divider = new Divider()
    divider.on('error', function(err){
        // handle the error safely
        console.log(err)
    })
    divider.on('divided', function(x,y,result){
        console.log(x+'/'+y+'='+result)
    })
    
    // Divide
    divider.divide(4,2).divide(4,0)

Безпечно «ловити» помилки

Хоча іноді все ж може бути код, який десь видає помилку, яка може призвести до невдалого винятку та потенційної аварії нашої програми, якщо ми не будемо її безпечно спіймати. Залежно від нашої архітектури коду, ми можемо використовувати один із наступних методів, щоб знайти його:

  • Коли ми знаємо, де виникає помилка, ми можемо перенести цей розділ у домен node.js

    var d = require('domain').create()
    d.on('error', function(err){
        // handle the error safely
        console.log(err)
    })
    
    // catch the uncaught errors in this asynchronous or synchronous code block
    d.run(function(){
        // the asynchronous or synchronous code that we want to catch thrown errors on
        var err = new Error('example')
        throw err
    })
  • Якщо ми знаємо, де відбувається помилка - це синхронний код, і з будь-якої причини не можемо використовувати домени (можливо, стару версію вузла), ми можемо використовувати оператор try catch:

    // catch the uncaught errors in this synchronous code block
    // try catch statements only work on synchronous code
    try {
        // the synchronous code that we want to catch thrown errors on
        var err = new Error('example')
        throw err
    } catch (err) {
        // handle the error safely
        console.log(err)
    }

    Однак будьте обережні, щоб не використовувати try...catchв асинхронному коді, оскільки асинхронно викинута помилка не буде виявлена:

    try {
        setTimeout(function(){
            var err = new Error('example')
            throw err
        }, 1000)
    }
    catch (err) {
        // Example error won't be caught here... crashing our app
        // hence the need for domains
    }

    Якщо ви хочете працювати try..catchразом з асинхронним кодом, під час роботи Node 7.4 або вище ви можете використовуватиasync/await для написання своїх асинхронних функцій.

    Інша річ, з якою слід бути обережною, try...catch- це ризик перетворити зворотний виклик завершення всередину tryоператора, наприклад:

    var divide = function(x,y,next) {
        // if error condition?
        if ( y === 0 ) {
            // "throw" the error safely by calling the completion callback
            // with the first argument being the error
            next(new Error("Can't divide by zero"))
        }
        else {
            // no error occured, continue on
            next(null, x/y)
        }
    }
    
    var continueElsewhere = function(err, result){
            throw new Error('elsewhere has failed')
    }
    
    try {
            divide(4, 2, continueElsewhere)
            // ^ the execution of divide, and the execution of 
            //   continueElsewhere will be inside the try statement
    }
    catch (err) {
            console.log(err.stack)
            // ^ will output the "unexpected" result of: elsewhere has failed
    }

    Цю готчу зробити дуже просто, оскільки ваш код стає складнішим. Таким чином, найкраще або використовувати домени, або повертати помилки, щоб уникнути (1) вилучень винятків в асинхронному коді (2) спроби ловити виконання, яке ви не хочете. У мовах, які дозволяють виконувати належну нитку замість асинхронного стилю-асинхронного стилю JavaScript, це менше проблеми.

  • Нарешті, у випадку, коли незрозуміла помилка трапляється у місці, яке не було зафіксовано у домені чи заяві про випробування, ми можемо змусити наш додаток не вийти з ладу за допомогою uncaughtExceptionслухача (однак це може перевести програму у невідомий стан ):

    // catch the uncaught errors that weren't wrapped in a domain or try catch statement
    // do not use this in modules, but only in applications, as otherwise we could have multiple of these bound
    process.on('uncaughtException', function(err) {
        // handle the error safely
        console.log(err)
    })
    
    // the asynchronous or synchronous code that emits the otherwise uncaught error
    var err = new Error('example')
    throw err

5
Дякую Рейносу, оновлено. Чи є у вас джерело, яке пояснює зло try catch? Як я хотів би підкріпити це доказами. Також зафіксовано приклад синхронізації.
balupton

2
Ця відповідь більше не є дійсною. Домени вирішують цю проблему (рекомендується node.js)
Габріель Лама

5
@balupton Помилки повинні бути кинуті для обробки помилок. Їх точно не слід уникати. Про них нічого не порушує виконання програми чи чогось іншого. Java та більшість інших сучасних мов мають чудову підтримку винятків. Мій єдиний висновок після прочитання деяких неправильно сформованих публікацій тут - це те, що люди їх не дуже добре розуміють, і тому їх бояться. Страх невизначеного сумніву. Ця дискусія була вирішена на користь винятків як мінімум 20 років тому.
enl8enmentnow

22
Тепер домени застаріли в io.js : " Цей модуль очікує на застаріння . Після того, як API заміни буде завершено, цей модуль буде повністю застарілий ... Користувачі, які абсолютно повинні мати функціонал, який надають домени, на даний момент можуть покластися на нього, але слід очікувати, що в майбутньому доведеться перейти до іншого рішення ".
Тимофій Гу

5
Зараз домен api застарілий ? Вони згадують API заміни - хтось знає, коли це вийде, і як це буде виглядати?
UpTheCreek

95

Далі йде узагальнення та курація з багатьох різних джерел на цю тему, включаючи приклад коду та цитати з вибраних публікацій блогу. Повний перелік найкращих практик можна знайти тут


Найкращі практики обробки помилок Node.JS


Номер1: Використовуйте обіцянки для обробки помилок асинхронізації

TL; DR: Поводження з асинхронними помилками у стилі зворотного дзвінка - це, мабуть, найшвидший шлях до пекла (він же піраміда приреченості). Найкращим подарунком, який ви можете подарувати своєму коду, є використання замість цього поважної бібліотеки обіцянок, яка забезпечує набагато компактний і звичний синтаксис коду, як-от пробування

Інакше: стиль зворотного виклику Node.JS (функція (помилка, відповідь)) - це багатообіцяючий спосіб нерентабельного коду за рахунок поєднання помилок з випадковим кодом, надмірного введення та незручного шаблону кодування.

Приклад коду - хороший

doWork()
.then(doWork)
.then(doError)
.then(doWork)
.catch(errorHandler)
.then(verify);

приклад коду анти шаблон - обробка помилок стилю зворотного виклику

getData(someParameter, function(err, result){
    if(err != null)
      //do something like calling the given callback function and pass the error
    getMoreData(a, function(err, result){
          if(err != null)
            //do something like calling the given callback function and pass the error
        getMoreData(b, function(c){ 
                getMoreData(d, function(e){ 
                    ...
                });
            });
        });
    });
});

Цитата в блозі: "У нас проблема з обіцянками" (З блогу pouchdb, що займає 11 місце за ключовими словами "Вузол обіцяє")

"... Насправді, зворотні виклики роблять щось ще більш зловісне: вони позбавляють нас стека, що зазвичай ми сприймаємо як належне в мовах програмування. Написання коду без стека - це дуже схоже на керування автомобілем без педалі гальма: ви не розумійте, як сильно вам це потрібно, поки ви не досягнете цього, і його там немає. Вся суть обіцянок полягає в тому, щоб повернути нам ті основи мови, які ми втратили, коли ми перейшли в асинхровку: повернення, кидання та стек. Але ти повинні знати, як правильно використовувати обіцянки, щоб скористатися ними ".


Число2: Використовуйте лише вбудований об'єкт Помилка

TL; DR: Досить часто бачити код, який видаляє помилки як рядок або як власний тип - це ускладнює логіку обробки помилок та сумісність між модулями. Якщо ви відхиляєте обіцянку, викидаєте виняток або випускаєте помилку - використання вбудованого об'єкта Error Node.JS збільшує рівномірність та запобігає втраті інформації про помилку

В іншому випадку: виконуючи якийсь модуль, будучи невідомим, який тип помилок приходить взамін - це значно важче міркувати про майбутній виняток і обробляти його. Навіть варто застосувати користувальницькі типи для опису помилок, що може призвести до втрати критичної інформації про помилку, наприклад, сліду стека!

Приклад коду - робити це правильно

    //throwing an Error from typical function, whether sync or async
 if(!productToAdd)
 throw new Error("How can I add new product when no value provided?");

//'throwing' an Error from EventEmitter
const myEmitter = new MyEmitter();
myEmitter.emit('error', new Error('whoops!'));

//'throwing' an Error from a Promise
 return new promise(function (resolve, reject) {
 DAL.getProduct(productToAdd.id).then((existingProduct) =>{
 if(existingProduct != null)
 return reject(new Error("Why fooling us and trying to add an existing product?"));

приклад коду анти-шаблон

//throwing a String lacks any stack trace information and other important properties
if(!productToAdd)
    throw ("How can I add new product when no value provided?");

Цитата в щоденнику: "Рядок не є помилкою" (З блогу devthought, 6 місце для ключових слів "Node.JS error error")

"... передача рядка замість помилки призводить до зменшення сумісності між модулями. Це розриває контракти з API, які можуть виконувати instanceof перевірку помилок, або які хочуть дізнатися більше про помилку . Об'єкти помилок, як ми побачимо, мають дуже цікаві властивості в сучасних JavaScript-системах, окрім того, що повідомлення передане конструктору .. "


Номер 3: Розрізняють оперативні та програмові помилки

TL; DR: Помилки операцій (наприклад, API отримано недійсний ввід) відносяться до відомих випадків, коли вплив помилки повністю зрозумілий і з ним можна обдумувати продумано. З іншого боку, помилка програміста (наприклад, спроба прочитати невизначену змінну) посилається на невідомі збої коду, які диктують витончений перезапуск програми

В іншому випадку: Ви завжди можете перезапустити програму, коли з’явиться помилка, але навіщо опускати ~ 5000 онлайн-користувачів через незначну та передбачувану помилку (операційна помилка)? навпаки, також не ідеально - тримати додаток, коли невідома проблема (помилка програміста) може призвести до непередбачуваної поведінки. Диференціація обох дозволяє діяти тактовно та застосовувати збалансований підхід на основі заданого контексту

Приклад коду - робити це правильно

    //throwing an Error from typical function, whether sync or async
 if(!productToAdd)
 throw new Error("How can I add new product when no value provided?");

//'throwing' an Error from EventEmitter
const myEmitter = new MyEmitter();
myEmitter.emit('error', new Error('whoops!'));

//'throwing' an Error from a Promise
 return new promise(function (resolve, reject) {
 DAL.getProduct(productToAdd.id).then((existingProduct) =>{
 if(existingProduct != null)
 return reject(new Error("Why fooling us and trying to add an existing product?"));

приклад коду - позначення помилки як оперативної (довіреної)

//marking an error object as operational 
var myError = new Error("How can I add new product when no value provided?");
myError.isOperational = true;

//or if you're using some centralized error factory (see other examples at the bullet "Use only the built-in Error object")
function appError(commonType, description, isOperational) {
    Error.call(this);
    Error.captureStackTrace(this);
    this.commonType = commonType;
    this.description = description;
    this.isOperational = isOperational;
};

throw new appError(errorManagement.commonErrors.InvalidInput, "Describe here what happened", true);

//error handling code within middleware
process.on('uncaughtException', function(error) {
    if(!error.isOperational)
        process.exit(1);
});

Цитата в блозі : "Інакше ви ризикуєте станом" (З блогу, який виводиться з ладу, 3 місце за ключовими словами "Виключення Node.JS uncaught")

" ... За своєю суттю, як працює кидок у JavaScript, майже ніколи немає способу безпечно" забрати, де ви зупинилися ", не протікаючи посилань чи створюючи якийсь інший не визначений крихкий стан. Найбезпечніший спосіб відповісти на викинута помилка полягає в тому, щоб вимкнути процес . Звичайно, на звичайному веб-сервері у вас може бути відкрито багато з’єднань, і нерозумно їх різко закривати, оскільки помилку викликав хтось інший. Кращий підхід - це надіслати відповідь про помилку на запит, який викликав помилку, а інші дозволяють закінчити звичайний час і перестати слухати нові запити у цього працівника "


Number4: Обробляйте помилки централізовано, через, але не в межах програмного забезпечення

TL; DR: Логіка керування помилками, такими як пошта для адміністратора та ведення журналів, повинна бути інкапсульована у спеціалізований та централізований об'єкт, який усі кінцеві точки (наприклад, середнє програмне забезпечення Express, завдання Cron, тестування блоків) викликають, коли виникає помилка.

Інакше: Якщо не обробляти помилки в одному місці, це призведе до дублювання коду і, ймовірно, до помилок, з якими неправильно обробляється

Приклад коду - типовий потік помилок

//DAL layer, we don't handle errors here
DB.addDocument(newCustomer, (error, result) => {
    if (error)
        throw new Error("Great error explanation comes here", other useful parameters)
});

//API route code, we catch both sync and async errors and forward to the middleware
try {
    customerService.addNew(req.body).then(function (result) {
        res.status(200).json(result);
    }).catch((error) => {
        next(error)
    });
}
catch (error) {
    next(error);
}

//Error handling middleware, we delegate the handling to the centrzlied error handler
app.use(function (err, req, res, next) {
    errorHandler.handleError(err).then((isOperationalError) => {
        if (!isOperationalError)
            next(err);
    });
});

Цитата в щоденнику: "Іноді більш низькі рівні не можуть зробити нічого корисного, окрім розповсюдження помилки своєму абоненту" (З блогу Joyent, що займає 1 місце за ключовими словами "Помилка Node.JS")

"... Ви можете отримати одну і ту ж помилку на декількох рівнях стеку. Це трапляється, коли нижчі рівні не можуть зробити нічого корисного, крім того, щоб поширити помилку своєму абоненту, який поширює помилку на її абонента тощо". тільки той, хто телефонує верхнього рівня, знає, що відповідає відповідній реакції, чи потрібно це повторити операцію, повідомити про помилку користувачеві чи щось інше. Але це не означає, що ви повинні намагатися повідомляти про всі помилки на одному верхньому рівні зворотний виклик, оскільки сам зворотний виклик не може знати, в якому контексті сталася помилка "


Number5: Помилки API документа за допомогою Swagger

TL; DR: Повідомте ваших абонентів API знати, які помилки можуть прийти взамін, щоб вони могли обдумати ці помисли, без збоїв. Зазвичай це робиться з документаційними рамками REST API, такими як Swagger

В іншому випадку: клієнт API може вирішити збій і перезапустити лише тому, що отримав назад помилку, яку він не міг зрозуміти. Примітка: абонент API може бути ви (дуже типово в середовищі мікросервісів)

Цитата в блозі: "Ви повинні сказати своїм абонентам, які помилки можуть трапитися" (З блогу Joyent, 1 місце для ключових слів "Node.JS logging")

… Ми говорили про те, як поводитися з помилками, але коли ви пишете нову функцію, як ви доставляєте помилки в код, який називав вашу функцію? … Якщо ви не знаєте, які помилки можуть статися або не знаєте, що вони означають, то ваша програма не може бути виправлена, крім випадкового випадку. Отже, якщо ви пишете нову функцію, ви повинні сказати своїм абонентам, які помилки можуть трапитися і що вони викликають


Номер 6: Вимкніть процес витончено, коли в місто приїжджає незнайомець

TL; DR: Коли трапляється невідома помилка (помилка розробника, див. Кращу практику №3) - існує непевність щодо здоров'я додатків. Поширена практика пропонує ретельно запустити процес, використовуючи інструмент «перезапуск», як Forever та PM2

В іншому випадку: Якщо трапляється незнайомий виняток, якийсь об'єкт може опинитися у несправному стані (наприклад, випромінювач подій, який використовується у всьому світі і більше не запускає події через деякий внутрішній збій), і всі майбутні запити можуть вийти з ладу або поводити себе шалено

Приклад коду - вирішення питання щодо збою

//deciding whether to crash when an uncaught exception arrives
//Assuming developers mark known operational errors with error.isOperational=true, read best practice #3
process.on('uncaughtException', function(error) {
 errorManagement.handler.handleError(error);
 if(!errorManagement.handler.isTrustedError(error))
 process.exit(1)
});


//centralized error handler encapsulates error-handling related logic 
function errorHandler(){
 this.handleError = function (error) {
 return logger.logError(err).then(sendMailToAdminIfCritical).then(saveInOpsQueueIfCritical).then(determineIfOperationalError);
 }

 this.isTrustedError = function(error)
 {
 return error.isOperational;
 }

Цитата в блозі: "Є три школи думок щодо поводження з помилками" (З блогу jsrecipes)

… Існує, головним чином, три школи думок щодо поводження з помилками: 1. Дозвольте програмі вийти з ладу та перезавантажте її. 2. Обробляйте всі можливі помилки і ніколи не збивайтесь. 3. Збалансований підхід між ними


Число7: Використовуйте зрілий реєстратор для збільшення видимості помилок

TL; DR: Набір зрілих засобів ведення журналів, таких як Вінстон, Бунян або Log4J, прискорить виявлення та розуміння помилок. Тож забудьте про console.log.

В іншому випадку: Снімінг через console.logs або вручну через безладний текстовий файл без інструментів для запитів або пристойний переглядач журналу може зайняти вас на роботі до пізнього часу

Приклад коду - реєстратор Winston в дії

//your centralized logger object
var logger = new winston.Logger({
 level: 'info',
 transports: [
 new (winston.transports.Console)(),
 new (winston.transports.File)({ filename: 'somefile.log' })
 ]
 });

//custom code somewhere using the logger
logger.log('info', 'Test Log Message with some parameter %s', 'some parameter', { anything: 'This is metadata' });

Цитата в блозі: "Дозволяє визначити кілька вимог (для лісоруба):" (З блогу strongblog)

… Давайте визначимо декілька вимог (для реєстратора): 1. Маркуйте час кожного рядка журналу. Цей досить пояснюючий сам - ви повинні мати можливість сказати, коли відбувся кожен запис журналу. 2. Формат журналу повинен бути легко засвоюваним як людиною, так і машинами. 3. Дозволяє для декількох настроюваних потоків призначення. Наприклад, ви можете писати журнали слідів до одного файлу, але коли виникає помилка, запишіть у той самий файл, потім у файл помилок і одночасно надішліть електронний лист ...


Номер8: Дізнайтеся про помилки та простої за допомогою продуктів APM

TL; DR: Моніторингові та продуктивні продукти (ака APM) активно вимірюють вашу кодову базу чи API, щоб вони могли автоматично магічно виділяти помилки, збої та повільні частини, які вам бракували

В іншому випадку: Ви можете витратити великі зусилля на вимірювання продуктивності та простоїв API, ймовірно, ви ніколи не дізнаєтесь, які є ваші найповільніші частини коду за сценарієм реального світу та як це впливає на UX

Цитата в блозі: "Сегменти продуктів APM" (З блогу Yoni Goldberg)

"... Продукти APM складають 3 основні сегменти: 1. Моніторинг веб-сайтів або API - зовнішні сервіси, які постійно контролюють тривалість роботи та продуктивність за допомогою HTTP-запитів. Налаштування може бути налаштовано за кілька хвилин. Наступні декілька обраних претендентів: Pingdom, Uptime Robot і New Relic 2 Кодування приладів - сімейство продуктів, яким потрібно вбудовувати агент у додаток, щоб використовувати функцію повільного виявлення коду, статистику винятків, моніторинг продуктивності та багато іншого. Нижче перелічено декілька вибраних претендентів: New Relic, App Dynamics 3. Інформаційна панель операційної розвідки -ця лінійка продуктів орієнтована на полегшення команді ops за допомогою метрик та кураторного вмісту, що допомагає легко бути в курсі продуктивності програми. Зазвичай це включає агрегування декількох джерел інформації (журнали додатків, журнали БД, журнал серверів тощо) та роботи по проектуванню панелі наперед. Нижче перелічено декількох обраних претендентів: Datadog, Splunk "


Наведене вище є скороченою версією - див. Тут більше кращих практик та прикладів


30

Ви можете зловити виняткові винятки, але це обмежене використання. Подивитися http://debuggable.com/posts/node-js-dealing-with-uncaught-exceptions:2c933d54-1428-443c-928d-4e1ecbdd56cb

monit, foreverабо upstartможе бути використаний для перезавантаження процесу вузла при його збої. Найкраще ви можете сподіватися на витончене вимкнення (наприклад, збережіть усі дані в пам'яті в обробці винятків).


4
+1 Посилання корисне, дякую. Я все ще шукаю найкращу практику та значення "витонченого перезавантаження" в контексті node.js
momo

Моє розуміння "витонченого перезапуску" в цьому контексті було б по суті тим, що пропонує nponeccop: нехай процес загине, і нехай все, що працює, в першу чергу перезапустить його.
Ілька

Дякую за це посилання! Дійсно корисно!
SatheeshJM

Це чудова відповідь. Я б не погоджувався щодо повернення Помилки у вашому першому прикладі. Повернення Errorзначень повертає значення поліморфним, що мусить семантику функції без потреби. Крім того, плавання на 0 вже оброблено в JavaScript, даючи Infinity, -Infinityабо NaN, де це значення typeof === 'number'. Їх можна перевірити !isFinite(value). Взагалі я б рекомендував ніколи не повертати помилку з функції. Краще з точки зору розбірливості коду та технічного обслуговування кидати або повертати спеціальне неполіморфне значення w / послідовна семантика.
впр

Посилання розірвано. downforeveryoneorjustme.com/debuggable.com
Кев

13

nodejs domains - це найсучасніший спосіб обробляти помилки в nodejs. Домени можуть фіксувати як помилки / інші події, так і традиційно кинуті об'єкти. Домени також забезпечують функцію обробки зворотних викликів із помилкою, переданою як перший аргумент методом перехоплення.

Як і у випадку звичайного поводження з помилками у стилі "уловлювання", як правило, найкраще викидати помилки, коли вони виникають, та блокувати області, де ви хочете виділити помилки, впливаючи на решту коду. Спосіб "блокувати" ці області - викликати domain.run з функцією блоку ізольованого коду.

У синхронному коді вищесказаного достатньо - коли трапляється помилка, ви або дозволите її перекинути, або ви її вловите та обробляєте, повертаючи будь-які дані, які потрібно відновити.

try {  
  //something
} catch(e) {
  // handle data reversion
  // probably log too
}

Коли помилка трапляється при асинхронному зворотному виклику, вам потрібно мати можливість повністю обробляти відкат даних (загальний стан, зовнішні дані, такі як бази даних тощо). АБО вам потрібно встановити щось, що вказує на те, що стався виняток - де б вам не було байдуже про цей прапор, вам доведеться почекати завершення зворотного дзвінка.

var err = null;
var d = require('domain').create();
d.on('error', function(e) {
  err = e;
  // any additional error handling
}
d.run(function() { Fiber(function() {
  // do stuff
  var future = somethingAsynchronous();
  // more stuff

  future.wait(); // here we care about the error
  if(err != null) {
    // handle data reversion
    // probably log too
  }

})});

Деякі з наведених вище кодів некрасиві, але ви можете створити шаблони для себе, щоб зробити їх красивішими, наприклад:

var specialDomain = specialDomain(function() {
  // do stuff
  var future = somethingAsynchronous();
  // more stuff

  future.wait(); // here we care about the error
  if(specialDomain.error()) {
    // handle data reversion
    // probably log too
  } 
}, function() { // "catch"
  // any additional error handling
});

ОНОВЛЕННЯ (2013-09):

Вище я використовую майбутнє, яке має на увазі семантику волокон , що дозволяє вам чекати на ф'ючерси в онлайні. Це фактично дозволяє використовувати традиційні блоки пробного лову для всього - що я вважаю найкращим способом. Однак ви не завжди можете це зробити (тобто в браузері) ...

Також є ф'ючерси, які не потребують семантики волокон (які потім працюють із звичайним браузером JavaScript). Їх можна назвати ф'ючерсами, обіцянками або відстроченнями (я звідси я буду лише посилатися на ф'ючерси) Прості старі бібліотеки ф’ючерсів JavaScript дозволяють поширювати помилки між ф'ючерсами. Лише деякі з цих бібліотек дозволяють правильно керувати будь-яким кинутим майбутнім, тому будьте обережні.

Приклад:

returnsAFuture().then(function() {
  console.log('1')
  return doSomething() // also returns a future

}).then(function() {
  console.log('2')
  throw Error("oops an error was thrown")

}).then(function() {
  console.log('3')

}).catch(function(exception) {
  console.log('handler')
  // handle the exception
}).done()

Це імітує нормальний пробний лов, навіть якщо шматки асинхронні. Він надрукував би:

1
2
handler

Зауважте, що він не друкує "3", оскільки було викинуто виняток, який перериває цей потік.

Погляньте на обіцянки синьої пташки:

Зауважте, що я не знайшов багатьох інших бібліотек, окрім цих, які належним чином обробляють викинуті винятки. Наприклад, відкладеного jQuery не слід - обробник "невдач" ніколи не отримав би виняток, кинутий обробником "тоді", який, на мою думку, є вимикачем угоди.


Правильна специфікація обіцянок у Javascript відома як Promises / A +. Ви можете побачити список реалізацій тут: github.com/promises-aplus/promises-spec/blob/master / ... . Зауважте, що голі Обіцянки / А + непридатні на практиці - Обіцянки / А + все ще залишають багато практичних проблем для бібліотек, щоб вирішити самостійно. Однак абсолютно необхідні речі, такі як розповсюдження помилок, які ви показуєте, детермінований порядок виконання та безпека від переповнення стека гарантуються.
Есаїлія


11

Про це я писав нещодавно на сайті http://snmaynard.com/2012/12/21/node-error-handling/ . Новою особливістю вузла у версії 0.8 є домени, які дозволяють поєднувати всі форми поводження з помилками в одну простішу форму управління. Ви можете прочитати про них у моєму дописі.

Ви також можете використовувати щось на зразок Bugsnag, щоб відстежувати невдалі винятки та отримувати сповіщення по електронній пошті, в чаті або створити путівку за винятковим винятком (я є співзасновником Bugsnag).


2
Модуль домену тепер офіційно застарілий. nodejs.org/api/domain.html
MattSidor

3

Я просто хотів би додати, що бібліотека Step.js допомагає вам обробляти винятки, завжди передаючи її функції наступного кроку. Тому ви можете мати на останньому кроці функцію, яка перевіряє наявність помилок в будь-якому з попередніх кроків. Такий підхід може значно спростити обробку помилок.

Нижче наведено цитату зі сторінки github:

будь-які викинуті винятки виловлюються і передаються як перший аргумент наступній функції. Поки ви не вкладаєте функції зворотного виклику вбудовані ваші основні функції, це запобігає виникненню будь-яких невиконаних винятків. Це дуже важливо для довго працюючих серверів node.JS, оскільки єдине вимкнено виняток може збити весь сервер.

Крім того, ви можете використовувати крок для управління виконанням сценаріїв, щоб мати розділ очищення як останній крок. Наприклад, якщо ви хочете написати скрипт збірки в Node і повідомити, скільки часу потрібно було написати, останній крок може зробити це (а не намагатися викопати останній зворотній зв'язок).


3

Один з випадків, коли може бути доречним використання пробного лову, - це використання циклу forEach. Це синхронно, але в той же час ви не можете просто використовувати оператор return у внутрішній області. Натомість для повернення об'єкта Error у відповідну область може бути використаний підхід "try-catch". Поміркуйте:

function processArray() {
    try { 
       [1, 2, 3].forEach(function() { throw new Error('exception'); }); 
    } catch (e) { 
       return e; 
    }
}

Це поєднання підходів, описаних вище @balupton.


Замість того, щоб викидати помилки, деякі розробники рекомендують використовувати концепцію Result від Rust, щоб повернути або OK, або Fail , коли збій є відомою можливістю. Це не дає відмов окремо від несподіваних помилок. Одне впровадження цього JS - це r-результат .
joeytwiddle

Це загальноприйняте дизайнерське рішення. Я думаю, що ваша концепція повернення помилок приблизно еквівалентна і проста для початку (без зайвих залежностей), але менш явна ( Результат дає вам болісно усвідомлювати, коли помилки, можливо, потрібно буде обробляти) і менш ефективний у тих випадках, коли стек побудовані без потреби.
joeytwiddle

1

Прочитавши цю публікацію деякий час тому, мені було цікаво, чи безпечно використовувати домени для обробки виключень на рівні api / function. Я хотів використовувати їх для спрощення коду обробки винятків у кожній написаній асинхронною функцією. Мене хвилює те, що використання нового домену для кожної функції призведе до значних витрат. Моє домашнє завдання вказує на те, що мінімальні накладні витрати та що ефективність насправді краща з доменами, ніж із спробою лову в деяких ситуаціях.

http://www.lighthouselogic.com/#/using-a-new-domain-for-each-async-function-in-node/


1

Тут дуже добре обговорювались помилки лову, але варто пам’ятати, щоб помилки десь були записані, щоб ви могли їх переглядати та виправляти.

Bunyan є популярною рамкою ведення журналу для NodeJS - вона підтримує виписку на купу різних вихідних місць, що робить її корисною для локальної налагодження, якщо ви уникаєте console.log. У обробці помилок вашого домену ви можете виплюнути помилку у файл журналу.

var log = bunyan.createLogger({
  name: 'myapp',
  streams: [
    {
      level: 'error',
      path: '/var/tmp/myapp-error.log'  // log ERROR to this file
    }
  ]
});

Це може зайняти багато часу, якщо у вас є багато помилок та / або серверів для перевірки, тому може бути варто заглянути в такий інструмент, як Raygun (відмова від відповідальності, я працюю в Raygun), щоб групувати помилки разом - або використовувати їх обидва разом. Якщо ви вирішили використовувати Raygun як інструмент, налаштування також досить просто

var raygunClient = new raygun.Client().init({ apiKey: 'your API key' });
raygunClient.send(theError);

Перехрестившись із використанням такого інструменту, як PM2 або назавжди, ваш додаток повинен мати змогу вийти з ладу, вийти з системи, що сталося, та перезавантажитись без великих проблем.


Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.