/** * Copyright 2015 Google Inc. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ /** * This file has been modified by Keymetrics */ 'use strict'; var cls = require('./cls'); var Trace = require('./trace.js'); var SpanData = require('./span-data.js'); var TraceWriter = require('./trace-writer.js'); var uuid = require('uuid'); var constants = require('./constants.js'); var tracingPolicy = require('./tracing-policy.js'); var isEqual = require('lodash.isequal'); var util = require('./util.js'); /** @type {TraceAgent} */ var traceAgent; /** * @constructor */ function TraceAgent(config, logger) { this.config_ = config; this.logger = logger; this.namespace = cls.createNamespace(); this.traceWriter = new TraceWriter(logger, config); this.policy = tracingPolicy.createTracePolicy(config); this.getCls = function() { return cls; }; if (config.onUncaughtException !== 'ignore') { this.unhandledException = function() { traceAgent.traceWriter.flushBuffer_(traceAgent.config_.projectId); if (config.onUncaughtException === 'flushAndExit') { setTimeout(function() { process.exit(1); }, 2000); } }; process.on('uncaughtException', this.unhandledException); } logger.info('trace agent activated'); } /** * Halts this agent and unpatches any patched modules. */ TraceAgent.prototype.stop = 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(); // Stop the trace writer from publishing any new traces. this.traceWriter.stop(); cls.destroyNamespace(); this.namespace = null; traceAgent = null; if (this.config_.onUncaughtException !== 'ignore') { process.removeListener('uncaughtException', this.unhandledException); } this.logger.info('trace agent deactivated'); }; /** * Returns the agent configuration * @return {object} configuration */ TraceAgent.prototype.config = function() { return this.config_; }; /** * Begin a new custom span. * @param {string} name The name of the span. * @param {Object=} labels Labels to be attached * to the newly created span. Non-object data types are silently ignored. * @param {number=} skipFrames The number of caller frames to eliminate from * stack traces. * @return {SpanData} The newly created span. */ TraceAgent.prototype.startSpan = function(name, labels, skipFrames) { var rootSpan = cls.getRootContext(); skipFrames = skipFrames || 0; if (rootSpan) { var newSpan = rootSpan.createChildSpanData(name, skipFrames + 1); newSpan.addLabels(labels); return newSpan; } else { this.logger.error ('Spans can only be created inside a supported web framework'); return SpanData.nullSpan; } }; /** * Close the provided span. * @param {SpanData} spanData The span to be ended. * @param {Object=} labels Labels to be attached * to the terminated span. Non-object data types are silently ignored. */ TraceAgent.prototype.endSpan = function(spanData, labels) { spanData.addLabels(labels); spanData.close(); }; /** * Determines whether a trace of the given name should be recorded based * on the current tracing policy. * * @param {string} options the req metadata * @param {!number} headerOptions the trace header options */ TraceAgent.prototype.shouldTrace = function(options, headerOptions) { var locallyAllowed = this.policy.shouldTrace(Date.now(), options); // Note: remotelyDisallowed is false if no trace options are present. var remotelyDisallowed = !(isNaN(headerOptions) || (headerOptions & constants.TRACE_OPTIONS_TRACE_ENABLED)); return locallyAllowed && !remotelyDisallowed; }; /** * Call this from inside a namespace.run(). * @param {string} name The name of the root span. * @param {string} traceId The id of the trace owning this span. * @param {string} parentId The id of the parent span. * @param {number=} skipFrames The number of caller frames to eliminate from * stack traces. * @param {string} spanKind The kind of root span; one of 'RPC_SERVER', * 'RPC_CLIENT', or 'SPAN_KIND_UNSPECIFIED'. */ TraceAgent.prototype.createRootSpanData = function(name, traceId, parentId, skipFrames, spanKind) { traceId = traceId || (uuid.v4().split('-').join('')); parentId = parentId || '0'; skipFrames = skipFrames || 0; spanKind = spanKind || 'RPC_SERVER'; var trace = new Trace(0, traceId); // project number added later var spanData = new SpanData(this, trace, name, parentId, true, skipFrames + 1); spanData.span.kind = spanKind; cls.setRootContext(spanData); return spanData; }; /** * Checks if a given request if one being made by ourselves */ TraceAgent.prototype.isTraceAgentRequest = function(options) { return options && options.headers && !!options.headers[constants.TRACE_AGENT_REQUEST_HEADER]; }; /** * Parse a cookie-style header string to extract traceId, spandId and options, * or returns null if the agent has been configured to ignore it. * @see util.parseContextFromHeader * * @param {string} str string representation of the trace headers * @return {?{traceId: string, spanId: string, options: number}} * object with keys. null if there is a problem. */ TraceAgent.prototype.parseContextFromHeader = function(str) { if (this.config_.ignoreContextHeader) { return null; } return util.parseContextFromHeader(str); }; /** * Generates a trace context header value that can be used * to follow the associated request through other Google services. * * @param {SpanData} spanData The span to be added to headers. Must not * be the nullSpan. * @param {boolean} traced Whether this request was traced by the agent. */ TraceAgent.prototype.generateTraceContext = function(spanData, traced) { if (spanData === SpanData.nullSpan) { return ''; } var header = spanData.trace.traceId + '/' + spanData.span.spanId; var options = traced ? spanData.options | constants.TRACE_OPTIONS_TRACE_ENABLED : spanData.options; header += (';o=' + options); return header; }; module.exports = { get: function(config, logger) { if (traceAgent && !config.forceNewAgent_) { if (!isEqual(config, traceAgent.config_)) { traceAgent.logger.warn('New configuration does not match configuration' + 'of existing agent. The old configuration will be used.\nNew: ' + JSON.stringify(config) + '\nExisting: ' + JSON.stringify(traceAgent.config_)); } return traceAgent; } traceAgent = new TraceAgent(config, logger); return traceAgent; } };