nodejs / node

Node.js JavaScript runtime βœ¨πŸ’πŸš€βœ¨
https://nodejs.org
Other
104.76k stars 28.3k forks source link

process: add 'warning' event #4782

Closed jasnell closed 8 years ago

jasnell commented 8 years ago

In several places throughout the code we write directly to stderr to report warnings (deprecation, possible eventemitter memory leak). The current design of simply dumping the text to stderr is less than ideal. This PR introduces a new "Process Warnings" mechanism that emits 'warn' events on the global process object. These are invoked with a Warning object that is similar in structure to an Error in that they have a name, message and stack trace.

By default, these warnings will be printed to stderr. This can be suppressed using the --no-warnings command line flag, however the 'warn' event will still be emitted by the process, allowing applications to handle the warnings in custom ways.

The --trace-warnings command line flag will tell Node.js to print the full stack trace of warnings as part of the default handling.

The existing --no-deprecation, --throw-deprecation and --trace-deprecation flags continue to work as they currently do, but the exact output of the warning message is modified to occur on process.nextTick(). The stack trace for the warning, however, is preserved and shows the correct call site.

Test cases and documentation are included.

Refs: https://github.com/nodejs/node-eps/pull/4#issuecomment-173079195

cjihrig commented 8 years ago

I wonder if it might be simpler to just use an Error or extend Error instead of defining a new class. Obviously errors and warnings are different things, but I figured I'd throw the idea out there.

jasnell commented 8 years ago

considered that but wanted to make sure there was a clear differentiation between the two. I think it's important for users not to conflate warnings with errors.

Fishrock123 commented 8 years ago

Is there a reason we wouldn't just print warnings to stderr when stderr.isTTY?

jasnell commented 8 years ago

@Fishrock123 ... you mean as opposed to used console.error? Or something else?

Fishrock123 commented 8 years ago

No I mean why not just print by default when we're interacting with a console?

jasnell commented 8 years ago

That's what this does. In the few places through the code that we currently print warnings, we just do an immediate console.error or console.trace. This PR moves that into an event handler and provides a more generic framework to report other kinds of warnings... By default, tho, the default handler simply just dumps that out to console.error.

jasnell commented 8 years ago

unless I'm missing what you're saying :-).

Fishrock123 commented 8 years ago

@jasnell Right, ok, I see that now. So of course there will be concerns about this dumping warnings to log files, so what I was asking, or trying to suggest is something like https://github.com/rvagg/branch-diff/blob/v1.4.1/branch-diff.js#L77-L78 - i.e. only log when process.stderr.isTTY. Reason being you're unlikely to be doing actual logging things on a direct console, I think.

jasnell commented 8 years ago

Yeah I started looking at that after you asked. It's definitely a possibility. On Jan 20, 2016 4:09 PM, "Jeremiah Senkpiel" notifications@github.com wrote:

@jasnell https://github.com/jasnell Right, ok, I see that now. So of course there will be concerns about this dumping warnings to log files, so what I was asking, or trying to suggest is something like https://github.com/rvagg/branch-diff/blob/v1.4.1/branch-diff.js#L77-L78 - i.e. only log when process.stderr.isTTY. Reason being you're unlikely to be doing actual logging things on a direct console, I think.

β€” Reply to this email directly or view it on GitHub https://github.com/nodejs/node/pull/4782#issuecomment-173408507.

benjamingr commented 8 years ago

Very nice idea!

bnoordhuis commented 8 years ago

Left some comments. I don't know if it's really an improvement, I guess I'm -0.

Fishrock123 commented 8 years ago

I'm not really so sure what the value of exposing this as an API is?

I'd like to be able to have more warnings as I outlined above, but what would a module actually do with this other than print / log them?

jasnell commented 8 years ago

This tries to keep the new API surface to a minimum, just a new event and an error like object. The main thing this tries to accomplish is give a bit more flexibility in how those warnings are printed/logged. For instance, an application that uses a custom logging solution can register a handler to do custom logging of warnings so that they appear properly alongside other logged events:

const winston = require('winston');
// .. configure winston
process.on('warn', (warning) => {
  winston.warn(warning.message, warning);
});

