-
Notifications
You must be signed in to change notification settings - Fork 2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[2.0] Switch apollo-engine-reporting to use fetch instead of the Node request module #1274
Merged
Merged
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
4d5a4da
Remove Node dependencies from package.json
martijnwalraven 15ebaa8
Replace Node request with fetch
martijnwalraven 7a9dbaf
Wrap fetch in @zeit/fetch-retry, convert to async/await, and fix types
martijnwalraven f72e4e8
Use async-retry directly because @zeit/fetch-retry doesn't support No…
martijnwalraven 77b3265
Merge branch 'version-2' into server-2.0/reporting-non-node
evans aa87e97
bump server-env version in graphql-extensions
evans adec27a
explicitly include factor in async-retry
evans b7f576a
change apollo-server-env to rc.5
evans File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,5 @@ | ||
import * as os from 'os'; | ||
import { gzip } from 'zlib'; | ||
import * as request from 'requestretry'; | ||
import { DocumentNode } from 'graphql'; | ||
import { | ||
FullTracesReport, | ||
|
@@ -9,6 +8,9 @@ import { | |
Trace, | ||
} from 'apollo-engine-reporting-protobuf'; | ||
|
||
import { fetch, Response } from 'apollo-server-env'; | ||
import * as retry from 'async-retry'; | ||
|
||
import { EngineReportingExtension } from './extension'; | ||
|
||
// Override the generated protobuf Traces.encode function so that it will look | ||
|
@@ -170,101 +172,94 @@ export class EngineReportingAgent<TContext = any> { | |
} | ||
} | ||
|
||
public sendReport(): Promise<void> { | ||
public async sendReport(): Promise<void> { | ||
const report = this.report; | ||
this.resetReport(); | ||
|
||
if (Object.keys(report.tracesPerQuery).length === 0) { | ||
return Promise.resolve(); | ||
return; | ||
} | ||
|
||
// Send traces asynchronously, so that (eg) addTrace inside a resolver | ||
// doesn't block on it. | ||
return Promise.resolve() | ||
.then(() => { | ||
if (this.options.debugPrintReports) { | ||
// tslint:disable-next-line no-console | ||
console.log( | ||
`Engine sending report: ${JSON.stringify(report.toJSON())}`, | ||
); | ||
} | ||
await Promise.resolve(); | ||
|
||
if (this.options.debugPrintReports) { | ||
// tslint:disable-next-line no-console | ||
console.log(`Engine sending report: ${JSON.stringify(report.toJSON())}`); | ||
} | ||
|
||
const protobufError = FullTracesReport.verify(report); | ||
if (protobufError) { | ||
throw new Error(`Error encoding report: ${protobufError}`); | ||
} | ||
const message = FullTracesReport.encode(report).finish(); | ||
|
||
const protobufError = FullTracesReport.verify(report); | ||
if (protobufError) { | ||
throw new Error(`Error encoding report: ${protobufError}`); | ||
const compressed = await new Promise<Buffer>((resolve, reject) => { | ||
// The protobuf library gives us a Uint8Array. Node 8's zlib lets us | ||
// pass it directly; convert for the sake of Node 6. (No support right | ||
// now for Node 4, which lacks Buffer.from.) | ||
const messageBuffer = Buffer.from( | ||
message.buffer as ArrayBuffer, | ||
message.byteOffset, | ||
message.byteLength, | ||
); | ||
gzip(messageBuffer, (err, compressed) => { | ||
if (err) { | ||
reject(err); | ||
} else { | ||
resolve(compressed); | ||
} | ||
const message = FullTracesReport.encode(report).finish(); | ||
}); | ||
}); | ||
|
||
return new Promise((resolve, reject) => { | ||
// The protobuf library gives us a Uint8Array. Node 8's zlib lets us | ||
// pass it directly; convert for the sake of Node 6. (No support right | ||
// now for Node 4, which lacks Buffer.from.) | ||
const messageBuffer = Buffer.from( | ||
message.buffer as ArrayBuffer, | ||
message.byteOffset, | ||
message.byteLength, | ||
); | ||
gzip(messageBuffer, (err, compressed) => { | ||
if (err) { | ||
reject(err); | ||
} else { | ||
resolve(compressed); | ||
} | ||
}); | ||
}); | ||
}) | ||
.then(compressed => { | ||
// Grab this here because the delayStrategy function has a different 'this'. | ||
const minimumRetryDelayMs = this.options.minimumRetryDelayMs || 100; | ||
const endpointUrl = | ||
(this.options.endpointUrl || 'https://engine-report.apollodata.com') + | ||
'/api/ingress/traces'; | ||
|
||
// note: retryrequest has built-in Promise support, unlike the base 'request'. | ||
return (request({ | ||
url: | ||
(this.options.endpointUrl || | ||
'https://engine-report.apollodata.com') + '/api/ingress/traces', | ||
// Wrap fetch with async-retry for automatic retrying | ||
const response: Response = await retry( | ||
// Retry on network errors and 5xx HTTP | ||
// responses. | ||
async () => { | ||
const response = await fetch(endpointUrl, { | ||
method: 'POST', | ||
headers: { | ||
'user-agent': 'apollo-engine-reporting', | ||
'x-api-key': this.apiKey, | ||
'content-encoding': 'gzip', | ||
}, | ||
body: compressed, | ||
// By default, retryrequest will retry on network errors and 5xx HTTP | ||
// responses. | ||
maxAttempts: this.options.maxAttempts || 5, | ||
// Note: use a non-arrow function as this API gives us useful information | ||
// on 'this', and use an 'as any' because the type definitions don't know | ||
// about the function version of this parameter. | ||
delayStrategy: function() { | ||
return Math.pow(minimumRetryDelayMs * 2, this.attempts); | ||
}, | ||
// XXX Back in Optics, we had an explicit proxyUrl option for corporate | ||
// proxies. I was never clear on why `request`'s handling of the | ||
// standard env vars wasn't good enough (see | ||
// https://github.com/apollographql/optics-agent-js/pull/70#discussion_r89374066). | ||
// We may have to add it here. | ||
|
||
// Include 'as any's because @types/requestretry doesn't understand the | ||
// promise API or delayStrategy. | ||
} as any) as any).catch((err: Error) => { | ||
throw new Error(`Error sending report to Engine servers: ${err}`); | ||
}); | ||
}) | ||
.then(response => { | ||
if (response.statusCode < 200 || response.statusCode >= 300) { | ||
// Note that we don't expect to see a 3xx here because request follows | ||
// redirects. | ||
throw new Error( | ||
`Error sending report to Engine servers (HTTP status ${ | ||
response.statusCode | ||
}): ${response.body}`, | ||
); | ||
} | ||
if (this.options.debugPrintReports) { | ||
// tslint:disable-next-line no-console | ||
console.log(`Engine report: status ${response.statusCode}`); | ||
|
||
if (response.status >= 500 && response.status < 600) { | ||
throw new Error(`${response.status}: ${response.statusText}`); | ||
} else { | ||
return response; | ||
} | ||
}); | ||
}, | ||
{ | ||
retries: this.options.maxAttempts || 5, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I assume this change which passes a "max attempts" (inclusive of first attempt) number to a "retries" (exclusive of first attempt) parameter was unintentional. See #3218. |
||
minTimeout: this.options.minimumRetryDelayMs || 100, | ||
factor: 2, | ||
}, | ||
).catch((err: Error) => { | ||
throw new Error(`Error sending report to Engine servers: ${err}`); | ||
}); | ||
|
||
if (response.status < 200 || response.status >= 300) { | ||
// Note that we don't expect to see a 3xx here because request follows | ||
// redirects. | ||
throw new Error( | ||
`Error sending report to Engine servers (HTTP status ${ | ||
response.status | ||
}): ${await response.text()}`, | ||
); | ||
} | ||
if (this.options.debugPrintReports) { | ||
// tslint:disable-next-line no-console | ||
console.log(`Engine report: status ${response.status}`); | ||
} | ||
} | ||
|
||
// Stop prevents reports from being sent automatically due to time or buffer | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I assume this change from body to statusText (which leaves out the response body which we do craft in the server to be read by humans in logs) was unintentional. See #3218.