侦听 Node.js 中所有发出的事件

在 Node.js 中,有没有办法监听 EventEmitter 对象发出的 所有事件?

例如,你能做一些像..。

event_emitter.on('',function(event[, arg1][, arg2]...) {}

我的想法是,我想获取所有的事件吐出的一个服务器端 EventEmitterJSON.stringify的事件数据,发送它通过一个网络套接字连接,改造他们在客户端作为一个事件,然后在客户端的事件行动。

59185 次浏览

You might want to look into RPC modules for node.js. If I am not mistaken the Dnode RPC module has an chat server/client example similar to what you are trying to do. So you could either make use of their module or copy what they are doing.

In brief the example shows a server which on connection creates listeners for all the server events from the connected client. It does this by simply iterating over a stored list of event names.

var evNames = [ 'joined', 'said', 'parted' ];


con.on('ready', function () {
evNames.forEach(function (name) {
emitter.on(name, client[name]);
});
emitter.emit('joined', client.name);
});

This code is clever because it automatically calls a remote procedure call on the client associated with the event when the event is emitted.

As mentioned this behavior is not in node.js core. But you can use hij1nx's EventEmitter2:

https://github.com/hij1nx/EventEmitter2

It won't break any existing code using EventEmitter, but adds support for namespaces and wildcards. For example:

server.on('foo.*', function(value1, value2) {
console.log(this.event, value1, value2);
});

I know this is a bit old, but what the hell, here is another solution you could take.

You can easily monkey-patch the emit function of the emitter you want to catch all events:

function patchEmitter(emitter, websocket) {
var oldEmit = emitter.emit;


emitter.emit = function() {
var emitArgs = arguments;
// serialize arguments in some way.
...
// send them through the websocket received as a parameter
...
oldEmit.apply(emitter, arguments);
}
}

This is pretty simple code and should work on any emitter.

You can also use another event emitter implementation like https://github.com/ozantunca/DispatcherJS. The implementation would be like:

dispatcher.on('*', function () {});

DispatcherJS also supports namespaces and even dependencies to determine which callbacks are going to be called first.

This is based on the answer that Martin provided above. I'm a bit new to node, so I needed to work out his answer for myself. The method at the end, logAllEmitterEvents is the important bit.

var events = require('events');
var hungryAnimalEventEmitter = new events.EventEmitter();


function emitHungryAnimalEvents()
{
hungryAnimalEventEmitter.emit("HungryCat");
hungryAnimalEventEmitter.emit("HungryDog");
hungryAnimalEventEmitter.emit("Fed");
}


var meow = function meow()
{
console.log('meow meow meow');
}


hungryAnimalEventEmitter.on('HungryCat', meow);


logAllEmitterEvents(hungryAnimalEventEmitter);


emitHungryAnimalEvents();


function logAllEmitterEvents(eventEmitter)
{
var emitToLog = eventEmitter.emit;


eventEmitter.emit = function () {
var event = arguments[0];
console.log("event emitted: " + event);
emitToLog.apply(eventEmitter, arguments);
}
}

With ES6 classes it's very easy:

class Emitter extends require('events') {
emit(type, ...args) {
console.log(type + " emitted")
super.emit(type, ...args)
}
}

Here's a debug tool inspired by Martin's answer (https://stackoverflow.com/a/18087021/1264797). I just now used this to figure out what was going wrong in a set of streams by logging all of their events to the console. Works great. As Martin illustrates, OP could use it by replacing the console.log() call with a websocket sender.

function debug_emitter(emitter, name) {
var orig_emit = emitter.emit;
emitter.emit = function() {
var emitArgs = arguments;
console.log("emitter " + name + " " + util.inspect(emitArgs));
orig_emit.apply(emitter, arguments);
}
}

Be aware that all solutions described above will involve some sort of hacking around node.js EventEmitter internal implementation.

The right answer to this question would be: the default EventEmitter implementation does not support that, you need to hack around it.

If you take a look on node.js source code for EventEmitter, you can see listeners are retrieved from a hash using event type as a key, and it will just return without any further action if the key is not found:

https://github.com/nodejs/node/blob/98819dfa5853d7c8355d70aa1aa7783677c391e5/lib/events.js#L176-L179

That's why something like eventEmitter.on('*', ()=>...) can't work by default.

Ran into the same problem today, heres a solution:

Object.create(Object.assign({},EventEmitter.prototype, {
_onAnyListeners:[],
emit:function(...args){
//Emit event on every other server


if(this._fireOnAny && typeof this._fireOnAny === 'function'){
this._fireOnAny.apply(this,args)
}


EventEmitter.prototype.emit.apply(this,args)
},
_fireOnAny:function(...args){
this._onAnyListeners.forEach((listener)=>listener.apply(this,args))
},
onAny:function(func){
if(typeof func !== 'function'){
throw new Error('Invalid type');
}
this._onAnyListeners.push(func);
},
removeOnAny:function(func){
const index = this._onAnyListeners.indexOf(func);
if(index === -1){
return;
}
this._onAnyListeners.splice(index,1);
}
}));

I needed to trace all emitted events in all libraries, so I tapped into the prototype.

This example uses a Typescript signature, but you can just remove it if you are not into that kind of nonsense.

Within the call, this refers to the object that is emitting. It was very easy to track all unique object:emits in my project.

  // For my example I use a `set` to track unique emits.
const items = new Set()


const originalEmit = EventEmitter.prototype.emit;
EventEmitter.prototype.emit = function (event: String | Symbol, ...args: any[]): boolean {


// Do what you want here
const id = this.constructor.name + ":" + event;
if (!items.has(id)) {
items.add(id);
console.log(id);
}


// And then call the original
return originalEmit.call(event, ...args);
}

You can very easily extend this and filter based on event name or class name.

Since Node.js v6.0.0, the new class syntax and argument spread operator is fully supported, so it's pretty safe and fairly easy to implement the desired functionality with simple inheritance and an method override:

'use strict';
var EventEmitter = require('events');


class MyEmitter extends EventEmitter {
emit(type, ...args) {
super.emit('*', ...args);
return super.emit(type, ...args) || super.emit('', ...args);
}
}

This implementation relies on the fact that the original emit method of the EventEmitter returns true/false depending if the event was handled by some listener or not. Notice that the override includes a return statement, so we keep this behavior for other consumers.

Here the idea is to use the star event (*) to create handlers that gets executed on every single event (say, for logging purposes) and the empty event ('') for a default or catch all handler, that gets executed if nothing else catches that event.

We make sure to call the star (*) event first, because in case of error events without any handlers, the result is actually an exception being thrown. For more details, take a look at the implementation of the EventEmitter.

For example:

var emitter = new MyEmitter();


emitter.on('foo', () => console.log('foo event triggered'));
emitter.on('*', () => console.log('star event triggered'));
emitter.on('', () => console.log('catch all event triggered'));


emitter.emit('foo');
// Prints:
//   star event triggered
//   foo event triggered


emitter.emit('bar');
// Prints:
//   star event triggered
//   catch all event triggered

Finally, if an EventEmitter instance already exists but you want to adjust that specific instance to the new behavior, it can be easily done by patching the method at runtime like this:

emitter.emit = MyEmitter.prototype.emit;

a monkey patch add onAny method to EventEmitter.

it is useful to be able to monitor only events of one problem.

var EventEmitter=require('events')
var origemit=EventEmitter.prototype.emit;
Object.assign( EventEmitter.prototype, {
emit:function(){
if(this._onAnyListeners){
this._onAnyListeners.forEach((listener)=>listener.apply(this,arguments))
}
return origemit.apply(this,arguments)
},
onAny:function(func){
if(typeof func !== 'function'){
throw new Error('Invalid type');
}
if(!this._onAnyListeners)this._onAnyListeners=[];
this._onAnyListeners.push(func);
},
removeOnAny:function(func){
const index = this._onAnyListeners.indexOf(func);
if(index === -1){
return;
}
this._onAnyListeners.splice(index,1);
}
});
// usage example
//gzip.onAny(function(a){console.log(a)})