UNPKG

apollo-tracer

Version:

Trace collection library for Node.js GraphQL servers

235 lines (203 loc) 8.15 kB
'use strict'; Object.defineProperty(exports, "__esModule", { value: true }); exports.addTracingToResolvers = exports.decorateWithTracer = exports.Tracer = undefined; var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; }; var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); var _performanceNow = require('performance-now'); var _performanceNow2 = _interopRequireDefault(_performanceNow); var _nodeUuid = require('node-uuid'); var _nodeUuid2 = _interopRequireDefault(_nodeUuid); var _request = require('request'); var _request2 = _interopRequireDefault(_request); var _graphqlTools = require('graphql-tools'); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } var TRACER_INGRESS_URL = 'https://nim-test-ingress.appspot.com'; var Tracer = function () { // TODO make sure Tracer can NEVER crash the server. // maybe wrap everything in try/catch, but need to test that. function Tracer(_ref) { var TRACER_APP_KEY = _ref.TRACER_APP_KEY; var _ref$sendReports = _ref.sendReports; var sendReports = _ref$sendReports === undefined ? true : _ref$sendReports; var reportFilterFn = _ref.reportFilterFn; var proxy = _ref.proxy; _classCallCheck(this, Tracer); if (!TRACER_APP_KEY || TRACER_APP_KEY.length < 36) { throw new Error('Tracer requires a well-formatted TRACER_APP_KEY'); } // TODO check that sendReports is a boolean // TODO check that report filter fn is a function (if defined) this.TRACER_APP_KEY = TRACER_APP_KEY; this.startTime = new Date().getTime(); this.startHrTime = (0, _performanceNow2.default)(); this.sendReports = sendReports; this.reportFilterFn = reportFilterFn; this.proxy = proxy; } _createClass(Tracer, [{ key: 'sendReport', value: function sendReport(report) { var filteredEvents = report.events; if (this.reportFilterFn) { filteredEvents = report.events.filter(this.reportFilterFn); } var options = { url: TRACER_INGRESS_URL, proxy: this.proxy, method: 'PUT', headers: { 'user-agent': 'apollo tracer v' + report.tracerApiVersion }, json: _extends({}, report, { events: filteredEvents }) }; (0, _request2.default)(options, function (err) { if (err) { console.error('Error trying to report to tracer backend:', err.message); return; } // console.log('status', response.statusCode); }); } }, { key: 'newLoggerInstance', value: function newLoggerInstance() { var _this = this; var queryId = _nodeUuid2.default.v4(); var events = []; var idCounter = 0; var startTime = new Date().getTime(); var startHrTime = (0, _performanceNow2.default)(); var log = function log(type) { var data = arguments.length <= 1 || arguments[1] === undefined ? null : arguments[1]; var id = idCounter++; var timestamp = (0, _performanceNow2.default)(); // const timestamp = (new Date()).getTime(); // console.log(timestamp, type, id, data); events.push({ id: id, timestamp: timestamp, type: type, data: data }); return id; }; var report = function report() { return { TRACER_APP_KEY: _this.TRACER_APP_KEY, tracerApiVersion: '0.1.0', queryId: queryId, startTime: startTime, startHrTime: startHrTime, events: events }; }; var submit = function submit() { if (_this.sendReports) { _this.sendReport(report()); } }; return { log: log, report: report, submit: submit }; } /* log(type, data = null) { // TODO ensure props is a valid props thingy // TODO ensure info is a valid info thingy // TODO ensure type is a valid type thingy const id = this.idCounter++; const timestamp = now(); // const timestamp = (new Date()).getTime(); console.log(timestamp, type, id, data); this.events.push({ id, timestamp, type, data }); return id; } report() { return { queryId: this.queryId, startTime: this.startTime, startHrTime: this.startHrTime, events: this.events, }; } */ }]); return Tracer; }(); function decorateWithTracer(fn, info) { return function (p, a, ctx, i) { var startEventId = ctx.tracer.log('resolver.start', info); var result = void 0; try { result = fn(p, a, ctx, i); } catch (e) { // console.log('yeah, it errored directly'); ctx.tracer.log('resolver.end', _extends({}, info, { resolverError: { message: e.message, stack: e.stack }, startEventId: startEventId })); throw e; } try { if (result === null) { ctx.tracer.log('resolver.end', _extends({}, info, { returnedNull: true, startEventId: startEventId })); return result; } if (typeof result === 'undefined') { ctx.tracer.log('resolver.end', _extends({}, info, { returnedUndefined: true, startEventId: startEventId })); return result; } if (typeof result.then === 'function') { result.then(function (res) { ctx.tracer.log('resolver.end', _extends({}, info, { startEventId: startEventId })); return res; }).catch(function (err) { // console.log('whoa, it threw an error!'); ctx.tracer.log('resolver.end', _extends({}, info, { startEventId: startEventId })); throw err; }); } else { // console.log('did not return a promise. logging now'); ctx.tracer.log('resolver.end', _extends({}, info, { startEventId: startEventId })); } return result; } catch (e) { // XXX this should basically never happen // if it does happen, we want to be able to collect these events. ctx.tracer.log('tracer.error', _extends({}, info, { result: result, tracerError: { message: e.message, stack: e.stack }, startEventId: startEventId })); ctx.tracer.log('resolver.end', _extends({}, info, { startEventId: startEventId })); return result; } }; } // This function modifies the schema in place to add tracing around all resolve functions function addTracingToResolvers(schema) { // XXX this is a hacky way of making sure that the schema only gets decorated // with tracer once. if (schema._apolloTracerApplied) { // console.log('Tracing already added to resolve functions. Not adding again.'); return; } // eslint-disable-next-line no-param-reassign schema._apolloTracerApplied = true; (0, _graphqlTools.forEachField)(schema, function (field, typeName, fieldName) { var functionName = typeName + '.' + fieldName; if (field.resolve) { // eslint-disable-next-line no-param-reassign field.resolve = decorateWithTracer(field.resolve, { type: 'resolve', functionName: functionName }); } }); } exports.Tracer = Tracer; exports.decorateWithTracer = decorateWithTracer; exports.addTracingToResolvers = addTracingToResolvers;