newrelic / newrelic-node-apollo-server-plugin Goto Github PK
View Code? Open in Web Editor NEWLicense: Apache License 2.0
License: Apache License 2.0
Near end of project, fill out specific implementation details to contributing guide(s) and readme to make production ready.
The document.name field is actually a construct with a value. Currently, it assigns it directly and ends up with '[object Object]'.
Was mid change and didn't want to munge things so capturing this as an issue but the fix is super quick/simple. Make sure to add a test case.
Currently, external instrumentation relies on API calls that requires defining a module to wait to load before getting an instrumentation API (shim) instance. With a plugin approach, we don't want to have to hook up to something like that. Grabbing API.shim
works in the short term but is not available when the agent is disabled and is not an official "public" API.
It is not clear if this supports Apollo Federation.
If federation is already supported it should be documented. For example do you add the plugin to the gateway, the services or both.
If not federation support would be very helpful and it should be documented that it is not supported.
The only other option is Apollo Studio and it is quite limited.
This is a must have for us as we are moving all of our APIs to GraphQL with Apollo Federation.
Add any additional documentation (docs site, other) external to the repo needed to support users.
Given a Federated Gateway, where a sub-graph service serves up individual libraries and defines a 'Library' type and other sub graph services extend 'Library' and serve up 'booksInStock' and 'magazinesInStock' respectively for each 'Library' ID they receive.
When a query to the federated server queries for 'booksInStock' or 'magazinesInStock' which results in a _entities style query.
// Federated query
query SubGraphs {
libraries {
branch
booksInStock {
isbn,
title,
author
}
magazinesInStock {
issue,
title
}
}
}
// Resulting sub-graph query to books service
// where $representations is an args array of Library ID's {id: <id>, __typeName: 'Library'}
query($representations:[_Any!]!) {
_entities(representations:$representations) {
...on Library {
booksInStock {
isbn
title
author
}
}
}
}
To allow customers to understand which exact item _entities represents, particularly in the future where we may not show deeper paths when multiple items are requested at the top level, we are considering adding the type, which we get in the selection set, to the path designation of a transaction.
Below are some potential options (but options are not necessarily limited to these).
The goal is to provide clarity to differentiate these while being at least relatively intuitive to customers (understanding the Enities are mapping to the type Library via ID).
_entities.Library
|| _entities.Library.booksInStock
_entities<Library>
|| _entities<Library>.booksInStock
[Library]
|| [Library].booksInStock
_entities on Library
|| _entities on Library.booksInStock
There is a resolve entity -> __entities. Also sends extra calls for resolving entities. The transaction naming is bad in this case, so not sure what it is querying.
This may be the ‘/POST//query/GetServiceDefinition/_service.sdl’ case.
Let's look into what other sorts of query the federated server may be sending and see if there's any improvement to be had in transaction naming (there may not be). I've seen the /POST//query/GetServiceDefinition/_service.sdl
case which on the call the customer seemed to think was the same case. Unsure if there are others.
Get the readme production ready near end of project with appropriate details.
This is minor but seems like we may want consistency here. Finalizing naming decision may make this unnecessary but wanted captured.
In the plugin model, batched queries execute through the plugin events separately (but often back to back at various steps depending on what is executed). As such, we currently aggregate the names for the batch transaction (and base segment) name. This aggregation happens at the end (for a consistent location for all names) but the invocation of that ending event depends on which completes first. So the order of the queries in the name can vary.
We may not have a good way to make this consistent, for the various cases, in which case we can discuss further.
For federated servers, the external segments that should be children of the operation are nested at the same level under the middleware. This is because the operation segment is not set to active.
Because we are keeping track of resolvers and manually associating with the operation, we don’t have a problem on normal server instrumentations.
We do not currently set the operation to active because it causes problems with batch queries. The operations for batch queries start back-to-back, then resolvers execute asynchronously and first to end is the first to hit ‘willSendResponse’ if I remember right. As such, there’s some consideration here as not to bleed transaction state.
This may or may not be easy, given the different use cases but the proper nesting is important for context in the federated case.
We need to add some tests to cover the use of aliases in a graphql query.
examples:
query {
Alias: someQuery {
returnedField
}
}
subquery example with alias:
query {
Alias: topLevelQuery {
returnedField
SubqueryAlias: SubQuery {
subqueryField
}
}
}
Tests should cover how we handle aliases for transaction naming, segment naming, metric generation and setting attributes.
We are only guaranteed to work (at least for now) with v2.14 of apollo-server and greater. Needs to be defensive.
How does this play with middleware-packages? for example, the express middleware package?
Splitting query into its own effort.
The query should be captured as an attribute. This is slightly different from datastores but seems like will want it to be tied to many of the same considerations.
Certainly, sanitation is a concern. We don't want to exposing sensitive information, unless customers are explicitly comfortable.
Should we honor the datastore configuration rules, even though this isn't a datastore for raw/obfuscated/none? Or only all/nothing via attribute configuration?
This should be discussed with security and also worth chatting w/ Vince others who have already done some work/thought around GraphQL.
We also need to determine the right destinations... I was originally thinking transaction event and maybe error event. The tricky part there is the batch query scenario where you could have multiple queries.
Perhaps we add to the request/operation segment/span only? Or both places? Others?
The Graphql query may not be available off of the GraphQLRequest object when persisted queries are used.
It appears we may be able to access the query off of the requestContext.source
:
https://github.com/apollographql/apollo-server/blob/2bccec2c5f5adaaf785f13ab98b6e52e22d5b22e/packages/apollo-server-core/src/requestPipeline.ts#L232
It looks like an extension is required to persist queries:
https://github.com/apolloGraphQL/apollo-link-persisted-queries
We may need to update the plugin to detect whether a persisted query is in play and we should populate a query attribute from a consistent location in either case.
oss third-party manifest
oss third-party notices
Don't forget to double-check accuracy of details
While things should be fine with the agent disabled, it may still do unnecessary work. If the agent has been disabled via configuration, we should just try to noop the plugin.
Setup module for use/development
NOTE: need to go through all approvals first
Banner change
Confirm any other steps needed with open source group
Add this to readme in support section:
* [New Relic Technical Support](https://support.newrelic.com/) 24/7/365 ticketed support. Read more about our [Technical Support Offerings](https://docs.newrelic.com/docs/licenses/license-information/general-usage-licenses/support-plan).
We should have a way to see usage of the plugin and keep track of uptake.
Not currently sure the best way here. One option is to add a new (or leverage existing?) support metric and add to angler. Could be specific to plugin or maybe tracked as a part of the API improvement in issue: #14.
It looks like apollo server has started rolling out 3.0.0 and it's breaking our versioned tests. At first glance it's just around how we bootstrap the plugins with apollo-server. You can see a failed run here 3.0.0 Failed Versioned Run
All versioned tests pass on 3.x of all apollo-server plugins
Run versioned tests
npm run versioned
It looks like our helpers to setup a framework with apollo need tweaked. I was able to change hapi and it works in both 2.x and 3.x
--- a/tests/versioned/apollo-server-hapi/apollo-server-hapi-setup.js
+++ b/tests/versioned/apollo-server-hapi/apollo-server-hapi-setup.js
@@ -49,10 +49,9 @@ function setupApolloServerHapiTests({suiteName, createTests, pluginConfig}, conf
port: 5000
})
+ await server.start()
await server.applyMiddleware({ app: hapiServer, path: graphqlPath })
- await server.installSubscriptionHandlers(hapiServer.listener)
-
We should be able to use this library and run server for integration test without requirement to provide NewRelic key and collector connection
https://discuss.newrelic.com/t/node-requiring-key-in-test-env-apollo-server-plugin/137364
Here is the code snippet that I am using with NestJS framework
when using the plugin as an import
,
import { createPlugin } from '@newrelic/apollo-server-plugin'
my IDE complains about;
TS7016: Could not find a declaration file for module '@newrelic/apollo-server-plugin'. '<snip>/node_modules/@newrelic/apollo-server-plugin/index.js' implicitly has an 'any' type.
Try `npm install @types/newrelic__apollo-server-plugin` if it exists or add a new declaration (.d.ts) file containing `declare module '@newrelic/apollo-server-plugin';
When used as require
My IDE complains about the untyped plugin to the point where I have to silence the linter with:
// eslint-disable-next-line @typescript-eslint/no-var-requires
const createPlugin = require('@newrelic/apollo-server-plugin')
adding a type wrapper to: https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/newrelic ?
Or add a d.ts file
https://www.typescriptlang.org/docs/handbook/declaration-files/templates/module-d-ts.html
Please help us better understand this feature request by choosing a priority from the following options:
[Really Want]
NOTE: would wait for community-plus conversion first... unless we delay that for some reason
Part of the expectation of this story is settling on a standard with the team, including understanding trade-offs.
I fired a query to to my GraphQL server that had a schema error, a field included an argument that did not exist.
Got
{
"error": {
"errors": [
{
"message": "Cannot read property 'value' of undefined",
"extensions": {
"code": "INTERNAL_SERVER_ERROR"
}
}
]
}
}
When I remove the plugin I get:
{
"error": {
"errors": [
{
"message": "Unknown argument \"activityType\" on field \"User.commutes\".",
"locations": [
{
"line": 8,
"column": 16
}
],
"extensions": {
"code": "GRAPHQL_VALIDATION_FAILED"
}
}
]
}
}
@newrelic/[email protected] & Node 14.17.0
I found the line that causes the type error, selection.name
comes through as undefined sometimes.
We could potentially verify but seems more likely customers will want these hidden than want them visible.
Given we are an 0.x release, we could potentially just do with a normal release or we can wait to the upcoming major release we know we are going to do to jump to 1.0.
During triage of #75 it turned out there was a bug with Node.js v16. In order to allow the PR to be merged, the tests for apollo-server-hapi were pinned to exclude v16: fix here. When Node.js v16 becomes LTS, the tests on should be revisited to see if the fix has been made.
The issue surfaced in Node.js with this PR nodejs/node#33035. The effect for apollo-server-hapi versioned tests are they just hang: See affected CI run. This is because the readable stream(http request), prematurely is destroyed. hapi has a listener for this, handle close, which basically just stops processing the request. You can reproduce the issue with this apollo hapi app and make this curl request:
curl --location --request POST 'http://localhost:6000/graphql' \
--header 'Content-Type: application/json' \
--data-raw '{"query":"query GetRoids {\n asteroids(date: \"2020-08-12\") {\n id,\n name,\n miss_distance_km,\n close_approach_date,\n relative_velocity_km_per_hour,\n }\n}","variables":{}}'
The code in Node.js has been reverted: revert PR, but was only made to v15. There is still an open issue in Node.js for fixing this nodejs/node#38657, which I assume will happen before v16 becomes LTS.
[Must Have]
When using Batch functionality with Apollo, it becomes difficult to read the Transaction names in New Relic One because of their length.
We'd like to see the ability to customize the Transaction name. We have different ways of determining uniqueness of operations/clients, so {deepest-path}
is unnecessary noise for us.
Ideally we could pass a callback like computeTransactionName
(to the plugin constructor) that is passed all relevant details (operation name, path, HTTP request object) and could return a String. The default callback, if one is not provided, could be the default functionality from today to prevent breaking changes.
Happy to send a PR if y'all are open to this change.
Really Want
Right now, we're only able to ignore based on URL patterns using the rules->ignore, config settings or the NEW_RELIC_IGNORING_RULES
ENV variable. This isn't super helpful when dealing with GraphQL, since the URL is pretty much always the same. We would like to be able to ignore certain requests based on a pattern applied against the transaction.name
(or open it up to more attributes, but personally we would like transaction.name
). The reason for this is that we don't want to see Introspection Requests in our APM.
Another way is that we could just have a config setting ignoreIntrospection
that we can set to true
or false
and pass into the config when creating the plugin:
const createPlugin = require('@newrelic/apollo-server-plugin')
const plugin = createPlugin({
ignoreIntrospection: true
})
// imported from supported module
const server = new ApolloServer({
typeDefs,
resolvers,
plugins: [plugin]
})
Really Want
After performing the NewRelic Apollo integration I've started noticing the following in our logs. This doesn't mean a whole lot but did notice this reported in the koa integration back in June - wondering if maybe it is related here. Apologies for the poor format:
2021-01-29T10:44:44.636Z [WARN] Cannot read property 'nameState' of null Context: {"error":{"message":"Cannot read property 'nameState' of null","extensions":{"code":"INTERNAL_SERVER_ERROR","exception":{"stacktrace":["TypeError: Cannot read property 'nameState' of null"," at setTransactionName (/usr/cocina/node_modules/@newrelic/apollo-server-plugin/lib/create-plugin.js:314:33)"," at Object.willSendResponse (/usr/cocina/node_modules/@newrelic/apollo-server-plugin/lib/create-plugin.js:224:11)"," at /usr/cocina/node_modules/apollo-server-core/dist/utils/dispatcher.js:21:31"," at Array.map (<anonymous>)"," at Dispatcher.callTargets (/usr/cocina/node_modules/apollo-server-core/dist/utils/dispatcher.js:18:24)"," at Dispatcher.<anonymous> (/usr/cocina/node_modules/apollo-server-core/dist/utils/dispatcher.js:27:43)"," at Generator.next (<anonymous>)"," at /usr/cocina/node_modules/apollo-server-core/dist/utils/dispatcher.js:8:71"," at new Promise (<anonymous>)"," at __awaiter (/usr/cocina/node_modules/apollo-server-core/dist/utils/dispatcher.js:4:12)"]}},"name":"TypeError"}}
I'd like to not have this error appear in the logs either by changing my integration somehow or updating the package if necessary.
Not clear what triggers this error.
Node v12.14.1
Apollo Server Express 2.17.0
We're also instrumented with AWS Xray as well.
https://newrelic.slack.com/archives/G24GM9VRR/p1599855383012600
Setups with nested resolvers can result in resolvers being invoked per item returned at the highest level resolver. For larger datasets, that can result in a ton of data.
At a high level, here are some of the issues that may cause:
After doing some initial research, it seems likely the best approach would be to do some sort of merge of the segments/spans while keeping metrics unique. That being said, there's awkwardness there with starting/ending if there's any sort of repeat.
Other options possible to help with this issue or others, depending on what customer needs come up:
Will want to consider impacts to metrics to still allow for best possible tracking down of issues on a per-field resolver basis.
More details to come here with examples, just getting a quick placeholder in. Also, some details may change.
The current naming has a few key issues:
Instead, we should focus naming on the resolvers while avoiding arbitrary decisions.
In the case where two resolvers are at the same level, name after the resolver one step up. This way, if the second resolver is slow at the same level, we aren't hiding it/implying it is not a problem.
Ignore scalars in this decision, as they are unlikely to contribute meaningfully to the performance characteristics and can result in naming too generically.
TBD:
For example: perhaps there is a consideration where it would be better to use a delimeter (such as 'detail1/detail2') and have the UI handle the display formatting.
An uninstrumented async-await function in resolver can result in weird middelware naming: middleware: <anonymous> //graphql
Unsure why, but seems something automatically behind the scenes adds //graphql
to some of the segment/spans when an uninstrumented async function is introduced into a resolver. I'm guessing this is due to transaction state propagated automatically via our promise/async-hooks instrumentation and assumptions made by our web-framework shim.
It doesn't seem to break anything but is inconsistent and I'm guessing not really meant to be added at this point. Or if it is, likely only to one segment and we should fix so it adds normally. This may require additional state management in our plugin and/or preventing certain actions.
NOTE: query has been split out of this already
It is expected some additional research/experimentation may be completed as a part of this story.
May also want to split resolver/field VS high-level attributes into separate stories.
Attribute thoughts, thus far, after experimenting. Haven’t focused on this yet.
What do we want on error events? Same as transaction events?
Transaction Event / Other? Note: with batch queries there will actually be two or more so maybe these can't go on transaction events
Resolver/field Span
args
Variable definitions? Where?
Is source name useful / ever something different?
source: Source {
body: 'mutation AddThing {\n addThing(name: "added thing!")\n }',
name: 'GraphQL request',
locationOffset: [Object]
}
We currently name based on resolvers hit and then fall back to the document on certain validation errors. For the federated case, no resolvers are hit (no willResolveField hook invoked) so we don’t have this info but we also do not fall back to the document.
Seems like we should always be able to leverage the document for naming / grabbing the path part of the transaction name. In this case, should be able to drop capturing deepest from the resolver execution.
Should fix this around the same time: #84
Can't find how to create a custom error
Can we have a feature to create a custom error if not there? Please do add documentation to it if present
Blocker
Add documentation to README around our support For Apollo Federated.
We may need one or more tests before this is actually possible. I believe tap yells at you if you give it a glob that resolves to nothing, unlike mocha.
Where possible, we want ot capture these in context of the correct spans. For example, an error in a resolver would be attached to a resolver span. This may influence where we wnt to grab from and if we want to add additional instrumentation capture (say parsing/validation).
Places errors exist to potentially grab from
If there are any specific scenarios/additional example documentation needed to help users, please add near end of project.
NOTE: this requires proper module setup completion first
Setup snyk integration. Ping team if have permissions issues.
Ensure templates, etc. match the open-by-default agent standards/template and Node agent specific tweaks we've settled on thus far.
This repo was generated off of a template that will likely vary in some ways.
There will be a separate story to fill out in-depth readme/contrib details specific to this implementation/repo. Feel free to pre-populate generic info.
Some (but not necessarily all) things to check/modify/create are:
I'm getting this error. It doesn't output in the server logs, but does show up on the client. The error completely prevents the request from processing.
newrelic.js
exports.config = {
agent_enabled: true,
ssl: false,
app_name: ['FNB Server'],
license_key: 'XYZ',
distributed_tracing: {
enabled: false,
},
logging: {
level: 'info'
},
allow_all_headers: true,
attributes: {
exclude: [
'request.headers.cookie',
'request.headers.authorization',
'request.headers.proxyAuthorization',
'request.headers.setCookie*',
'request.headers.x*',
'response.headers.cookie',
'response.headers.authorization',
'response.headers.proxyAuthorization',
'response.headers.setCookie*',
'response.headers.x*',
],
}
}
Yes, the plugin is not compatible with apollo-server-azure-functions
Would be fantastic if apollo-server-azure-functions was supported
I tested this plugin with apollo-server-azure-functions and I didn't get any errors on initialisation, but I could not see any data being sent to newrelic. Just wondering if there is any roadmap for supporting it
Really Want
It is not clear if this supports Apollo Federation.
If federation is already supported it should be documented. For example do you add the plugin to the gateway, the services or both.
If not federation support would be very helpful and it should be documented that it is not supported.
The only other option is Apollo Studio and it is quite limited.
This is a must have for us as we are moving all of our APIs to GraphQL with Apollo Federation.
I have a feeling the disconnect between the query being on the operation and the params being on the field resolve where they are passed in may end up being confusing for end users. The paradigm that will likely be in folks minds will be similar to URL/request params which end up on the same constructs.
I'm thinking maybe we just capture in both places. So each time we add to the resolve span we add to the operation.
Unsure on 'naming'. We may want to simplify attributes for consistency... but we can start out with something intuitive.
Add versioned test for apollo-hapi-server
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.