mikaelbr/gulp-notify

Making notifications, error logging, and watchers play nicely

Closed this issue ยท 20 comments

I've been banging my head to get gulp-notify, gulp-plumber, and gulp-sass working for a few hours. The build works fine, but I'm struggling to get error notifications to play nice. What I want is:

  1. OS notification that an error occurred
  2. Error reported in the console
  3. Watch task does not stop

It feels like none of the modules want this to happen.

First Attempt

I tried these .onError commands directly in the task stream, but that stops the watcher:

gulp.task('css', function () {
    return gulp.src('scss/**/*.scss')
    .pipe(sass())

    // First try
    .on('error', function () {
        notify({
            title: 'Gulp: CSS',
            message: 'Error'
        })
    })

    // Second try
    .on('error', notify.onError({
        title: 'Gulp: CSS',
        message: 'Error'
    }))

    // Third try
    .on('error', function (err) {
        return notify().write({
            title: 'Gulp: CSS',
            message: 'Error'
        });
    })
});

Note: I found all these different syntaxes in gulpfiles from others, but with no real explanations of why they should be used.

Second Attempt

Then I tried using gulp-sass callback for onError:

gulp.task('css', function () {
    return gulp.src('scss/**/*.scss')
    .pipe(sass({
        errLogToConsole: false,
        onError: notify.onError({
            title: 'Gulp: CSS',
            message: 'Error'
        })
    }))
});

But setting errLogToConsole: false means you can't see the error. Changing to true means the watcher stops.

Third Attempt

Then I tried I'm using gulp-plumber so the watcher doesn't stop.

gulp.task('css', function () {
    return gulp.src('scss/**/*.scss')
    .pipe(plumber({
        errorHandler: reportError
    }))
    .pipe(sass())
    .on('error', reportError)
});

With the error handling function:

function reportError (error) {
    notify().write({
        title: 'Gulp: CSS',
        message: 'Error'
    });

    console.log(error.toString());
}

This sort of works, but notify.write() ignores the title property passed in. I couldn't find any docs for this.

The watcher breaks but doesn't stop. Correcting the CSS error and saving has no effect.

Even if that did work, the error handler would be hardcoded for the CSS task. I'm trying to re-use this for JS too.

Fourth Attempt

Then I tried to rewrite the second attempt to be self-contained and not rely on gulp-plumber with an anonymous function for error reporting + notification:

gulp.task('css', function () {
    return gulp.src('scss/**/*.scss')
    .pipe(sass({
        errLogToConsole: false,
        onError: function (err) {
            console.log(err);        
            notify().write({
                title: 'Gulp: CSS',
                message: 'Error'
            });
        }
    }))
});

This technically does all three things I want:

  1. OS notification that an error occurred (but not nicely formatted)
  2. Error reported in the console
  3. Watch task does not to stop (and stays functional)

But it means I need another workaround for JS (or other tasks that may need error handling).

What am I missing here?

There has to be a better way.

This solution worked for me. Remove the return statement, then the watcher doesn't stop on error.

gulpjs/gulp#259

That's a good thread. Seems like omitting return is not recommended though because it stops piping the stream.

Need to look at this.emit('end') since I wasn't sure what that did before. Looks like that's the "correct" way to use gulp-plumber.

Hi and sorry for the late response.

As of v2.0.1 gulp-notify should do a emit('end') on error. What version are you using?

I'm on v2.2.0 now. Went through and updated all the modules before testing this out. Haven't started playing with emit('end') yet though. Sounds like that might be the right way.

@brendanfalkowski I too had an issue with the write() function not carrying over the 'title' property.

I've submitted a pull-request to fix this behaviour - #82

Okay, did a bunch of reading and think I have this sorted out. Here's the list:

My simple error handler

Using this.emit('end') was the proper solution. I was actually close with attempt 3 and have expanded that as follows:

gulp.task('css', function () {
    return gulp.src('scss/**/*.scss')
    .pipe(plumber({
        errorHandler: reportError
    }))
    .pipe(sass())
    .on('error', reportError)
});

And the corrected error handler:

var reportError = function (error) {
    notify({
        title: 'Gulp Task Error',
        message: 'Check the console.'
    }).write(error);

    console.log(error.toString());

    this.emit('end');
}

