traceTreeBuilder.js 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  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 usage_reporting_protobuf_1 = require("@apollo/usage-reporting-protobuf");
  6. const UnreachableCaseError_js_1 = require("../utils/UnreachableCaseError.js");
  7. function internalError(message) {
  8. return new Error(`[internal apollo-server error] ${message}`);
  9. }
  10. class TraceTreeBuilder {
  11. constructor(options) {
  12. this.rootNode = new usage_reporting_protobuf_1.Trace.Node();
  13. this.trace = new usage_reporting_protobuf_1.Trace({
  14. root: this.rootNode,
  15. fieldExecutionWeight: 1,
  16. });
  17. this.stopped = false;
  18. this.nodes = new Map([
  19. [responsePathAsString(), this.rootNode],
  20. ]);
  21. const { sendErrors, maskedBy } = options;
  22. if (!sendErrors || 'masked' in sendErrors) {
  23. this.transformError = () => new graphql_1.GraphQLError('<masked>', {
  24. extensions: { maskedBy },
  25. });
  26. }
  27. else if ('transform' in sendErrors) {
  28. this.transformError = sendErrors.transform;
  29. }
  30. else if ('unmodified' in sendErrors) {
  31. this.transformError = null;
  32. }
  33. else {
  34. throw new UnreachableCaseError_js_1.UnreachableCaseError(sendErrors);
  35. }
  36. }
  37. startTiming() {
  38. if (this.startHrTime) {
  39. throw internalError('startTiming called twice!');
  40. }
  41. if (this.stopped) {
  42. throw internalError('startTiming called after stopTiming!');
  43. }
  44. this.trace.startTime = dateToProtoTimestamp(new Date());
  45. this.startHrTime = process.hrtime();
  46. }
  47. stopTiming() {
  48. if (!this.startHrTime) {
  49. throw internalError('stopTiming called before startTiming!');
  50. }
  51. if (this.stopped) {
  52. throw internalError('stopTiming called twice!');
  53. }
  54. this.trace.durationNs = durationHrTimeToNanos(process.hrtime(this.startHrTime));
  55. this.trace.endTime = dateToProtoTimestamp(new Date());
  56. this.stopped = true;
  57. }
  58. willResolveField(info) {
  59. if (!this.startHrTime) {
  60. throw internalError('willResolveField called before startTiming!');
  61. }
  62. if (this.stopped) {
  63. return () => { };
  64. }
  65. const path = info.path;
  66. const node = this.newNode(path);
  67. node.type = info.returnType.toString();
  68. node.parentType = info.parentType.toString();
  69. node.startTime = durationHrTimeToNanos(process.hrtime(this.startHrTime));
  70. if (typeof path.key === 'string' && path.key !== info.fieldName) {
  71. node.originalFieldName = info.fieldName;
  72. }
  73. return () => {
  74. node.endTime = durationHrTimeToNanos(process.hrtime(this.startHrTime));
  75. };
  76. }
  77. didEncounterErrors(errors) {
  78. errors.forEach((err) => {
  79. if (err.extensions?.serviceName) {
  80. return;
  81. }
  82. const errorForReporting = this.transformAndNormalizeError(err);
  83. if (errorForReporting === null) {
  84. return;
  85. }
  86. this.addProtobufError(errorForReporting.path, errorToProtobufError(errorForReporting));
  87. });
  88. }
  89. addProtobufError(path, error) {
  90. if (!this.startHrTime) {
  91. throw internalError('addProtobufError called before startTiming!');
  92. }
  93. if (this.stopped) {
  94. throw internalError('addProtobufError called after stopTiming!');
  95. }
  96. let node = this.rootNode;
  97. if (Array.isArray(path)) {
  98. const specificNode = this.nodes.get(path.join('.'));
  99. if (specificNode) {
  100. node = specificNode;
  101. }
  102. else {
  103. const responsePath = responsePathFromArray(path, this.rootNode);
  104. if (!responsePath) {
  105. throw internalError('addProtobufError called with invalid path!');
  106. }
  107. node = this.newNode(responsePath);
  108. }
  109. }
  110. node.error.push(error);
  111. }
  112. newNode(path) {
  113. const node = new usage_reporting_protobuf_1.Trace.Node();
  114. const id = path.key;
  115. if (typeof id === 'number') {
  116. node.index = id;
  117. }
  118. else {
  119. node.responseName = id;
  120. }
  121. this.nodes.set(responsePathAsString(path), node);
  122. const parentNode = this.ensureParentNode(path);
  123. parentNode.child.push(node);
  124. return node;
  125. }
  126. ensureParentNode(path) {
  127. const parentPath = responsePathAsString(path.prev);
  128. const parentNode = this.nodes.get(parentPath);
  129. if (parentNode) {
  130. return parentNode;
  131. }
  132. return this.newNode(path.prev);
  133. }
  134. transformAndNormalizeError(err) {
  135. if (this.transformError) {
  136. const clonedError = Object.assign(Object.create(Object.getPrototypeOf(err)), err);
  137. const rewrittenError = this.transformError(clonedError);
  138. if (rewrittenError === null) {
  139. return null;
  140. }
  141. if (!(rewrittenError instanceof graphql_1.GraphQLError)) {
  142. return err;
  143. }
  144. return new graphql_1.GraphQLError(rewrittenError.message, {
  145. nodes: err.nodes,
  146. source: err.source,
  147. positions: err.positions,
  148. path: err.path,
  149. originalError: err.originalError,
  150. extensions: rewrittenError.extensions || err.extensions,
  151. });
  152. }
  153. return err;
  154. }
  155. }
  156. exports.TraceTreeBuilder = TraceTreeBuilder;
  157. function durationHrTimeToNanos(hrtime) {
  158. return hrtime[0] * 1e9 + hrtime[1];
  159. }
  160. function responsePathAsString(p) {
  161. if (p === undefined) {
  162. return '';
  163. }
  164. let res = String(p.key);
  165. while ((p = p.prev) !== undefined) {
  166. res = `${p.key}.${res}`;
  167. }
  168. return res;
  169. }
  170. function responsePathFromArray(path, node) {
  171. let responsePath;
  172. let nodePtr = node;
  173. for (const key of path) {
  174. nodePtr = nodePtr?.child?.find((child) => child.responseName === key);
  175. responsePath = {
  176. key,
  177. prev: responsePath,
  178. typename: nodePtr?.type ?? undefined,
  179. };
  180. }
  181. return responsePath;
  182. }
  183. function errorToProtobufError(error) {
  184. return new usage_reporting_protobuf_1.Trace.Error({
  185. message: error.message,
  186. location: (error.locations || []).map(({ line, column }) => new usage_reporting_protobuf_1.Trace.Location({ line, column })),
  187. json: JSON.stringify(error),
  188. });
  189. }
  190. function dateToProtoTimestamp(date) {
  191. const totalMillis = +date;
  192. const millis = totalMillis % 1000;
  193. return new usage_reporting_protobuf_1.google.protobuf.Timestamp({
  194. seconds: (totalMillis - millis) / 1000,
  195. nanos: millis * 1e6,
  196. });
  197. }
  198. exports.dateToProtoTimestamp = dateToProtoTimestamp;
  199. //# sourceMappingURL=traceTreeBuilder.js.map