jstrace
Version:
dynamic tracing written in javascript (similar to dtrace/ktap etc)
246 lines (213 loc) • 4.75 kB
JavaScript
/**
* Module dependencies.
*/
var debug = require('debug')('jstrace:remote');
var assert = require('assert');
var utils = require('./utils');
var util = require('util');
var Transform = require('stream').Transform;
/**
* Expose `Remote`.
*/
module.exports = Remote;
/**
* Initialize a remote with the given
* function `body` string.
*
* @param {String} body
* @param {Server} server
* @api private
*/
function Remote(body, server) {
body = parse(body);
this.fn = new Function('traces', 'console', 'process', 'require', body);
this.actor = server.actor;
this.server = server;
this.patterns = [];
this.handlers = [];
this.invoke();
this.regexp = utils.patterns(this.patterns);
}
/**
* Invoke the .remote function that has been constructed,
* and report back with errors.
*
* @api private
*/
Remote.prototype.invoke = function(){
var proc = {
stdout: this.stdout(),
stderr: this.stderr(),
__proto__: process
};
try {
this.fn(this, this, proc, require);
} catch (err) {
// TODO: decorate with hostname/pid etc
this.error(err.stack);
}
var self = this;
this.actor.on('cleanup', function(reply){
debug('cleanup');
if (~self.patterns.indexOf('cleanup')) {
self.handlers.forEach(function(h){
if ('cleanup' == h.pattern) h.fn();
});
}
reply();
});
};
/**
* Remote process.stdout implementation.
*
* @return {Stream}
* @api private
*/
Remote.prototype.stdout = function(){
var self = this;
var stream = Transform();
stream._transform = function(chunk, _, done){
debug('stdout %j', chunk.toString());
if (!self.actor) {
debug('no actor');
return done();
}
self.actor.send('stdout', chunk, done);
};
return stream;
};
/**
* Remote process.stderr implementation.
*
* @return {Stream}
* @api private
*/
Remote.prototype.stderr = function(){
var self = this;
var stream = Transform();
stream._transform = function(chunk, _, done){
debug('stderr %j', chunk.toString());
if (!self.actor) {
debug('no actor');
return done();
}
self.actor.send('stderr', chunk, done);
};
return stream;
};
/**
* Remote console.log() implementation.
*
* @param {Mixed} ...
* @api public
*/
Remote.prototype.log = function(){
var str = util.format.apply(this, arguments) + '\n'
debug('log %j', str);
if (!this.actor) return debug('no actor');
this.actor.send('stdout', str);
};
/**
* Remote console.dir() implementation.
*
* Prefixed with the hostname/process/pid.
* TODO: just send server info and format on the client
*
* @param {Mixed} ...
* @api public
*/
Remote.prototype.dir = function(){
var str = util.format.apply(this, arguments) + '\n'
debug('dir %j', str);
if (!this.actor) return debug('no actor');
this.actor.send('stdio', {
stream: 'stdout',
value: this.server.prefix(str)
});
};
/**
* Remote console.error() implementation.
*
* @param {Mixed} ...
* @api public
*/
Remote.prototype.error = function(){
var str = util.format.apply(this, arguments) + '\n'
debug('error %j', str);
if (!this.actor) return debug('no actor');
this.actor.send('stderr', str);
};
/**
* Check if remote has subscribed to `name`.
*
* @param {String} name
* @return {Boolean}
* @api private
*/
Remote.prototype.subscribed = function(name){
return this.regexp.test(name);
};
/**
* Send a trace, evaluated on the remote side.
*
* @param {String} name
* @param {Object} obj
* @api private
*/
Remote.prototype.send = function(name, obj){
this.trace(this.server.trace(name, obj));
};
/**
* Pass `trace` to the matching pattern methods.
*
* @param {Object} trace
* @api private
*/
Remote.prototype.trace = function(trace){
this.handlers.forEach(function(h){
if (!h.regexp.test(trace.name)) return;
h.fn(trace);
});
};
/**
* Listen on `pattern` and invoke `fn()`.
*
* @param {String} pattern
* @param {Function} fn
* @api public
*/
Remote.prototype.on = function(pattern, fn){
debug('on %j', pattern);
this.patterns.push(pattern);
this.handlers.push({
regexp: utils.pattern(pattern),
pattern: pattern,
fn: fn
});
};
/**
* Emit a remote event, received by the client.
*
* @param {String} name
* @param {Mixed} [arg ...]
* @api public
*/
Remote.prototype.emit = function(name){
var args = [].slice.call(arguments, 1);
debug('emit %j %j', name, args);
if (!this.actor) return debug('no actor');
this.actor.send('event', {
name: name,
args: args
});
};
/**
* Return the body of a serialized function.
*
* @param {String} str
* @return {String}
* @api private
*/
function parse(str) {
return str.slice(str.indexOf('{') + 1, -1).trim();
}