In attempt 3, I was (blissfully ignorant) put notify's config object into the write() function โ€” which doesn't work. Putting it inside notify() works as expected.

So that got me my three goals:

  1. OS notification that an error occurred (but not nicely formatted)
  2. Error reported in the console
  3. Watch task does not to stop (and stays functional)

Stumbling over notify().write()

I have no idea what .write() does in Node / steams. I couldn't find any documentation or issues mentioning it. But I figured out three things.

  1. If you pass nothing write() the notification doesn't happen on my Mac.
  2. If you pass an empty string '' the notification shows a grey Gulp icon.
  3. If you pass the error object (as I am) the notification shows a red Gulp icon. Presumably for errors. I like that.

My advanced error handler

After getting this working, I went back and made another list of enhancements. I was able to solve these:

  1. Improve the error reporting in the console with more readable formatting.
  2. Improve the notification to mention the task that failed + line number. Normally it will be the last file I saved that broke (Sass compiling) so this means I don't need to check the console.
  3. Play a sound on errors so two senses can detect a failed task.

Here's what I ended up with. Make sure you install the gulp-util module.

var reportError = function (error) {
    var lineNumber = (error.lineNumber) ? 'LINE ' + error.lineNumber + ' -- ' : '';

    notify({
        title: 'Task Failed [' + error.plugin + ']',
        message: lineNumber + 'See console.',
        sound: 'Sosumi' // See: https://github.com/mikaelbr/node-notifier#all-notification-options-with-their-defaults
    }).write(error);

    gutil.beep(); // Beep 'sosumi' again

    // Inspect the error object
    //console.log(error);

    // Easy error reporting
    //console.log(error.toString());

    // Pretty error reporting
    var report = '';
    var chalk = gutil.colors.white.bgRed;

    report += chalk('TASK:') + ' [' + error.plugin + ']\n';
    report += chalk('PROB:') + ' ' + error.message + '\n';
    if (error.lineNumber) { report += chalk('LINE:') + ' ' + error.lineNumber + '\n'; }
    if (error.fileName)   { report += chalk('FILE:') + ' ' + error.fileName + '\n'; }
    console.error(report);

    // Prevent the 'watch' task from stopping
    this.emit('end');
}

The console output and notification look like this. I'm pretty happy with it now, but it took close to six hours to work all this out.

screen shot 2015-05-08 at 11 54 49 pm

screen shot 2015-05-08 at 11 55 06 pm

@brendanfalkowski you should post that somewhere, found that really helpful tonight!

The "proper" solution to this is to use the built in error handler for gulp-notify found here which handles all this automatically.

@mikaelbr in the readme:
"If you want to notify on errors gulp-plumber can be used to not break the run and force you to have to restart gulp."

Has this been resolved?

I'm afraid this isn't due to a limitation in gulp-notify or something I can resolve. It's how node streams are designed. Gulp-plumber patches/alters the behavior of piping a stream. I think I've heard gulp 4 does this in the core, but I haven't confirmed it yet.

@brendanfalkowski Excellent article, thanks for putting your time in and sharing this!

Agree with previous commenters. That is a wonderful piece of work @brendanfalkowski and shouldn't be lost in closed GH ticket. Perhaps a self-answered SO question?

Either way, glad I came across it! Thank you.

Thanks @Rosseyn @amituuush @willthemoor

I rewrote a better version of this because a bunch of things broke when Node jumped itself from 0.whatever to 4.x later in 2015. Trying to get a bunch of my practices documented here: http://manuals.gravitydept.com/

But haven't gotten to my CSS / JS standards or build process yet. Will try to remember to add this on GitHub. Right now it's a WIP across a few projects (like most things) but I'm trying to extract and centralize all these little tools for re-use.

I'm curious what packages are involved to get the Advanced Error Handler working, mention here please? #81 (comment)

I'm thinking: gulp-notify, gulp-util, gulp-plumber, and anything else?

@colorful-tones These are in my setup now. I think it was the same back then.

  • gulp-if
  • gulp-util
  • gulp-plumber
  • gulp-notify

It's not shown in the above code, but gulp-if is used to suppress notifications when compiling from the server. Will get this stuff in a repo sometime. It's still on the list.

@colorful-tones It takes a bit of faffing to get a decent gulp process doesn't it.

