traceTreeBuilder.js 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. exports.dateToProtoTimestamp = exports.TraceTreeBuilder = void 0;
  4. const graphql_1 = require("graphql");
  5. const apollo_reporting_protobuf_1 = require("apollo-reporting-protobuf");
  6. function internalError(message) {
  7. return new Error(`[internal apollo-server error] ${message}`);
  8. }
  9. class TraceTreeBuilder {
  10. constructor(options) {
  11. this.rootNode = new apollo_reporting_protobuf_1.Trace.Node();
  12. this.logger = console;
  13. this.trace = new apollo_reporting_protobuf_1.Trace({ root: this.rootNode });
  14. this.stopped = false;
  15. this.nodes = new Map([
  16. [responsePathAsString(), this.rootNode],
  17. ]);
  18. this.rewriteError = options.rewriteError;
  19. if (options.logger)
  20. this.logger = options.logger;
  21. }
  22. startTiming() {
  23. if (this.startHrTime) {
  24. throw internalError('startTiming called twice!');
  25. }
  26. if (this.stopped) {
  27. throw internalError('startTiming called after stopTiming!');
  28. }
  29. this.trace.startTime = dateToProtoTimestamp(new Date());
  30. this.startHrTime = process.hrtime();
  31. }
  32. stopTiming() {
  33. if (!this.startHrTime) {
  34. throw internalError('stopTiming called before startTiming!');
  35. }
  36. if (this.stopped) {
  37. throw internalError('stopTiming called twice!');
  38. }
  39. this.trace.durationNs = durationHrTimeToNanos(process.hrtime(this.startHrTime));
  40. this.trace.endTime = dateToProtoTimestamp(new Date());
  41. this.stopped = true;
  42. }
  43. willResolveField(info) {
  44. if (!this.startHrTime) {
  45. throw internalError('willResolveField called before startTiming!');
  46. }
  47. if (this.stopped) {
  48. throw internalError('willResolveField called after stopTiming!');
  49. }
  50. const path = info.path;
  51. const node = this.newNode(path);
  52. node.type = info.returnType.toString();
  53. node.parentType = info.parentType.toString();
  54. node.startTime = durationHrTimeToNanos(process.hrtime(this.startHrTime));
  55. if (typeof path.key === 'string' && path.key !== info.fieldName) {
  56. node.originalFieldName = info.fieldName;
  57. }
  58. return () => {
  59. node.endTime = durationHrTimeToNanos(process.hrtime(this.startHrTime));
  60. };
  61. }
  62. didEncounterErrors(errors) {
  63. errors.forEach((err) => {
  64. if (err.extensions && err.extensions.serviceName) {
  65. return;
  66. }
  67. const errorForReporting = this.rewriteAndNormalizeError(err);
  68. if (errorForReporting === null) {
  69. return;
  70. }
  71. this.addProtobufError(errorForReporting.path, errorToProtobufError(errorForReporting));
  72. });
  73. }
  74. addProtobufError(path, error) {
  75. if (!this.startHrTime) {
  76. throw internalError('addProtobufError called before startTiming!');
  77. }
  78. if (this.stopped) {
  79. throw internalError('addProtobufError called after stopTiming!');
  80. }
  81. let node = this.rootNode;
  82. if (Array.isArray(path)) {
  83. const specificNode = this.nodes.get(path.join('.'));
  84. if (specificNode) {
  85. node = specificNode;
  86. }
  87. else {
  88. this.logger.warn(`Could not find node with path ${path.join('.')}; defaulting to put errors on root node.`);
  89. }
  90. }
  91. node.error.push(error);
  92. }
  93. newNode(path) {
  94. const node = new apollo_reporting_protobuf_1.Trace.Node();
  95. const id = path.key;
  96. if (typeof id === 'number') {
  97. node.index = id;
  98. }
  99. else {
  100. node.responseName = id;
  101. }
  102. this.nodes.set(responsePathAsString(path), node);
  103. const parentNode = this.ensureParentNode(path);
  104. parentNode.child.push(node);
  105. return node;
  106. }
  107. ensureParentNode(path) {
  108. const parentPath = responsePathAsString(path.prev);
  109. const parentNode = this.nodes.get(parentPath);
  110. if (parentNode) {
  111. return parentNode;
  112. }
  113. return this.newNode(path.prev);
  114. }
  115. rewriteAndNormalizeError(err) {
  116. if (this.rewriteError) {
  117. const clonedError = Object.assign(Object.create(Object.getPrototypeOf(err)), err);
  118. const rewrittenError = this.rewriteError(clonedError);
  119. if (rewrittenError === null) {
  120. return null;
  121. }
  122. if (!(rewrittenError instanceof graphql_1.GraphQLError)) {
  123. return err;
  124. }
  125. return new graphql_1.GraphQLError(rewrittenError.message, err.nodes, err.source, err.positions, err.path, err.originalError, rewrittenError.extensions || err.extensions);
  126. }
  127. return err;
  128. }
  129. }
  130. exports.TraceTreeBuilder = TraceTreeBuilder;
  131. function durationHrTimeToNanos(hrtime) {
  132. return hrtime[0] * 1e9 + hrtime[1];
  133. }
  134. function responsePathAsString(p) {
  135. if (p === undefined) {
  136. return '';
  137. }
  138. let res = String(p.key);
  139. while ((p = p.prev) !== undefined) {
  140. res = `${p.key}.${res}`;
  141. }
  142. return res;
  143. }
  144. function errorToProtobufError(error) {
  145. return new apollo_reporting_protobuf_1.Trace.Error({
  146. message: error.message,
  147. location: (error.locations || []).map(({ line, column }) => new apollo_reporting_protobuf_1.Trace.Location({ line, column })),
  148. json: JSON.stringify(error),
  149. });
  150. }
  151. function dateToProtoTimestamp(date) {
  152. const totalMillis = +date;
  153. const millis = totalMillis % 1000;
  154. return new apollo_reporting_protobuf_1.google.protobuf.Timestamp({
  155. seconds: (totalMillis - millis) / 1000,
  156. nanos: millis * 1e6,
  157. });
  158. }
  159. exports.dateToProtoTimestamp = dateToProtoTimestamp;
  160. //# sourceMappingURL=traceTreeBuilder.js.map