Another example would be an application built on Electron that surfaces the warnings in the GUI somehow as opposed to the console (where they quite possibly wouldn't be seen).

benjamingr commented 8 years ago

I think it might be a good idea to make warnings a thing, document the hook and allow libraries to emit their own warnings in a standard way.

In second thought it's the sort of thing that needs to be discussed more in depth in an issue first.

jasnell commented 8 years ago

There's no particular rush on landing this so keeping it open for discussion for a while is just fine :-)

Fishrock123 commented 8 years ago

other example would be an application built on Electron that surfaces the warnings in the GUI somehow as opposed to the console (where they quite possibly wouldn't be seen).

Ah, that is an excellent note. cc @zcbenz What sort of API would be useful to you folks for warnings?

zcbenz commented 8 years ago

Ah, that is an excellent note. cc @zcbenz What sort of API would be useful to you folks for warnings?

The warn event looks perfect to me.

ronkorving commented 8 years ago

Great feature! I agree with @cjihrig though that an Error object would be more intuitive (despite the name). At least with an Error object I know which properties to expect (even without docs).

One question though, could it be made so that if there is a listener for "warn", it automatically doesn't print to stderr? Quite like how "error" and signal listening works. Listening turns off the default behavior.

jasnell commented 8 years ago

Yes, we can do that, however it may be a bit surprising. Users may not be aware that a module has registered a listener and they could end up missing warnings they need to see.

On Thu, Jan 21, 2016 at 6:54 PM, Ron Korving notifications@github.com wrote:

Great feature! I agree with @cjihrig https://github.com/cjihrig though that an Error object would be more intuitive (despite the name). At least with an Error object I know which properties to expect (even without docs).

One question though, could it be made so that if there is a listener for "warn", it automatically doesn't print to stderr? Quite like how error and signal listening works. Listening turns off the default behavior.

β€” Reply to this email directly or view it on GitHub https://github.com/nodejs/node/pull/4782#issuecomment-173787784.

ronkorving commented 8 years ago

I guess some feedback from other users and collaborators on the matter would be good then.

jasnell commented 8 years ago

@trevnorris @Fishrock123 @ronkorving ... pushed an update that addresses many of the comments ( I hope :-) ...)

jasnell commented 8 years ago

@shigeki ... I'd like to get your input on this as well. I'd like to be able to use this mechanism to have node raise security warnings when users do things that are known to be unsafe... for instance: https://nodejs.org/dist/latest-v5.x/docs/api/crypto.html#crypto_support_for_weak_or_compromised_algorithms

benjamingr commented 8 years ago

There is a lot of potential in standardizing it - libraries would be able to dispatch events for problematic usage in an easy to deal with way.

jasnell commented 8 years ago

@nodejs/ctc ... updated. ping.

benjamingr commented 8 years ago

I think it would be really nice to know how clients will interact with this API. Are userland libraries expected to emit their own warn events?

jasnell commented 8 years ago

@bnoordhuis @benjamingr .. updated!

jasnell commented 8 years ago

@bnoordhuis @benjamingr @nodejs/ctc: I believe this is ready to go. I've updated based on the feedback, rebased and updated.

@bnoordhuis / @Fishrock123 ... I removed the TTY restriction. After going through a bunch of scenarios, there are too many cases where not getting the warning (particularly deprecation warnings) would be problematic.

I'd like to get this landed soon.

ronkorving commented 8 years ago

Just in the nick of time perhaps, and at the risk of bike shedding, but what do you think is the more common used event name for warnings? on("warn") or on("warning")? I'm personally really fine with either, but I can imagine some precedent has already been set somewhere, so I just wanna make sure the right naming choice is made. Once this goes in, there's no going back on the name.

jasnell commented 8 years ago

@ronkorving ... I'm not quite sure there is enough of a precedent to say really. I picked warn because it's three fewer characters to type than warning ;-)

ronkorving commented 8 years ago

I'm not one of those people typing err instead of error, but that may just be me too :)

I was just thinking:

process.on('error', function (error) { /* ... */ });
process.on('warning', function (warning) { /* ... */ });

The thing you receive is the thing you listen for. While "warn" is a verb, "error" is not.

jasnell commented 8 years ago

Ok, makes sense to me. I'll switch it to 'warning' ;-)

jasnell commented 8 years ago

@ronkorving ... done!

ronkorving commented 8 years ago

Cool :+1:

benjamingr commented 8 years ago

Note that bluebird starts using this hook today, still undocumented. We use it in order to warn against common misuses of promises.

jasnell commented 8 years ago

@benjamingr: is bluebird using 'warn' or 'warning' as the event name? Want to make sure we're in sync.

vkurchatkin commented 8 years ago

@benjamingr a reminder that emitting events on objects you don't own is a bad idea (unless it's a documented use case)

jasnell commented 8 years ago

In most cases, yes. For this particular mechanism it ought to be safe given that the semantics are clear. Is there something I'm missing tho? On Feb 12, 2016 5:39 AM, "Vladimir Kurchatkin" notifications@github.com wrote:

@benjamingr https://github.com/benjamingr a reminder that emitting events on objects you don't own is a bad idea