Heres an extract from our gulp script compile process which shows off the error handling.

Hi @brendanfalkowski, firsly thanks for putting your time and making very useful thing! Everything is working except line number. I can't see the error's line number, 'lineNumber' variable is undefined.

Could you help me?

Below is my code:

var gulp 		      = require('gulp'),
	stylus 		    = require('gulp-stylus'),
        jade                 = require('gulp-jade'),
  	plumber 	    = require('gulp-plumber'),
  	browserSync   = require('browser-sync'),
  	del           = require('del'),
  	concat        = require('gulp-concat'),
  	imagemin      = require('gulp-imagemin'),
  	sass          = require('gulp-sass'),
  	sourcemaps    = require('gulp-sourcemaps'),
  	uglify        = require('gulp-uglify'),
  	autoprefixer  = require('gulp-autoprefixer'),
  	cache         = require('gulp-cache'),
  	pngquant      = require('imagemin-pngquant'),
        gutil         = require('gulp-util'),
        notify        = require('gulp-notify'),
	beep          = require('beepbeep');



// error handler function
var onError = function (error) {
  var lineNumber = (error.lineNumber) ? 'LINE ' + error.lineNumber + ' -- ' : '';

  notify({
    title: 'Task Failed [' + error.plugin + ']',
    // message:  error.toString(),
    message: lineNumber + 'See console.',
    // wait: false,
  }).write(error);

  // beep sound
  gutil.beep(3);

  // Pretty error reporting
  var report = '';
  var chalk = gutil.colors.white.bgRed;

  report += chalk('TASK:') + ' [' + error.plugin + ']\n';
  report += chalk('PROB:') + ' ' + error.message + '\n';
  if (error.lineNumber) { report += chalk('LINE:') + ' ' + error.lineNumber + '\n'; }
  if (error.fileName)   { report += chalk('FILE:') + ' ' + error.fileName + '\n'; }
  console.error(report);

  this.emit('end');
};

@a-zhunusbekov โ€” One of the packages changed the contents of the error object, and it broke a while back for me too. Currently I have these packages installed to support my error handler:

"gulp":              "3.9.1",
"gulp-if":           "2.0.2",
"gulp-notify":       "2.2.0",
"gulp-plumber":      "1.1.0",
"gulp-util":         "3.0.7",
"minimist":          "1.2.0"

And the revised handler code looks like this:

var reportError = function (error) {
    // [log]
    //console.log(error);

    // Format and ouput the whole error object
    //console.log(error.toString());


    // ----------------------------------------------
    // Pretty error reporting

    var report = '\n';
    var chalk = gutil.colors.white.bgRed;

    if (error.plugin) {
        report += chalk('PLUGIN:') + ' [' + error.plugin + ']\n';
    }

    if (error.message) {
        report += chalk('ERROR:\040') + ' ' + error.message + '\n';
    }

    console.error(report);


    // ----------------------------------------------
    // Notification

    if (error.line && error.column) {
        var notifyMessage = 'LINE ' + error.line + ':' + error.column + ' -- ';
    } else {
        var notifyMessage = '';
    }

    notify({
        title: 'FAIL: ' + error.plugin,
        message: notifyMessage + 'See console.',
        sound: 'Sosumi' // See: https://github.com/mikaelbr/node-notifier#all-notification-options-with-their-defaults
    }).write(error);

    gutil.beep(); // System beep (backup)


    // ----------------------------------------------
    // Prevent the 'watch' task from stopping

    this.emit('end');
}

Hope that helps. Maybe I'll get to putting this up in a nice repo over the holidays :)

Can you show full code of 'My simple error handler' for css?

@veremey โ€” That's still it (above). It gets used like this:

gulp.task('css', function () {
    return gulp.src(config.tasks.css.src)
        .pipe(sourcemaps.init())
        .pipe(plumber({
            errorHandler: reportError
        }))
        .pipe(sass(config.tasks.css.sassOptions))
        .pipe(autoprefixer(config.tasks.css.autoprefixerOptions))
        .pipe(sourcemaps.write('.'))
        .pipe(gulp.dest(config.tasks.css.dest))
        .pipe(gulpif(!isProduction, notify({
            title: 'CSS',
            message: '<%= file.relative %>',
            onLast: true
        })));
});