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 |
|
\ | No newline at end of file |