β€” Reply to this email directly or view it on GitHub https://github.com/nodejs/node/pull/4782#issuecomment-183333025.

vkurchatkin commented 8 years ago

@jasnell we need to document these cases, although I'd prefer special hook designed for each case (e.g. process.emitWarning instead of process.emit('warning')

jasnell commented 8 years ago

That's a possibility for sure. It gives us more flexibility later to evolve the mechanism also. On Feb 12, 2016 6:05 AM, "Vladimir Kurchatkin" notifications@github.com wrote:

@jasnell https://github.com/jasnell we need to document these cases, although I'd prefer special hook designed for each case (e.g. process.emitWarning instead of process.emit('warning')

β€” Reply to this email directly or view it on GitHub https://github.com/nodejs/node/pull/4782#issuecomment-183344160.

ronkorving commented 8 years ago

Then process.warn(warning) may be good naming. In the future, behavior may not just be limited to event emission. I like the idea :+1:

benjamingr commented 8 years ago

We added it to bluebird to provoke this discussion exactly. I appreciate your concerns. I'd love for this to be ctc agenda so it can be discussed whether or not this should be user facing.

jasnell commented 8 years ago

I've already heard from several people who definitely would like this to be user facing, and yes, for the over whelming majority of cases, emitting events on objects you don't own is bad practice because you simply cannot know what the side effects are. The original intent was for this to cover internal warnings but given the expressed interest, a process.emitWarning method does make a lot of sense (and would be trivial to add).

Thinking about it, a process.warn method might be easily confused (and abused) as a generic log warn mechanism (e.g. console.warn), which is not really the intent. emitWarning is sufficiently different to avoid such confusion. On Feb 12, 2016 6:32 AM, "Benjamin Gruenbaum" notifications@github.com wrote:

We added it to bluebird to provoke this discussion exactly. I appreciate your concerns. I'd love for this to be ctc agenda so it can be discussed whether or not this should be user facing.

β€” Reply to this email directly or view it on GitHub https://github.com/nodejs/node/pull/4782#issuecomment-183352913.

jasnell commented 8 years ago

@vkurchatkin @benjamingr @ronkorving ... ok, I've added process.emitWarning() ... there are essentially three flavors:

// String
process.emitWarning('this is the warning message');

// Object
process.emitWarning({name: 'FooWarning', message: 'this is the warning message');

// Error object
var warning = getErrorObjectSomehow();
process.emitWarning(warning);

The first two will be coerced into Error objects before passing off to the process.on('warning') event.

An optional warned parameter can be passed to avoid duplicate warnings...

var warned = false;
function doWarning() {
  warned = process.emitWarning('only warn once', warned);
}
doWarning(); // emits the warning
doWarning(); // emits nothing

Documentation is updated and a test case provided.

jasnell commented 8 years ago

@node/ctc ... can I get another round of review on this one? ;-)

benjamingr commented 8 years ago

We'd still likely use a process.emit("warning" manually because it'd work in older versions of node and Bluebird supports 0.10 (wait, do we?)

jasnell commented 8 years ago

Well, you certainly wouldn't get the default handling of the process warning in older versions of node so you'd need to watch out for that... your warnings may go completely unnoticed. The process.emitWarning method would be simple to polyfill also, which would likely be more maintainable long term.

benjamingr commented 8 years ago

emitting events on objects you don't own is bad practice because you simply cannot know what the side effects are.

Well, typically objects you don't own wouldn't give you the ability in the first place, process being an EventEmitter means it explicitly exports the capability to emit events on it. This has proven useful for unhandledRejection and works beautifully in userland. A channel to say "hey, what you're doing is wrong!" without relying on the temrinal output is something userland could use - since it lets application developers dictate a clear policy for warnings and separates debug from release by having different handlers.

Process plays the part of "the object everyone has the capability to listen to" and sometimes you need to fire a global event (unhandledRejection being a good example).

The only problematic part in this story is "why has this not emerged from userland". To be fair users asked for regardless of this pr (e.g. https://github.com/petkaantonov/bluebird/issues/980).

jasnell commented 8 years ago

Adding this to the ctc-agenda because I'd like to get review and sign off...

rvagg commented 8 years ago

The warned parameter is kind of odd. If the logic for whether this should be emitted or not is at the source of the call then surely it could just as easily be surrounded in an if at that same place. The code is already doing this (if (warned) return true;) so it seems like an odd way to hand off responsibility for whether or not to take action. Unless there's some larger plan in mind for this argument?

jasnell commented 8 years ago

@rvagg ... feedback addressed. I removed the warned argument from emitWarning and added the Error.call(this)