Skip to content

Commit

Permalink
The module now exports a constructor (#361)
Browse files Browse the repository at this point in the history
Now this module exports a constructor.  In addition, the `start`
method has been renamed to `startAgent`, and the agent must be
stopped before it can be started again.

These changes were made to more closely align this module with
the other Google Cloud API Client Libraries via the
`google-cloud` modules in [1].

[1]: https://github.com/GoogleCloudPlatform/google-cloud-node
  • Loading branch information
DominicKramer authored Feb 7, 2017
1 parent 7b306d7 commit b0e6c04
Show file tree
Hide file tree
Showing 49 changed files with 1,173 additions and 786 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ This module provides Stackdriver Trace support for Node.js applications. [Stackd

3. Include and start the library *as the very first action in your application*:

require('@google/cloud-trace').start();
var agent = require('@google/cloud-trace')().startAgent();

If you use `--require` in your start up command, make sure that the trace agent is --required first.

Expand All @@ -47,7 +47,7 @@ If you are running somewhere other than the Google Cloud Platform, see [running

See [the default configuration](config.js) for a list of possible configuration options. These options can be passed to the agent through the object argument to the start command shown above:

require('@google/cloud-trace').start({samplingRate: 500});
require('@google/cloud-trace')().startAgent({samplingRate: 500});

Alternatively, you can provide configuration through a config file. This can be useful if you want to load our module using `--require` on the command line instead of editing your main script. You can start by copying the default config file and modifying it to suit your needs. The `GCLOUD_DIAGNOSTICS_CONFIG` environment variable should point to your configuration file.

Expand Down Expand Up @@ -142,7 +142,7 @@ For any of the web frameworks listed above (`express`, `hapi`, and `restify`), a
The API is exposed by the `agent` returned by a call to `start`:

```javascript
var agent = require('@google/cloud-trace').start();
var agent = require('@google/cloud-trace')().startAgent();
```

For child spans, you can either use the `startSpan` and `endSpan` API, or use the `runInSpan` function that uses a callback-style. For root spans, you must use `runInRootSpan`.
Expand Down
3 changes: 2 additions & 1 deletion bin/run-test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ function run {
}

# Run test/coverage
run test test/hooks
run test
run test/hooks
for test in test/standalone/test-*.js ;
do
if [[ ! $(node --version) =~ v0\.12\..* || ! "${test}" =~ .*trace\-koa\.js ]]
Expand Down
76 changes: 66 additions & 10 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,11 @@ var initConfig = function(projectConfig) {
*/
var publicAgent = {
isActive: function() {
return agent !== phantomTraceAgent;
// TODO: The use of agent.isRunning() is only needed because the
// _private() function is used in testing.
// Remove the _private() function so that agent.isRunning()
// can be removed.
return agent !== phantomTraceAgent && agent.isRunning();
},

startSpan: function(name, labels) {
Expand All @@ -122,15 +126,13 @@ var publicAgent = {
return agent.addTransactionLabel(key, value);
},

start: function(projectConfig) {
if (this.isActive()) { // already started.
agent.logger.warn('Calling start on already started agent.' +
'New configuration will be ignored.');
return this;
}

startAgent: function(projectConfig) {
var config = initConfig(projectConfig);

if (this.isActive() && !config.forceNewAgent_) { // already started.
throw new Error('Cannot call start on an already started agent.');
}

if (!config.enabled) {
return this;
}
Expand Down Expand Up @@ -229,9 +231,63 @@ var publicAgent = {
private_: function() { return agent; }
};

module.exports = global._google_trace_agent = publicAgent;
/**
* <p class="notice">
* *This module is experimental, and should be used by early adopters. This
* module uses APIs that may be undocumented and subject to change without
* notice.*
* </p>
*
* This module provides Stackdriver Trace support for Node.js applications.
* [Stackdriver Trace](https://cloud.google.com/cloud-trace/) is a feature of
* [Google Cloud Platform](https://cloud.google.com/) that collects latency
* data (traces) from your applications and displays it in near real-time in
* the [Google Cloud Console][cloud-console].
*
* @constructor
* @alias module:trace
*
* @resource [What is Stackdriver Trace]{@link
* https://cloud.google.com/cloud-trace/}
*
* @param {object} options - [Configuration object](#/docs)
*/
// TODO: Remove this constructor.
function Trace(options) {
if (!(this instanceof Trace)) {
return new Trace(options);
}
}

/**
* Start the Trace agent that will make your application available for
* tracing with Stackdriver Trace.
*
* @param {object=} config - Trace configuration
*
* @resource [Introductory video]{@link
* https://www.youtube.com/watch?v=NCFDqeo7AeY}
*
* @example
* trace.startAgent();
*/
Trace.prototype.startAgent = function(config) {
publicAgent.startAgent(config);
return publicAgent;
};

Trace.prototype.isActive = function() {
return publicAgent.isActive();
};

Trace.prototype.get = function() {
return publicAgent.get();
};

global._google_trace_agent = publicAgent;
module.exports = Trace;

// If the module was --require'd from the command line, start the agent.
if (module.parent && module.parent.id === 'internal/preload') {
module.exports.start();
module.exports().startAgent();
}
9 changes: 9 additions & 0 deletions src/trace-agent.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ var traceAgent;
function TraceAgent(config, logger) {
this.config_ = config;
this.logger = logger;
this.running = true;

hooks.activate(this);
pluginLoader.activate(this);
Expand All @@ -60,10 +61,18 @@ function TraceAgent(config, logger) {
logger.info('trace agent activated');
}

TraceAgent.prototype.isRunning = function() {
return this.running;
};

/**
* Halts this agent and unpatches any patched modules.
*/
TraceAgent.prototype.stop = function() {
// TODO: This property is only needed because of the way the tests are
// implemented. Change the tests so that this property is not
// needed.
this.running = false;
hooks.deactivate();
pluginLoader.deactivate();
cls.destroyNamespace();
Expand Down
3 changes: 2 additions & 1 deletion test/fixtures/preloaded-agent.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
'use strict';

var assert = require('assert');
var agent = require('../../');
var agent = require('../../')();

assert(agent.isActive());
agent.get().stop();
console.log('Preload test passed.');
2 changes: 1 addition & 1 deletion test/fixtures/start-agent.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,4 @@

process.env.GCLOUD_TRACE_LOGLEVEL = 4;
require('glob'); // Load a module before agent
require('../..').start();
require('../..')().startAgent();
96 changes: 74 additions & 22 deletions test/hooks/common.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,8 @@ if (!process.env.GCLOUD_PROJECT) {
process.exit(1);
}

var config = { enhancedDatabaseReporting: true, samplingRate: 0 };
var agent = require('../..').start(config).private_();
// We want to disable publishing to avoid conflicts with production.
agent.traceWriter.publish_ = function() {};
agent._shouldTraceArgs = [];
var shouldTrace = agent.shouldTrace;
agent.shouldTrace = function() {
agent._shouldTraceArgs.push([].slice.call(arguments, 0));
return shouldTrace.apply(this, arguments);
};
require('../../src/trace-writer').publish_ = function() {};

var cls = require('../../src/cls.js');

Expand All @@ -45,32 +37,66 @@ var SERVER_RES = '1729';
var SERVER_KEY = fs.readFileSync(path.join(__dirname, 'fixtures', 'key.pem'));
var SERVER_CERT = fs.readFileSync(path.join(__dirname, 'fixtures', 'cert.pem'));

function init(agent) {
agent._shouldTraceArgs = [];
var shouldTrace = agent.shouldTrace;
agent.shouldTrace = function() {
agent._shouldTraceArgs.push([].slice.call(arguments, 0));
return shouldTrace.apply(this, arguments);
};
}

/**
* Cleans the tracer state between test runs.
*/
function cleanTraces() {
function cleanTraces(agent) {
if (arguments.length !== 1) {
throw new Error('cleanTraces() expected 1 argument. ' +
'Received: ' + arguments.length);
}

agent.traceWriter.buffer_ = [];
agent._shouldTraceArgs = [];
}

function getTraces() {
function getTraces(agent) {
if (arguments.length !== 1) {
throw new Error('getTraces() expected 1 argument. ' +
'Received: ' + arguments.length);
}

return agent.traceWriter.buffer_.map(JSON.parse);
}

function getShouldTraceArgs() {
function getShouldTraceArgs(agent) {
if (arguments.length !== 1) {
throw new Error('getSHouldTraceArgs() expected 1 argument. ' +
'Received: ' + arguments.length);
}

return agent._shouldTraceArgs;
}

function getMatchingSpan(predicate) {
var spans = getMatchingSpans(predicate);
function getMatchingSpan(agent, predicate) {
if (arguments.length !== 2) {
throw new Error('getMatchingSpan() expected 2 arguments. ' +
'Received: ' + arguments.length);
}

var spans = getMatchingSpans(agent, predicate);
assert.equal(spans.length, 1,
'predicate did not isolate a single span');
return spans[0];
}

function getMatchingSpans(predicate) {
function getMatchingSpans(agent, predicate) {
if (arguments.length !== 2) {
throw new Error('getMatchingSpans() expected 2 arguments. ' +
'Received: ' + arguments.length);
}

var list = [];
getTraces().forEach(function(trace) {
getTraces(agent).forEach(function(trace) {
trace.spans.forEach(function(span) {
if (predicate(span)) {
list.push(span);
Expand All @@ -81,6 +107,11 @@ function getMatchingSpans(predicate) {
}

function assertSpanDurationCorrect(span) {
if (arguments.length !== 1) {
throw new Error('assertSpanDurationCorrect() expected 1 argument. ' +
'Received: ' + arguments.length);
}

var duration = Date.parse(span.endTime) - Date.parse(span.startTime);
assert(duration > SERVER_WAIT * (1 - FORGIVENESS),
'Duration was ' + duration + ', expected ' + SERVER_WAIT);
Expand All @@ -100,27 +131,42 @@ function assertSpanDurationCorrect(span) {
*
* @param {function(?)=} predicate
*/
function assertDurationCorrect(predicate) {
function assertDurationCorrect(agent, predicate) {
if (arguments.length === 0) {
throw new Error('assertDurationCorrect() expected at lest one argument. ' +
'Received: ' + arguments.length);
}

// We assume that the tests never care about top level transactions created
// by the harness itself
predicate = predicate || function(span) { return span.name !== 'outer'; };
var span = getMatchingSpan(predicate);
var span = getMatchingSpan(agent, predicate);
assertSpanDurationCorrect(span);
}

function doRequest(method, done, tracePredicate, path) {
function doRequest(agent, method, done, tracePredicate, path) {
if (arguments.length < 4) {
throw new Error('doRequest() expected at least 4 arguments. ' +
'Received: ' + arguments.length);
}

http.get({port: SERVER_PORT, method: method, path: path || '/'}, function(res) {
var result = '';
res.on('data', function(data) { result += data; });
res.on('end', function() {
assert.equal(SERVER_RES, result);
assertDurationCorrect(tracePredicate);
assertDurationCorrect(agent, tracePredicate);
done();
});
});
}

function runInTransaction(fn) {
function runInTransaction(agent, fn) {
if (arguments.length !== 2) {
throw new Error('runInTransaction() expected 2 arguments. ' +
'Received: ' + arguments.length);
}

cls.getNamespace().run(function() {
var spanData = agent.createRootSpanData('outer');
fn(function() {
Expand All @@ -133,7 +179,12 @@ function runInTransaction(fn) {
// Also calls cb after that duration.
// Returns a method which, when called, closes the child span
// right away and cancels callback from being called after the duration.
function createChildSpan(cb, duration) {
function createChildSpan(agent, cb, duration) {
if (arguments.length !== 3) {
throw new Error('createChildSpan() expected 3 arguments. ' +
'Received: ' + arguments.length);
}

var span = agent.startSpan('inner');
var t = setTimeout(function() {
agent.endSpan(span);
Expand All @@ -148,6 +199,7 @@ function createChildSpan(cb, duration) {
}

module.exports = {
init: init,
assertSpanDurationCorrect: assertSpanDurationCorrect,
assertDurationCorrect: assertDurationCorrect,
cleanTraces: cleanTraces,
Expand Down
Loading

0 comments on commit b0e6c04

Please sign in to comment.