@google-cloud/trace-agent
Advanced tools
Comparing version 2.1.0 to 2.1.1
# Node.js Agent for Google Cloud Trace ChangeLog | ||
## 2017-07-17, Version 2.1.1 (Beta), @ofrobots | ||
This module is now in Beta. | ||
### Commits | ||
* [[`17555e2071`](https://github.com/GoogleCloudPlatform/cloud-trace-nodejs/commit/17555e2071)] - beta (#524) (Ali Ijaz Sheikh) [#524](https://github.com/GoogleCloudPlatform/cloud-trace-nodejs/pull/524) | ||
* [[`e6671790c1`](https://github.com/GoogleCloudPlatform/cloud-trace-nodejs/commit/e6671790c1)] - Deduplicate internal code (#511) (Kelvin Jin) [#511](https://github.com/GoogleCloudPlatform/cloud-trace-nodejs/pull/511) | ||
* [[`eaab39ac1e`](https://github.com/GoogleCloudPlatform/cloud-trace-nodejs/commit/eaab39ac1e)] - **test**: omit agent argument from all test-common helper functions (#518) (Kelvin Jin) [#518](https://github.com/GoogleCloudPlatform/cloud-trace-nodejs/pull/518) | ||
* [[`ca72dd7f44`](https://github.com/GoogleCloudPlatform/cloud-trace-nodejs/commit/ca72dd7f44)] - warn when creating a child of a closed span (#520) (Ali Ijaz Sheikh) | ||
* [[`be0b006b35`](https://github.com/GoogleCloudPlatform/cloud-trace-nodejs/commit/be0b006b35)] - make TraceWriter a singleton (#517) (Kelvin Jin) [#517](https://github.com/GoogleCloudPlatform/cloud-trace-nodejs/pull/517) | ||
* [[`eb0a11be23`](https://github.com/GoogleCloudPlatform/cloud-trace-nodejs/commit/eb0a11be23)] - increase severity of module order log message (#519) (Ali Ijaz Sheikh) | ||
* [[`300dc4fc34`](https://github.com/GoogleCloudPlatform/cloud-trace-nodejs/commit/300dc4fc34)] - Fix document source link (#514) (Oleg Shalygin) [#514](https://github.com/GoogleCloudPlatform/cloud-trace-nodejs/pull/514) | ||
* [[`8561232a04`](https://github.com/GoogleCloudPlatform/cloud-trace-nodejs/commit/8561232a04)] - Fix typos (#513) (Oleg Shalygin) [#513](https://github.com/GoogleCloudPlatform/cloud-trace-nodejs/pull/513) | ||
* [[`a9e46cb1c8`](https://github.com/GoogleCloudPlatform/cloud-trace-nodejs/commit/a9e46cb1c8)] - Update datastore test to use datastore module (#509) (Matthew Loring) [#509](https://github.com/GoogleCloudPlatform/cloud-trace-nodejs/pull/509) | ||
## 2017-06-12, Version 2.1.0 (Experimental), @matthewloring | ||
@@ -4,0 +20,0 @@ |
126
index.js
@@ -25,12 +25,15 @@ /** | ||
var cls = require('./src/cls.js'); | ||
var common = require('@google-cloud/common'); | ||
var extend = require('extend'); | ||
var constants = require('./src/constants.js'); | ||
var gcpMetadata = require('gcp-metadata'); | ||
var traceUtil = require('./src/util.js'); | ||
var TraceApi = require('./src/trace-api.js'); | ||
var TraceAgent = require('./src/trace-api.js'); | ||
var pluginLoader = require('./src/trace-plugin-loader.js'); | ||
var TraceWriter = require('./src/trace-writer.js'); | ||
var modulesLoadedBeforeTrace = []; | ||
var traceAgent; | ||
for (var i = 0; i < filesLoadedBeforeTrace.length; i++) { | ||
@@ -44,5 +47,10 @@ var moduleName = traceUtil.packageNameFromPath(filesLoadedBeforeTrace[i]); | ||
var onUncaughtExceptionValues = ['ignore', 'flush', 'flushAndExit']; | ||
var initConfig = function(projectConfig) { | ||
/** | ||
* Normalizes the user-provided configuration object by adding default values | ||
* and overriding with env variables when they are provided. | ||
* @param {*} projectConfig The user-provided configuration object. It will not | ||
* be modified. | ||
* @return A normalized configuration object. | ||
*/ | ||
function initConfig(projectConfig) { | ||
var envConfig = { | ||
@@ -58,10 +66,28 @@ logLevel: process.env.GCLOUD_TRACE_LOGLEVEL, | ||
var config = extend(true, {}, require('./config.js'), projectConfig, envConfig); | ||
// Enforce the upper limit for the label value size. | ||
if (config.maximumLabelValueSize > constants.TRACE_SERVICE_LABEL_VALUE_LIMIT) { | ||
config.maximumLabelValueSize = constants.TRACE_SERVICE_LABEL_VALUE_LIMIT; | ||
} | ||
// Clamp the logger level. | ||
if (config.logLevel < 0) { | ||
config.logLevel = 0; | ||
} else if (config.logLevel >= common.logger.LEVELS.length) { | ||
config.logLevel = common.logger.LEVELS.length - 1; | ||
} | ||
return config; | ||
}; | ||
} | ||
var traceApi = new TraceApi('Custom Span API'); | ||
var agent; | ||
/** | ||
* Stops the Trace Agent. This disables the publicly exposed agent instance, | ||
* as well as any instances passed to plugins. This also prevents the Trace | ||
* Writer from publishing additional traces. | ||
*/ | ||
function stop() { | ||
if (traceAgent && traceAgent.isActive()) { | ||
TraceWriter.get().stop(); | ||
traceAgent.disable(); | ||
pluginLoader.deactivate(); | ||
cls.destroyNamespace(); | ||
} | ||
} | ||
@@ -83,15 +109,14 @@ /** | ||
if (traceApi.isActive() && !config.forceNewAgent_) { // already started. | ||
if (traceAgent && !config.forceNewAgent_) { // already started. | ||
throw new Error('Cannot call start on an already started agent.'); | ||
} else if (traceAgent) { | ||
// For unit tests only. | ||
// Undoes initialization that occurred last time start() was called. | ||
stop(); | ||
} | ||
if (!config.enabled) { | ||
return traceApi; | ||
return traceAgent; | ||
} | ||
if (config.logLevel < 0) { | ||
config.logLevel = 0; | ||
} else if (config.logLevel >= common.logger.LEVELS.length) { | ||
config.logLevel = common.logger.LEVELS.length - 1; | ||
} | ||
var logger = common.logger({ | ||
@@ -102,69 +127,36 @@ level: common.logger.LEVELS[config.logLevel], | ||
if (config.projectId) { | ||
logger.info('Locally provided ProjectId: ' + config.projectId); | ||
} | ||
if (onUncaughtExceptionValues.indexOf(config.onUncaughtException) === -1) { | ||
logger.error('The value of onUncaughtException should be one of ', | ||
onUncaughtExceptionValues); | ||
throw new Error('Invalid value for onUncaughtException configuration.'); | ||
} | ||
var headers = {}; | ||
headers[constants.TRACE_AGENT_REQUEST_HEADER] = 1; | ||
if (modulesLoadedBeforeTrace.length > 0) { | ||
logger.warn('Tracing might not work as the following modules ' + | ||
logger.error('Tracing might not work as the following modules ' + | ||
'were loaded before the trace agent was initialized: ' + | ||
JSON.stringify(modulesLoadedBeforeTrace)); | ||
} | ||
// CLS namespace for context propagation | ||
cls.createNamespace(); | ||
TraceWriter.create(logger, config, function(err) { | ||
if (err) { | ||
stop(); | ||
} | ||
}); | ||
if (typeof config.projectId === 'undefined') { | ||
// Queue the work to acquire the projectId (potentially from the | ||
// network.) | ||
gcpMetadata.project({ | ||
property: 'project-id', | ||
headers: headers | ||
}, function(err, response, projectId) { | ||
if (response && response.statusCode !== 200) { | ||
if (response.statusCode === 503) { | ||
err = new Error('Metadata service responded with a 503 status ' + | ||
'code. This may be due to a temporary server error; please try ' + | ||
'again later.'); | ||
} else { | ||
err = new Error('Metadata service responded with the following ' + | ||
'status code: ' + response.statusCode); | ||
} | ||
} | ||
if (err) { | ||
logger.error('Unable to acquire the project number from metadata ' + | ||
'service. Please provide a valid project number as an env. ' + | ||
'variable, or through config.projectId passed to start(). ' + err); | ||
if (traceApi.isActive()) { | ||
agent.stop(); | ||
traceApi.disable_(); | ||
pluginLoader.deactivate(); | ||
} | ||
return; | ||
} | ||
config.projectId = projectId; | ||
}); | ||
} else if (typeof config.projectId !== 'string') { | ||
traceAgent = new TraceAgent('Custom Span API', logger, config); | ||
pluginLoader.activate(logger, config); | ||
if (typeof config.projectId !== 'string' && typeof config.projectId !== 'undefined') { | ||
logger.error('config.projectId, if provided, must be a string. ' + | ||
'Disabling trace agent.'); | ||
return traceApi; | ||
stop(); | ||
return traceAgent; | ||
} | ||
agent = require('./src/trace-agent.js').get(config, logger); | ||
traceApi.enable_(agent); | ||
pluginLoader.activate(agent); | ||
// Make trace agent available globally without requiring package | ||
global._google_trace_agent = traceAgent; | ||
return traceApi; | ||
logger.info('trace agent activated'); | ||
return traceAgent; | ||
} | ||
function get() { | ||
return traceApi; | ||
return traceAgent; | ||
} | ||
global._google_trace_agent = traceApi; | ||
module.exports = { | ||
@@ -171,0 +163,0 @@ start: start, |
{ | ||
"name": "@google-cloud/trace-agent", | ||
"version": "2.1.0", | ||
"version": "2.1.1", | ||
"description": "Node.js Support for StackDriver Trace", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
@@ -10,3 +10,3 @@ # Stackdriver Trace for Node.js | ||
> *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.* | ||
> **Beta**. *This is a Beta release of the Stackdriver Trace agent for Node.js. These libraries might be changed in backward-incompatible ways and are not subject to any SLA or deprecation policy.* | ||
@@ -13,0 +13,0 @@ 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]. |
@@ -20,3 +20,2 @@ /** | ||
var constants = require('./constants.js'); | ||
var is = require('is'); | ||
var TraceSpan = require('./trace-span.js'); | ||
@@ -26,2 +25,3 @@ var TraceLabels = require('./trace-labels.js'); | ||
var util = require('util'); | ||
var TraceWriter = require('./trace-writer.js'); | ||
@@ -40,5 +40,4 @@ // Auto-incrementing integer | ||
*/ | ||
function SpanData(agent, trace, name, parentSpanId, isRoot, skipFrames) { | ||
function SpanData(trace, name, parentSpanId, isRoot, skipFrames) { | ||
var spanId = uid++; | ||
this.agent = agent; | ||
var spanName = traceUtil.truncate(name, constants.TRACE_SERVICE_SPAN_NAME_LIMIT); | ||
@@ -48,4 +47,9 @@ this.span = new TraceSpan(spanName, spanId, parentSpanId); | ||
this.isRoot = isRoot; | ||
this.serializedTraceContext = traceUtil.generateTraceContext({ | ||
traceId: this.trace.traceId, | ||
spanId: this.span.spanId, | ||
options: 1 // always traced | ||
}); | ||
trace.spans.push(this.span); | ||
if (agent.config().stackTraceLimit > 0) { | ||
if (TraceWriter.get().config().stackTraceLimit > 0) { | ||
// This is a mechanism to get the structured stack trace out of V8. | ||
@@ -59,3 +63,3 @@ // prepareStackTrace is called th first time the Error#stack property is | ||
var origLimit = Error.stackTraceLimit; | ||
Error.stackTraceLimit = agent.config().stackTraceLimit + skipFrames; | ||
Error.stackTraceLimit = TraceWriter.get().config().stackTraceLimit + skipFrames; | ||
@@ -94,12 +98,4 @@ var origPrepare = Error.prepareStackTrace; | ||
/** | ||
* Creates a child span of this span. | ||
* @param name The name of the child span. | ||
* @param {number} skipFrames The number of caller frames to eliminate from | ||
* stack traces. | ||
* @returns {SpanData} The new child trace span data. | ||
*/ | ||
SpanData.prototype.createChildSpanData = function(name, skipFrames) { | ||
return new SpanData(this.agent, this.trace, name, this.span.spanId, false, | ||
skipFrames + 1); | ||
SpanData.prototype.getTraceContext = function() { | ||
return this.serializedTraceContext; | ||
}; | ||
@@ -110,3 +106,3 @@ | ||
var string_val = typeof value === 'string' ? value : util.inspect(value); | ||
var v = traceUtil.truncate(string_val, this.agent.config().maximumLabelValueSize); | ||
var v = traceUtil.truncate(string_val, TraceWriter.get().config().maximumLabelValueSize); | ||
this.span.setLabel(k, v); | ||
@@ -116,24 +112,8 @@ }; | ||
/** | ||
* Add properties from provided `labels` param to the span as labels. | ||
* | ||
* @param {Object<string, string}>=} labels Labels to be attached to the newly | ||
* created span. Non-object data types are silently ignored. | ||
* Closes the span. | ||
*/ | ||
SpanData.prototype.addLabels = function(labels) { | ||
var that = this; | ||
if (is.object(labels)) { | ||
Object.keys(labels).forEach(function(key) { | ||
that.addLabel(key, labels[key]); | ||
}); | ||
} | ||
}; | ||
/** | ||
* Closes the span and queues it for publishing if it is a root. | ||
*/ | ||
SpanData.prototype.close = function() { | ||
SpanData.prototype.endSpan = function() { | ||
this.span.close(); | ||
if (this.isRoot) { | ||
this.agent.logger.info('Writing root span'); | ||
this.agent.traceWriter.writeSpan(this); | ||
TraceWriter.get().writeSpan(this); | ||
} | ||
@@ -140,0 +120,0 @@ }; |
@@ -20,98 +20,74 @@ /** | ||
var constants = require('./constants.js'); | ||
var extend = require('extend'); | ||
var is = require('is'); | ||
var TraceLabels = require('./trace-labels.js'); | ||
var util = require('./util.js'); | ||
var Trace = require('./trace.js'); | ||
var SpanData = require('./span-data.js'); | ||
var uuid = require('uuid'); | ||
var TracingPolicy = require('./tracing-policy.js'); | ||
/** | ||
* This file describes an interface for third-party plugins to enable tracing | ||
* for arbitrary modules. | ||
* Phantom implementation of the trace api. When disabled, a TraceAgent instance | ||
* will have its public method implementations replaced with corresponding | ||
* no-op implementations in this object. | ||
*/ | ||
/** | ||
* An object that represents a single child span. It exposes functions for | ||
* adding labels to or closing the span. | ||
* @param {TraceAgent} agent The underlying trace agent object. | ||
* @param {SpanData} span The internal data structure backing the child span. | ||
*/ | ||
function ChildSpan(agent, span) { | ||
this.agent_ = agent; | ||
this.span_ = span; | ||
this.serializedTraceContext_ = agent.generateTraceContext(span, true); | ||
} | ||
/** | ||
* Adds a label to the child span. | ||
* @param {string} key The name of the label to add. | ||
* @param {*} value The value of the label to add. | ||
*/ | ||
ChildSpan.prototype.addLabel = function(key, value) { | ||
this.span_.addLabel(key, value); | ||
var phantomApiImpl = { | ||
enhancedDatabaseReportingEnabled: function() { return false; }, | ||
runInRootSpan: function(opts, fn) { return fn(null); }, | ||
createChildSpan: function(opts) { return null; }, | ||
getResponseTraceContext: function(context, traced) { return ''; }, | ||
wrap: function(fn) { return fn; }, | ||
wrapEmitter: function(ee) {}, | ||
}; | ||
/** | ||
* Ends the child span. This function should only be called once. | ||
*/ | ||
ChildSpan.prototype.endSpan = function() { | ||
this.span_.close(); | ||
}; | ||
// A sentinal stored in CLS to indicate that the current request was not sampled. | ||
var nullSpan = {}; | ||
/** | ||
* Gets the trace context serialized as a string. This string can be set as the | ||
* 'x-cloud-trace-context' field in an HTTP request header to support | ||
* distributed tracing. | ||
* TraceAgent exposes a number of methods to create trace spans and propagate | ||
* trace context across asynchronous boundaries. Trace spans are published | ||
* in the background with a separate TraceWriter instance, which should be | ||
* initialized beforehand. | ||
* @constructor | ||
* @param {String} name A string identifying this TraceAgent instance in logs. | ||
* @param {common.logger} logger A logger object. | ||
* @param {Configuration} config An object specifying how this instance should | ||
* be configured. | ||
*/ | ||
ChildSpan.prototype.getTraceContext = function() { | ||
return this.serializedTraceContext_; | ||
}; | ||
/** | ||
* An object that represents a single root span. It exposes functions for adding | ||
* labels to or closing the span. | ||
* @param {TraceAgent} agent The underlying trace agent object. | ||
* @param {SpanData} span The internal data structure backing the root span. | ||
*/ | ||
function RootSpan(agent, span) { | ||
this.agent_ = agent; | ||
this.span_ = span; | ||
this.serializedTraceContext_ = agent.generateTraceContext(span, true); | ||
function TraceAgent(name, logger, config) { | ||
this.pluginName_ = name; | ||
this.logger_ = logger; | ||
this.namespace_ = cls.getNamespace(); | ||
this.policy_ = TracingPolicy.createTracePolicy(config); | ||
this.config_ = config; | ||
} | ||
/** | ||
* Adds a label to the span. | ||
* @param {string} key The name of the label to add. | ||
* @param {*} value The value of the label to add. | ||
* Disable this TraceAgent instance. This function is only for internal use and | ||
* unit tests. | ||
* @private | ||
*/ | ||
RootSpan.prototype.addLabel = function(key, value) { | ||
this.span_.addLabel(key, value); | ||
TraceAgent.prototype.disable = function() { | ||
// Even though plugins should be unpatched, setting a new policy that | ||
// never generates traces allows persisting wrapped methods (either because | ||
// they are already instantiated or the plugin doesn't unpatch them) to | ||
// short-circuit out of trace generation logic. | ||
this.policy_ = new TracingPolicy.TraceNonePolicy(); | ||
this.namespace_ = null; | ||
for (var memberName in phantomApiImpl) { | ||
this[memberName] = phantomApiImpl[memberName]; | ||
} | ||
}; | ||
/** | ||
* Ends the span. This function should only be called once. | ||
* Returns whether the TraceAgent instance is active. This function is only for | ||
* internal use and unit tests; under normal circumstances it will always return | ||
* true. | ||
* @private | ||
*/ | ||
RootSpan.prototype.endSpan = function() { | ||
this.span_.close(); | ||
TraceAgent.prototype.isActive = function() { | ||
return !!this.namespace_; | ||
}; | ||
/** | ||
* Gets the trace context serialized as a string. This string can be set as the | ||
* 'x-cloud-trace-context' field in an HTTP request header to support | ||
* distributed tracing. | ||
*/ | ||
RootSpan.prototype.getTraceContext = function() { | ||
return this.serializedTraceContext_; | ||
}; | ||
// A sentinal stored in CLS to indicate that the current request was not sampled. | ||
var nullSpan = {}; | ||
/** | ||
* The functional implementation of the Trace API | ||
*/ | ||
function TraceApiImplementation(agent, pluginName) { | ||
this.agent_ = agent; | ||
this.logger_ = agent.logger; | ||
this.pluginName_ = pluginName; | ||
} | ||
/** | ||
* Gets the value of enhancedDatabaseReporting in the trace agent's | ||
@@ -122,4 +98,4 @@ * configuration object. | ||
*/ | ||
TraceApiImplementation.prototype.enhancedDatabaseReportingEnabled = function() { | ||
return this.agent_.config_.enhancedDatabaseReporting; | ||
TraceAgent.prototype.enhancedDatabaseReportingEnabled = function() { | ||
return this.config_.enhancedDatabaseReporting; | ||
}; | ||
@@ -148,5 +124,8 @@ | ||
*/ | ||
TraceApiImplementation.prototype.runInRootSpan = function(options, fn) { | ||
TraceAgent.prototype.runInRootSpan = function(options, fn) { | ||
var that = this; | ||
if (!this.agent_.namespace) { | ||
// TODO validate options | ||
// Don't create a root span if the required namespace doesn't exist, or we | ||
// are already in a root span | ||
if (!this.namespace_) { | ||
this.logger_.warn(this.pluginName_ + ': CLS namespace not present; not ' + | ||
@@ -160,6 +139,32 @@ 'running in root span.'); | ||
} | ||
return this.agent_.namespace.runAndReturn(function() { | ||
var skipFrames = options.skipFrames ? options.skipFrames + 3 : 3; | ||
var rootSpan = createRootSpan_(that, options, skipFrames); | ||
return fn(rootSpan); | ||
return this.namespace_.runAndReturn(function() { | ||
// Attempt to read incoming trace context. | ||
var incomingTraceContext; | ||
if (is.string(options.traceContext) && !that.config_.ignoreContextHeader) { | ||
incomingTraceContext = util.parseContextFromHeader(options.traceContext); | ||
} | ||
incomingTraceContext = incomingTraceContext || {}; | ||
// Consult the trace policy, and don't create a root span if the trace | ||
// policy disallows it. | ||
var locallyAllowed = that.policy_.shouldTrace(Date.now(), options.url || ''); | ||
var remotelyAllowed = isNaN(incomingTraceContext.options) || | ||
(incomingTraceContext.options & constants.TRACE_OPTIONS_TRACE_ENABLED); | ||
if (!locallyAllowed || !remotelyAllowed) { | ||
cls.setRootContext(nullSpan); | ||
return fn(null); | ||
} | ||
// Create a new root span, and invoke fn with it. | ||
var traceId = incomingTraceContext.traceId || (uuid.v4().split('-').join('')); | ||
var parentId = incomingTraceContext.spanId || '0'; | ||
var rootContext = new SpanData(new Trace(0, traceId), /* Trace object */ | ||
options.name, /* Span name */ | ||
parentId, /* Parent's span ID */ | ||
true, /* Is root span */ | ||
options.skipFrames ? options.skipFrames + 2 : 2); | ||
rootContext.span.kind = 'RPC_SERVER'; | ||
cls.setRootContext(rootContext); | ||
return fn(rootContext); | ||
}); | ||
@@ -172,3 +177,3 @@ }; | ||
* @param {object} options An object that specifies options for how the child | ||
* span is created and propogated. | ||
* span is created and propagated. | ||
* @param {string} options.name The name to apply to the child span. | ||
@@ -180,6 +185,6 @@ * @param {?number} options.skipFrames The number of stack frames to skip when | ||
*/ | ||
TraceApiImplementation.prototype.createChildSpan = function(options) { | ||
TraceAgent.prototype.createChildSpan = function(options) { | ||
var rootSpan = cls.getRootContext(); | ||
if (!rootSpan) { | ||
// Lost context | ||
// Context was lost. | ||
this.logger_.warn(this.pluginName_ + ': Attempted to create child span ' + | ||
@@ -189,9 +194,19 @@ 'without root'); | ||
} else if (rootSpan === nullSpan) { | ||
// Chose not to sample | ||
// Context wasn't lost, but there's no root span, indicating that this | ||
// request should not be traced. | ||
return null; | ||
} else { | ||
if (rootSpan.span.isClosed()) { | ||
this.logger_.warn(this.pluginName_ + ': creating child for an already closed span', | ||
options.name, rootSpan.span.name); | ||
} | ||
// Create a new child span and return it. | ||
options = options || {}; | ||
var childContext = this.agent_.startSpan(options.name, {}, | ||
options.skipFrames ? options.skipFrames + 2 : 2); | ||
return new ChildSpan(this.agent_, childContext); | ||
var skipFrames = options.skipFrames ? options.skipFrames + 1 : 1; | ||
var childContext = new SpanData(rootSpan.trace, /* Trace object */ | ||
options.name, /* Span name */ | ||
rootSpan.span.spanId, /* Parent's span ID */ | ||
false, /* Is root span */ | ||
skipFrames); /* # of frames to skip in stack trace */ | ||
return childContext; | ||
} | ||
@@ -214,5 +229,5 @@ }; | ||
*/ | ||
TraceApiImplementation.prototype.getResponseTraceContext = function( | ||
TraceAgent.prototype.getResponseTraceContext = function( | ||
incomingTraceContext, isTraced) { | ||
var traceContext = this.agent_.parseContextFromHeader(incomingTraceContext); | ||
var traceContext = util.parseContextFromHeader(incomingTraceContext); | ||
if (!traceContext) { | ||
@@ -222,4 +237,3 @@ return ''; | ||
traceContext.options = traceContext.options & isTraced; | ||
return traceContext.traceId + '/' + traceContext.spanId + ';o=' + | ||
traceContext.options; | ||
return util.generateTraceContext(traceContext); | ||
}; | ||
@@ -233,4 +247,4 @@ | ||
*/ | ||
TraceApiImplementation.prototype.wrap = function(fn) { | ||
if (!this.agent_.namespace) { | ||
TraceAgent.prototype.wrap = function(fn) { | ||
if (!this.namespace_) { | ||
this.logger_.warn(this.pluginName_ + ': No CLS namespace to bind ' + | ||
@@ -240,3 +254,3 @@ 'function'); | ||
} | ||
return this.agent_.namespace.bind(fn); | ||
return this.namespace_.bind(fn); | ||
}; | ||
@@ -250,104 +264,14 @@ | ||
*/ | ||
TraceApiImplementation.prototype.wrapEmitter = function(emitter) { | ||
if (!this.agent_.namespace) { | ||
TraceAgent.prototype.wrapEmitter = function(emitter) { | ||
if (!this.namespace_) { | ||
this.logger_.warn(this.pluginName_ + ': No CLS namespace to bind ' + | ||
'emitter to'); | ||
} | ||
this.agent_.namespace.bindEmitter(emitter); | ||
this.namespace_.bindEmitter(emitter); | ||
}; | ||
TraceApiImplementation.prototype.constants = constants; | ||
TraceAgent.prototype.constants = constants; | ||
TraceApiImplementation.prototype.labels = TraceLabels; | ||
TraceAgent.prototype.labels = TraceLabels; | ||
/** | ||
* Phantom implementation of the trace api. This allows API users to decouple | ||
* the enable/disable logic from the calls to the tracing API. The phantom API | ||
* has a lower overhead than isEnabled checks inside the API functions. | ||
* @private | ||
*/ | ||
var phantomApiImpl = { | ||
enhancedDatabaseReportingEnabled: function() { return false; }, | ||
runInRootSpan: function(opts, fn) { return fn(null); }, | ||
createChildSpan: function(opts) { return null; }, | ||
getResponseTraceContext: function(context, traced) { return ''; }, | ||
wrap: function(fn) { return fn; }, | ||
wrapEmitter: function(ee) {}, | ||
constants: constants, | ||
labels: TraceLabels | ||
}; | ||
/** | ||
* Creates an object that provides an interface to the trace agent | ||
* implementation. | ||
* Upon creation, the object is in an "uninitialized" state, corresponding | ||
* to its intended (no-op) behavior before the trace agent is started. | ||
* When the trace agent is started, the interface object becomes | ||
* "initialized", and its underlying implementation is switched to that of | ||
* the actual agent implementation. | ||
* Finally, when the trace agent is stopped, this object enters the "disabled" | ||
* state, and its underlying implementation is switched back to no-op. | ||
* Currently, this only happens when the application's GCP project ID could | ||
* not be determined from the GCP metadata service. | ||
* This object's state changes strictly from uninitialized to initialized, | ||
* and from initialized to disabled. | ||
*/ | ||
module.exports = function TraceApi(pluginName) { | ||
var impl = phantomApiImpl; | ||
extend(this, { | ||
enhancedDatabaseReportingEnabled: function() { | ||
return impl.enhancedDatabaseReportingEnabled(); | ||
}, | ||
runInRootSpan: function(opts, fn) { | ||
return impl.runInRootSpan(opts, fn); | ||
}, | ||
createChildSpan: function(opts) { | ||
return impl.createChildSpan(opts); | ||
}, | ||
getResponseTraceContext: function(incomingTraceContext, isTraced) { | ||
return impl.getResponseTraceContext(incomingTraceContext, isTraced); | ||
}, | ||
wrap: function(fn) { | ||
return impl.wrap(fn); | ||
}, | ||
wrapEmitter: function(ee) { | ||
return impl.wrapEmitter(ee); | ||
}, | ||
constants: impl.constants, | ||
labels: impl.labels, | ||
isActive: function() { | ||
return impl !== phantomApiImpl; | ||
}, | ||
enable_: function(agent) { | ||
impl = new TraceApiImplementation(agent, pluginName); | ||
}, | ||
disable_: function() { | ||
impl = phantomApiImpl; | ||
}, | ||
private_: function() { return impl.agent_; } | ||
}); | ||
return this; | ||
}; | ||
// Module-private functions | ||
function createRootSpan_(api, options, skipFrames) { | ||
options = options || {}; | ||
// If the options object passed in has the getTraceContext field set, | ||
// try to retrieve the header field containing incoming trace metadata. | ||
var incomingTraceContext; | ||
if (is.string(options.traceContext)) { | ||
incomingTraceContext = api.agent_.parseContextFromHeader(options.traceContext); | ||
} | ||
incomingTraceContext = incomingTraceContext || {}; | ||
if (!api.agent_.shouldTrace(options.url || '', | ||
incomingTraceContext.options)) { | ||
cls.setRootContext(nullSpan); | ||
return null; | ||
} | ||
var rootContext = api.agent_.createRootSpanData(options.name, | ||
incomingTraceContext.traceId, | ||
incomingTraceContext.spanId, | ||
skipFrames + 1); | ||
return new RootSpan(api.agent_, rootContext); | ||
} | ||
module.exports = TraceAgent; |
@@ -23,3 +23,3 @@ /** | ||
var util = require('./util.js'); | ||
var TraceApi = require('./trace-api.js'); | ||
var TraceAgent = require('./trace-api.js'); | ||
@@ -30,3 +30,3 @@ var plugins = Object.create(null); | ||
var logger; | ||
var logger_; | ||
@@ -40,3 +40,3 @@ function checkLoadedModules() { | ||
if (file.match(regex)) { | ||
logger.error(moduleName + ' tracing might not work as ' + file + | ||
logger_.error(moduleName + ' tracing might not work as ' + file + | ||
' was loaded before the trace agent was initialized.'); | ||
@@ -50,3 +50,3 @@ break; | ||
if (first !== '@google-cloud/trace-agent') { | ||
logger.error('Tracing might not work as ' + first + | ||
logger_.error('Tracing might not work as ' + first + | ||
' was loaded with --require before the trace agent was initialized.'); | ||
@@ -65,6 +65,6 @@ } | ||
} else if (patch.unpatch && patch.intercept) { | ||
logger.warn('Plugin for ' + patch.file + ': unpatch is not compatible ' + | ||
logger_.warn('Plugin for ' + patch.file + ': unpatch is not compatible ' + | ||
'with intercept.'); | ||
} else if (patch.patch && !patch.unpatch) { | ||
logger.warn('Plugin for ' + patch.file + ': patch method given without ' + | ||
logger_.warn('Plugin for ' + patch.file + ': patch method given without ' + | ||
'accompanying unpatch.'); | ||
@@ -74,11 +74,12 @@ } | ||
function activate(agent) { | ||
function activate(logger, config) { | ||
if (activated) { | ||
logger.error('Plugins activated more than once.'); | ||
logger_.error('Plugins activated more than once.'); | ||
return; | ||
} | ||
activated = true; | ||
logger = agent.logger; | ||
var pluginConfig = agent.config().plugins; | ||
logger_ = logger; | ||
var pluginConfig = config.plugins; | ||
for (var moduleName in pluginConfig) { | ||
@@ -88,11 +89,6 @@ if (!pluginConfig[moduleName]) { | ||
} | ||
// Create a new object exposing functions to create trace spans and | ||
// propagate context. This relies on functions currently exposed by the | ||
// agent. | ||
var api = new TraceApi(moduleName); | ||
api.enable_(agent); | ||
plugins[moduleName] = { | ||
file: pluginConfig[moduleName], | ||
patches: {}, | ||
api: api | ||
agent: new TraceAgent(moduleName, logger_, config) | ||
}; | ||
@@ -124,3 +120,3 @@ } | ||
if (Object.keys(patchSet).length === 0) { | ||
logger.warn(moduleRoot + ': version ' + version + ' not supported ' + | ||
logger_.warn(moduleRoot + ': version ' + version + ' not supported ' + | ||
'by plugin.'); | ||
@@ -138,6 +134,6 @@ } | ||
if (patch.patch) { | ||
patch.patch(patch.module, instrumentation.api); | ||
patch.patch(patch.module, instrumentation.agent); | ||
} | ||
if (patch.intercept) { | ||
patch.module = patch.intercept(patch.module, instrumentation.api); | ||
patch.module = patch.intercept(patch.module, instrumentation.agent); | ||
intercepts[loadPath] = { | ||
@@ -169,3 +165,3 @@ interceptedValue: patch.module | ||
} | ||
logger.info('Patching ' + request + ' at version ' + moduleVersion); | ||
logger_.info('Patching ' + request + ' at version ' + moduleVersion); | ||
var patchedRoot = loadAndPatch(instrumentation, moduleRoot, | ||
@@ -192,3 +188,3 @@ moduleVersion); | ||
var instrumentation = plugins[moduleName]; | ||
instrumentation.api.disable_(); | ||
instrumentation.agent.disable(); | ||
for (var moduleRoot in instrumentation.patches) { | ||
@@ -199,3 +195,3 @@ var patchSet = instrumentation.patches[moduleRoot]; | ||
if (patch.unpatch !== undefined) { | ||
logger.info('Unpatching' + moduleName); | ||
logger_.info('Unpatching ' + moduleName); | ||
patch.unpatch(patch.module); | ||
@@ -202,0 +198,0 @@ } |
@@ -19,4 +19,4 @@ /** | ||
var common = require('@google-cloud/common'); | ||
var gcpMetadata = require('gcp-metadata'); | ||
var common = require('@google-cloud/common'); | ||
var util = require('util'); | ||
@@ -27,4 +27,3 @@ var traceLabels = require('./trace-labels.js'); | ||
/* @const {Array<string>} list of scopes needed to operate with the trace API */ | ||
var SCOPES = ['https://www.googleapis.com/auth/trace.append']; | ||
var onUncaughtExceptionValues = ['ignore', 'flush', 'flushAndExit']; | ||
@@ -34,2 +33,5 @@ var headers = {}; | ||
/* @const {Array<string>} list of scopes needed to operate with the trace API */ | ||
var SCOPES = ['https://www.googleapis.com/auth/trace.append']; | ||
/** | ||
@@ -42,4 +44,4 @@ * Creates a basic trace writer. | ||
*/ | ||
function TraceWriter(logger, options) { | ||
options = options || {}; | ||
function TraceWriter(logger, config) { | ||
config = config || {}; | ||
@@ -52,3 +54,3 @@ var serviceOptions = { | ||
}; | ||
common.Service.call(this, serviceOptions, options); | ||
common.Service.call(this, serviceOptions, config); | ||
@@ -59,3 +61,3 @@ /** @private */ | ||
/** @private */ | ||
this.config_ = options; | ||
this.config_ = config; | ||
@@ -71,8 +73,47 @@ /** @private {Array<string>} stringified traces to be published */ | ||
if (onUncaughtExceptionValues.indexOf(config.onUncaughtException) === -1) { | ||
logger.error('The value of onUncaughtException should be one of ', | ||
onUncaughtExceptionValues); | ||
throw new Error('Invalid value for onUncaughtException configuration.'); | ||
} | ||
var onUncaughtException = config.onUncaughtException; | ||
if (onUncaughtException !== 'ignore') { | ||
var that = this; | ||
this.unhandledException_ = function() { | ||
that.flushBuffer_(); | ||
if (onUncaughtException === 'flushAndExit') { | ||
setTimeout(function() { | ||
process.exit(1); | ||
}, 2000); | ||
} | ||
}; | ||
process.on('uncaughtException', this.unhandledException_); | ||
} | ||
} | ||
util.inherits(TraceWriter, common.Service); | ||
TraceWriter.prototype.stop = function() { | ||
this.isActive = false; | ||
}; | ||
TraceWriter.prototype.initialize = function(cb) { | ||
var that = this; | ||
// Ensure that cb is called only once. | ||
var pendingOperations = 2; | ||
// Schedule periodic flushing of the buffer, but only if we are able to get | ||
// the project number (potentially from the network.) | ||
var that = this; | ||
that.getProjectId(function(err, project) { | ||
if (err) { return; } // ignore as index.js takes care of this. | ||
that.scheduleFlush_(project); | ||
if (err) { | ||
that.logger_.error('Unable to acquire the project number from metadata ' + | ||
'service. Please provide a valid project number as an env. ' + | ||
'variable, or through config.projectId passed to start(). ' + err); | ||
cb(err); | ||
} else { | ||
that.config_.projectId = project; | ||
that.scheduleFlush_(); | ||
if (--pendingOperations === 0) { | ||
cb(); | ||
} | ||
} | ||
}); | ||
@@ -106,9 +147,11 @@ | ||
that.defaultLabels_ = labels; | ||
if (--pendingOperations === 0) { | ||
cb(); | ||
} | ||
}); | ||
}); | ||
} | ||
util.inherits(TraceWriter, common.Service); | ||
}; | ||
TraceWriter.prototype.stop = function() { | ||
this.isActive = false; | ||
TraceWriter.prototype.config = function() { | ||
return this.config_; | ||
}; | ||
@@ -161,2 +204,12 @@ | ||
}, function(err, response, projectId) { | ||
if (response && response.statusCode !== 200) { | ||
if (response.statusCode === 503) { | ||
err = new Error('Metadata service responded with a 503 status ' + | ||
'code. This may be due to a temporary server error; please try ' + | ||
'again later.'); | ||
} else { | ||
err = new Error('Metadata service responded with the following ' + | ||
'status code: ' + response.statusCode); | ||
} | ||
} | ||
if (err) { | ||
@@ -206,3 +259,3 @@ callback(err); | ||
that.logger_.info('No project number, dropping trace.'); | ||
return; // ignore as index.js takes care of this. | ||
return; // if we even reach this point, disabling traces is already imminent. | ||
} | ||
@@ -217,3 +270,3 @@ | ||
that.logger_.info('Flushing: trace buffer full'); | ||
setImmediate(function() { that.flushBuffer_(project); }); | ||
setImmediate(function() { that.flushBuffer_(); }); | ||
} | ||
@@ -228,9 +281,9 @@ }); | ||
*/ | ||
TraceWriter.prototype.scheduleFlush_ = function(project) { | ||
TraceWriter.prototype.scheduleFlush_ = function() { | ||
this.logger_.info('Flushing: performing periodic flush'); | ||
this.flushBuffer_(project); | ||
this.flushBuffer_(); | ||
// Do it again after delay | ||
if (this.isActive) { | ||
setTimeout(this.scheduleFlush_.bind(this, project), | ||
setTimeout(this.scheduleFlush_.bind(this), | ||
this.config_.flushDelaySeconds * 1000).unref(); | ||
@@ -245,3 +298,3 @@ } | ||
*/ | ||
TraceWriter.prototype.flushBuffer_ = function(projectId) { | ||
TraceWriter.prototype.flushBuffer_ = function() { | ||
if (this.buffer_.length === 0) { | ||
@@ -255,3 +308,3 @@ return; | ||
this.logger_.debug('Flushing traces', buffer); | ||
this.publish_(projectId, '{"traces":[' + buffer.join() + ']}'); | ||
this.publish_('{"traces":[' + buffer.join() + ']}'); | ||
}; | ||
@@ -265,8 +318,6 @@ | ||
*/ | ||
TraceWriter.prototype.publish_ = function(projectId, json) { | ||
// TODO(ofrobots): assert.ok(this.config_.project), and stop accepting | ||
// projectId as an argument. | ||
TraceWriter.prototype.publish_ = function(json) { | ||
var that = this; | ||
var uri = 'https://cloudtrace.googleapis.com/v1/projects/' + | ||
projectId + '/traces'; | ||
this.config_.projectId + '/traces'; | ||
@@ -279,2 +330,3 @@ var options = { | ||
}; | ||
that.logger_.debug('TraceWriter: publishing to ' + uri); | ||
that.request(options, function(err, body, response) { | ||
@@ -290,7 +342,22 @@ if (err) { | ||
/** | ||
* Export TraceWriter. | ||
* FIXME(ofrobots): TraceWriter should be a singleton. We should export | ||
* a get function that returns the instance instead. | ||
*/ | ||
module.exports = TraceWriter; | ||
// Singleton | ||
var traceWriter; | ||
module.exports = { | ||
create: function(logger, config, cb) { | ||
if (!cb) { | ||
cb = function() {}; | ||
} | ||
if (!traceWriter || config.forceNewAgent_) { | ||
traceWriter = new TraceWriter(logger, config); | ||
traceWriter.initialize(cb); | ||
} | ||
return traceWriter; | ||
}, | ||
get: function() { | ||
if (!traceWriter) { | ||
throw new Error('TraceWriter singleton was not initialized.'); | ||
} | ||
return traceWriter; | ||
} | ||
}; |
@@ -81,2 +81,21 @@ /** | ||
/** | ||
* Generates a trace context header value that can be used | ||
* to follow the associated request through other Google services. | ||
* | ||
* @param {?{traceId: string, spanId: string, options: number}} traceContext | ||
* An object with information sufficient for creating a serialized trace | ||
* context. | ||
*/ | ||
function generateTraceContext(traceContext) { | ||
if (!traceContext) { | ||
return ''; | ||
} | ||
var header = traceContext.traceId + '/' + traceContext.spanId; | ||
if (typeof traceContext.options !== 'undefined') { | ||
header += (';o=' + traceContext.options); | ||
} | ||
return header; | ||
} | ||
/** | ||
* Retrieves a package name from the full import path. | ||
@@ -132,2 +151,3 @@ * For example: | ||
parseContextFromHeader: parseContextFromHeader, | ||
generateTraceContext: generateTraceContext, | ||
packageNameFromPath: packageNameFromPath, | ||
@@ -134,0 +154,0 @@ findModulePath: findModulePath, |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
286002
3364
1