diff --git a/CHANGELOG.md b/CHANGELOG.md
index b3c52302aa6..53f8dea105e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,6 +5,10 @@ The version headers in this history reflect the versions of Apollo Server itself
- [__CHANGELOG for `@apollo/gateway`__](https://github.com/apollographql/apollo-server/blob/master/packages/apollo-gateway/CHANGELOG.md)
- [__CHANGELOG for `@apollo/federation`__](https://github.com/apollographql/apollo-server/blob/master/packages/apollo-federation/CHANGELOG.md)
+### v2.13.0
+
+- `apollo-server-core`: Support providing a custom logger implementation (e.g. [`winston`](https://npm.im/winston), [`bunyan`](https://npm.im/bunyan), etc.) to capture server console messages. Though there has historically been limited output from Apollo Server, some messages are important to capture in the larger context of production logging facilities or can benefit from using more advanced structure, like JSON-based logging. This also introduces a `logger` property to the `GraphQLRequestContext` that is exposed to plugins, making it possible for plugins to leverage the same server-level logger, and allowing implementors to create request-specific log contexts, if desired. When not provided, these will still output to `console`. [PR #3894](https://github.com/apollographql/apollo-server/pull/3894)
+
### v2.12.0
- `apollo-server-core`: When operating in gateway mode using the `gateway` property of the Apollo Server constructor options, the failure to initialize a schema during initial start-up, e.g. connectivity problems, will no longer result in the federated executor from being assigned when the schema eventually becomes available. This precludes a state where the gateway may never become available to serve federated requests, even when failure conditions are no longer present. [PR #3811](https://github.com/apollographql/apollo-server/pull/3811)
diff --git a/docs/source/api/apollo-gateway.mdx b/docs/source/api/apollo-gateway.mdx
index 4bc25991967..1cf5ee6e4d2 100644
--- a/docs/source/api/apollo-gateway.mdx
+++ b/docs/source/api/apollo-gateway.mdx
@@ -99,13 +99,20 @@ example of using `ApolloGateway`, see [The gateway](/federation/gateway/).
});
```
+ * `logger`: `Logger`
+
+ A logging implementation to be used in place of `console`. The implementation must provide the methods which satisfy the requirements of [the `Logger` interface](https://github.com/apollographql/apollo-server/blob/80a12d89ea1ae9a0892f4a81d9213eddf95ca965/packages/apollo-server-types/src/index.ts#L114-L121) (i.e. it must provide `debug`, `info`, `warn` and `error` methods). When a custom logger is provided, it will receive all levels of logging and it is up to the logger itself to determine how it wishes to handle each level. When a custom logger is _not_ provided, Gateway will default to outputting `warn` and `error` levels unless `debug: true` is specified, in which case it will output all log levels (i.e. `debug` through `error`).
+
+ Additionally, this `logger` will be made available on the `GraphQLRequestContext` and available to plugins. This allows a plugin to, e.g., augment the logger on a per-request basis within the `requestDidStart` life-cycle hook.
+
+
* `debug`: `Boolean`
If `true`, the gateway logs startup messages, along with the query plan for
each incoming request. The default value is `false`.
-
+
* `fetcher`: `typeof fetch`
-
+
When specified, overrides the default
[Fetch API](https://fetch.spec.whatwg.org/#fetch-api) implementation
which is used when communicating with downstream services. By default,
diff --git a/docs/source/api/apollo-server.md b/docs/source/api/apollo-server.md
index 4dac689d772..c5ba9fc92a6 100644
--- a/docs/source/api/apollo-server.md
+++ b/docs/source/api/apollo-server.md
@@ -68,6 +68,12 @@ new ApolloServer({
| AWS Lambda | {
event: [`APIGatewayProxyEvent`](https://github.com/DefinitelyTyped/DefinitelyTyped/blob/50adc95acf873e714256074311353232fcc1b5ed/types/aws-lambda/index.d.ts#L78-L92),
context: [`LambdaContext`](https://github.com/DefinitelyTyped/DefinitelyTyped/blob/50adc95acf873e714256074311353232fcc1b5ed/types/aws-lambda/index.d.ts#L510-L534)
}
|
| Micro | { req: [`MicroRequest`](https://github.com/apollographql/apollo-server/blob/c356bcf3f2864b8d2fcca0add455071e0606ef46/packages/apollo-server-micro/src/types.ts#L3-L5), res: [`ServerResponse`](https://github.com/DefinitelyTyped/DefinitelyTyped/blob/50adc95acf873e714256074311353232fcc1b5ed/types/node/v10/http.d.ts#L145-L158) }
|
+ * `logger`: `Logger`
+
+ A logging implementation to be used in place of `console`. The implementation must provide the methods which satisfy the requirements of [the `Logger` interface](https://github.com/apollographql/apollo-server/blob/80a12d89ea1ae9a0892f4a81d9213eddf95ca965/packages/apollo-server-types/src/index.ts#L114-L121) (i.e. it must provide `debug`, `info`, `warn` and `error` methods). When a custom logger is provided, it will receive all levels of logging and it is up to the logger itself to determine how it wishes to handle each level. When a custom logger is _not_ provided, Apollo Server will default to outputting `warn` and `error` levels unless `debug: true` is specified, in which case it will output all log levels (i.e. `debug` through `error`).
+
+ Additionally, this `logger` will be made available on the `GraphQLRequestContext` and available to plugins. This allows a plugin to, e.g., augment the logger on a per-request basis within the `requestDidStart` life-cycle hook.
+
* `rootValue`: <`Any`> | <`Function`>
A value or function called with the parsed `Document`, creating the root value passed to the graphql executor. The function is useful if you wish to provide a different root value based on the query operation type.
@@ -337,6 +343,10 @@ addMockFunctionsToSchema({
a service. You can also specify an API key with the `ENGINE_API_KEY`
environment variable, although the `apiKey` option takes precedence.
+* `logger`: `Logger`
+
+ By default, this will inherit from the `logger` provided to `ApolloServer` which defaults to `console` when not provided. If specified within the `EngineReportingOptions` it can be used to send engine reporting to a separate logger. If provided, the implementation must provide the methods which satisfy the requirements of [the `Logger` interface](https://github.com/apollographql/apollo-server/blob/80a12d89ea1ae9a0892f4a81d9213eddf95ca965/packages/apollo-server-types/src/index.ts#L114-L121) (i.e. it must provide `debug`, `info`, `warn` and `error` methods).
+
* `calculateSignature`: (ast: DocumentNode, operationName: string) => string
Specify the function for creating a signature for a query.
diff --git a/docs/source/integrations/plugins.md b/docs/source/integrations/plugins.md
index 0400c487195..2ab21962324 100644
--- a/docs/source/integrations/plugins.md
+++ b/docs/source/integrations/plugins.md
@@ -269,6 +269,15 @@ const server = new ApolloServer({
The `requestDidStart` event fires whenever Apollo Server begins fulfilling a GraphQL request.
+```typescript
+requestDidStart?(
+ requestContext: WithRequired<
+ GraphQLRequestContext,
+ 'request' | 'context' | 'logger'
+ >
+): GraphQLRequestListener | void;
+```
+
This function can optionally return an object that includes functions for responding
to request lifecycle events that might follow `requestDidStart`.
@@ -318,7 +327,7 @@ does not occur.
parsingDidStart?(
requestContext: WithRequired<
GraphQLRequestContext,
- 'metrics' | 'source'
+ 'metrics' | 'source' | 'logger'
>,
): (err?: Error) => void | void;
```
@@ -338,7 +347,7 @@ available at this stage, because parsing must succeed for validation to occur.
validationDidStart?(
requestContext: WithRequired<
GraphQLRequestContext,
- 'metrics' | 'source' | 'document'
+ 'metrics' | 'source' | 'document' | 'logger'
>,
): (err?: ReadonlyArray) => void | void;
```
@@ -355,7 +364,7 @@ both the `operationName` string and `operation` AST are available.
didResolveOperation?(
requestContext: WithRequired<
GraphQLRequestContext,
- 'metrics' | 'source' | 'document' | 'operationName' | 'operation'
+ 'metrics' | 'source' | 'document' | 'operationName' | 'operation' | 'logger'
>,
): ValueOrPromise;
```
@@ -371,7 +380,7 @@ are invoked in series, and the first non-null response is used.
responseForOperation?(
requestContext: WithRequired<
GraphQLRequestContext,
- 'metrics' | 'source' | 'document' | 'operationName' | 'operation'
+ 'metrics' | 'source' | 'document' | 'operationName' | 'operation' | 'logger'
>,
): ValueOrPromise;
```
@@ -385,7 +394,7 @@ GraphQL operation specified by a request's `document` AST.
executionDidStart?(
requestContext: WithRequired<
GraphQLRequestContext,
- 'metrics' | 'source' | 'document' | 'operationName' | 'operation'
+ 'metrics' | 'source' | 'document' | 'operationName' | 'operation' | 'logger'
>,
): (err?: Error) => void | void;
```
@@ -399,7 +408,7 @@ parsing, validating, or executing a GraphQL operation.
didEncounterErrors?(
requestContext: WithRequired<
GraphQLRequestContext,
- 'metrics' | 'source' | 'errors'
+ 'metrics' | 'source' | 'errors' | 'logger'
>,
): ValueOrPromise;
```
@@ -414,7 +423,7 @@ if the GraphQL operation encounters one or more errors.
willSendResponse?(
requestContext: WithRequired<
GraphQLRequestContext,
- 'metrics' | 'response'
+ 'metrics' | 'response' | 'logger'
>,
): ValueOrPromise;
```
diff --git a/package-lock.json b/package-lock.json
index 9da063a5c72..9ca389b2b49 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -3592,6 +3592,15 @@
"integrity": "sha512-PH7bfkt1nu4pnlxz+Ws+wwJJF1HE12W3ia+Iace2JT7q56DLH3hbyjOJyNHJYRxk3PkKaC36fHfHKyeG1rMgCA==",
"dev": true
},
+ "@types/bunyan": {
+ "version": "1.8.6",
+ "resolved": "https://registry.npmjs.org/@types/bunyan/-/bunyan-1.8.6.tgz",
+ "integrity": "sha512-YiozPOOsS6bIuz31ilYqR5SlLif4TBWsousN2aCWLi5233nZSX19tFbcQUPdR7xJ8ypPyxkCGNxg0CIV5n9qxQ==",
+ "dev": true,
+ "requires": {
+ "@types/node": "*"
+ }
+ },
"@types/caseless": {
"version": "0.12.2",
"resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.2.tgz",
@@ -4449,9 +4458,17 @@
"graphql-tag": "^2.9.2",
"graphql-tools": "^4.0.0",
"graphql-upload": "^8.0.2",
+ "loglevel": "^1.6.7",
"sha.js": "^2.4.11",
"subscriptions-transport-ws": "^0.9.11",
"ws": "^6.0.0"
+ },
+ "dependencies": {
+ "loglevel": {
+ "version": "1.6.7",
+ "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.7.tgz",
+ "integrity": "sha512-cY2eLFrQSAfVPhCgH1s7JI73tMbg9YC3v3+ZHVW67sBS7UxWzNEk/ZBbSfLykBWHp33dqqtOv82gjhKEi81T/A=="
+ }
}
},
"apollo-server-env": {
@@ -4798,6 +4815,15 @@
"integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==",
"dev": true
},
+ "async": {
+ "version": "2.6.3",
+ "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz",
+ "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==",
+ "dev": true,
+ "requires": {
+ "lodash": "^4.17.14"
+ }
+ },
"async-limiter": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz",
@@ -5207,6 +5233,18 @@
"integrity": "sha1-y5T662HIaWRR2zZTThQi+U8K7og=",
"dev": true
},
+ "bunyan": {
+ "version": "1.8.12",
+ "resolved": "https://registry.npmjs.org/bunyan/-/bunyan-1.8.12.tgz",
+ "integrity": "sha1-8VDw9nSKvdcq6uhPBEA74u8RN5c=",
+ "dev": true,
+ "requires": {
+ "dtrace-provider": "~0.8",
+ "moment": "^2.10.6",
+ "mv": "~2",
+ "safe-json-stringify": "~1"
+ }
+ },
"busboy": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/busboy/-/busboy-0.3.1.tgz",
@@ -5575,6 +5613,16 @@
"object-visit": "^1.0.0"
}
},
+ "color": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/color/-/color-3.0.0.tgz",
+ "integrity": "sha512-jCpd5+s0s0t7p3pHQKpnJ0TpQKKdleP71LWcA0aqiljpiuAkOSUFN/dyH8ZwF0hRmFlrIuRhufds1QyEP9EB+w==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^1.9.1",
+ "color-string": "^1.5.2"
+ }
+ },
"color-convert": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
@@ -5588,6 +5636,38 @@
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
},
+ "color-string": {
+ "version": "1.5.3",
+ "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.3.tgz",
+ "integrity": "sha512-dC2C5qeWoYkxki5UAXapdjqO672AM4vZuPGRQfO8b5HKuKGBbKWpITyDYN7TOFKvRW7kOgAn3746clDBMDJyQw==",
+ "dev": true,
+ "requires": {
+ "color-name": "^1.0.0",
+ "simple-swizzle": "^0.2.2"
+ }
+ },
+ "colornames": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/colornames/-/colornames-1.1.1.tgz",
+ "integrity": "sha1-+IiQMGhcfE/54qVZ9Qd+t2qBb5Y=",
+ "dev": true
+ },
+ "colors": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz",
+ "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==",
+ "dev": true
+ },
+ "colorspace": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.2.tgz",
+ "integrity": "sha512-vt+OoIP2d76xLhjwbBaucYlNSpPsrJWPlBTtwCpQKIu6/CSMutyzX93O/Do0qzpH3YoHEes8YEFXyZ797rEhzQ==",
+ "dev": true,
+ "requires": {
+ "color": "3.0.x",
+ "text-hex": "1.0.x"
+ }
+ },
"columnify": {
"version": "1.5.4",
"resolved": "https://registry.npmjs.org/columnify/-/columnify-1.5.4.tgz",
@@ -6280,6 +6360,12 @@
"resolved": "https://registry.npmjs.org/dataloader/-/dataloader-1.4.0.tgz",
"integrity": "sha512-68s5jYdlvasItOJnCuI2Q9s4q98g0pCyL3HrcKJu8KNugUl8ahgmZYg38ysLTgQjjXX3H8CJLkAvWrclWfcalw=="
},
+ "date-format": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/date-format/-/date-format-2.1.0.tgz",
+ "integrity": "sha512-bYQuGLeFxhkxNOF3rcMtiZxvCBAquGzZm6oWA1oZ0g2THUzivaRhv8uOhdr19LmoobSOLoIAxeUK2RdbM8IFTA==",
+ "dev": true
+ },
"dateformat": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz",
@@ -6498,6 +6584,17 @@
"wrappy": "1"
}
},
+ "diagnostics": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/diagnostics/-/diagnostics-1.1.1.tgz",
+ "integrity": "sha512-8wn1PmdunLJ9Tqbx+Fx/ZEuHfJf4NKSN2ZBj7SJC/OWRWha843+WsTjqMe1B5E3p28jqBlp+mJ2fPVxPyNgYKQ==",
+ "dev": true,
+ "requires": {
+ "colorspace": "1.1.x",
+ "enabled": "1.0.x",
+ "kuler": "1.0.x"
+ }
+ },
"dicer": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/dicer/-/dicer-0.3.0.tgz",
@@ -6539,6 +6636,25 @@
"is-obj": "^1.0.0"
}
},
+ "dtrace-provider": {
+ "version": "0.8.8",
+ "resolved": "https://registry.npmjs.org/dtrace-provider/-/dtrace-provider-0.8.8.tgz",
+ "integrity": "sha512-b7Z7cNtHPhH9EJhNNbbeqTcXB8LGFFZhq1PGgEvpeHlzd36bhbdTWoE/Ba/YguqpBSlAPKnARWhVlhunCMwfxg==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "nan": "^2.14.0"
+ },
+ "dependencies": {
+ "nan": {
+ "version": "2.14.0",
+ "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz",
+ "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==",
+ "dev": true,
+ "optional": true
+ }
+ }
+ },
"duplexer": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.1.tgz",
@@ -6610,6 +6726,15 @@
"integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==",
"dev": true
},
+ "enabled": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/enabled/-/enabled-1.0.2.tgz",
+ "integrity": "sha1-ll9lE9LC0cX0ZStkouM5ZGf8L5M=",
+ "dev": true,
+ "requires": {
+ "env-variable": "0.0.x"
+ }
+ },
"encodeurl": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
@@ -6638,6 +6763,12 @@
"integrity": "sha512-6u0VYSCo/OW6IoD5WCLLy9JUGARbamfSavcNXry/eu8aHVFei6CD3Sw+VGX5alea1i9pgPHW0mbu6Xj0uBh7gA==",
"dev": true
},
+ "env-variable": {
+ "version": "0.0.6",
+ "resolved": "https://registry.npmjs.org/env-variable/-/env-variable-0.0.6.tgz",
+ "integrity": "sha512-bHz59NlBbtS0NhftmR8+ExBEekE7br0e01jw+kk0NDro7TtZzBYZ5ScGPs3OmwnpyfHTHOtr1Y6uedCdrIldtg==",
+ "dev": true
+ },
"envinfo": {
"version": "7.5.0",
"resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.5.0.tgz",
@@ -7302,6 +7433,12 @@
"bser": "^2.0.0"
}
},
+ "fecha": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fecha/-/fecha-2.3.3.tgz",
+ "integrity": "sha512-lUGBnIamTAwk4znq5BcqsDaxSmZ9nDVJaij6NvRt/Tg4R69gERA+otPKbS86ROw9nxVMw2/mp1fnaiWqbs6Sdg==",
+ "dev": true
+ },
"figgy-pudding": {
"version": "3.5.1",
"resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.1.tgz",
@@ -7386,6 +7523,12 @@
"integrity": "sha512-qFlJnOBWDfIaunF54/lBqNKmXOI0HqNhu+mHkLmbaBXlS71PUd9OjFOdyevHt/aHoHB1+eW7eKHgRKOG5aHSpw==",
"dev": true
},
+ "flatted": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.1.tgz",
+ "integrity": "sha512-a1hQMktqW9Nmqr5aktAux3JMNqaucxGcjtjWnZLHX7yyPCmlSV3M54nGYbqT8K+0GhF3NBgmJCc3ma+WOgX8Jg==",
+ "dev": true
+ },
"flush-write-stream": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz",
@@ -11891,6 +12034,15 @@
}
}
},
+ "kuler": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/kuler/-/kuler-1.0.1.tgz",
+ "integrity": "sha512-J9nVUucG1p/skKul6DU3PUZrhs0LPulNaeUOox0IyXDi8S4CztTHs1gQphhuZmzXG7VOQSf6NJfKuzteQLv9gQ==",
+ "dev": true,
+ "requires": {
+ "colornames": "^1.1.1"
+ }
+ },
"left-pad": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz",
@@ -12058,6 +12210,69 @@
"resolved": "https://registry.npmjs.org/lodash.xorby/-/lodash.xorby-4.7.0.tgz",
"integrity": "sha1-nBmm+fBjputT3QPBtocXmYAUY9c="
},
+ "log4js": {
+ "version": "4.5.1",
+ "resolved": "https://registry.npmjs.org/log4js/-/log4js-4.5.1.tgz",
+ "integrity": "sha512-EEEgFcE9bLgaYUKuozyFfytQM2wDHtXn4tAN41pkaxpNjAykv11GVdeI4tHtmPWW4Xrgh9R/2d7XYghDVjbKKw==",
+ "dev": true,
+ "requires": {
+ "date-format": "^2.0.0",
+ "debug": "^4.1.1",
+ "flatted": "^2.0.0",
+ "rfdc": "^1.1.4",
+ "streamroller": "^1.0.6"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
+ "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
+ "dev": true,
+ "requires": {
+ "ms": "^2.1.1"
+ }
+ },
+ "ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+ "dev": true
+ },
+ "rfdc": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.1.4.tgz",
+ "integrity": "sha512-5C9HXdzK8EAqN7JDif30jqsBzavB7wLpaubisuQIGHWf2gUXSpzy6ArX/+Da8RjFpagWsCn+pIgxTMAmKw9Zug==",
+ "dev": true
+ }
+ }
+ },
+ "logform": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/logform/-/logform-2.1.2.tgz",
+ "integrity": "sha512-+lZh4OpERDBLqjiwDLpAWNQu6KMjnlXH2ByZwCuSqVPJletw0kTWJf5CgSNAUKn1KUkv3m2cUz/LK8zyEy7wzQ==",
+ "dev": true,
+ "requires": {
+ "colors": "^1.2.1",
+ "fast-safe-stringify": "^2.0.4",
+ "fecha": "^2.3.3",
+ "ms": "^2.1.1",
+ "triple-beam": "^1.3.0"
+ },
+ "dependencies": {
+ "fast-safe-stringify": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.0.7.tgz",
+ "integrity": "sha512-Utm6CdzT+6xsDk2m8S6uL8VHxNwI6Jub+e9NYTcAms28T84pTa25GJQV9j0CY0N1rM8hK4x6grpF2BQf+2qwVA==",
+ "dev": true
+ },
+ "ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+ "dev": true
+ }
+ }
+ },
"loglevel": {
"version": "1.6.4",
"resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.4.tgz",
@@ -12745,6 +12960,13 @@
"integrity": "sha512-xV2bxeN6F7oYjZWTe/YPAy6MN2M+sL4u/Rlm2AHCIVGfo2p1yGmBHQ6vHehl4bRTZBdHu3TSkWdYgkwpYzAGSw==",
"dev": true
},
+ "moment": {
+ "version": "2.24.0",
+ "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz",
+ "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==",
+ "dev": true,
+ "optional": true
+ },
"move-concurrently": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz",
@@ -12781,6 +13003,44 @@
"integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=",
"dev": true
},
+ "mv": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/mv/-/mv-2.1.1.tgz",
+ "integrity": "sha1-rmzg1vbV4KT32JN5jQPB6pVZtqI=",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "mkdirp": "~0.5.1",
+ "ncp": "~2.0.0",
+ "rimraf": "~2.4.0"
+ },
+ "dependencies": {
+ "glob": {
+ "version": "6.0.4",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz",
+ "integrity": "sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI=",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "2 || 3",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ }
+ },
+ "rimraf": {
+ "version": "2.4.5",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.4.5.tgz",
+ "integrity": "sha1-7nEM5dk6j9uFb7Xqj/Di11k0sto=",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "glob": "^6.0.1"
+ }
+ }
+ }
+ },
"mz": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
@@ -12824,6 +13084,13 @@
"integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=",
"dev": true
},
+ "ncp": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ncp/-/ncp-2.0.0.tgz",
+ "integrity": "sha1-GVoh1sRuNh0vsSgbo4uR6d9727M=",
+ "dev": true,
+ "optional": true
+ },
"needle": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/needle/-/needle-2.4.0.tgz",
@@ -13274,6 +13541,12 @@
"wrappy": "1"
}
},
+ "one-time": {
+ "version": "0.0.4",
+ "resolved": "https://registry.npmjs.org/one-time/-/one-time-0.0.4.tgz",
+ "integrity": "sha1-+M33eISCb+Tf+T46nMN7HkSAdC4=",
+ "dev": true
+ },
"onetime": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz",
@@ -14308,6 +14581,13 @@
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
},
+ "safe-json-stringify": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/safe-json-stringify/-/safe-json-stringify-1.2.0.tgz",
+ "integrity": "sha512-gH8eh2nZudPQO6TytOvbxnuhYBOvDBBLW52tz5q6X58lJcd/tkmqFR+5Z9adS8aJtURSXWThWy/xJtJwixErvg==",
+ "dev": true,
+ "optional": true
+ },
"safe-regex": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz",
@@ -14490,6 +14770,23 @@
"resolved": "https://registry.npmjs.org/simple-lru-cache/-/simple-lru-cache-0.0.2.tgz",
"integrity": "sha1-1ZzDoZPBpdAyD4Tucy9uRxPlEd0="
},
+ "simple-swizzle": {
+ "version": "0.2.2",
+ "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
+ "integrity": "sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=",
+ "dev": true,
+ "requires": {
+ "is-arrayish": "^0.3.1"
+ },
+ "dependencies": {
+ "is-arrayish": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
+ "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==",
+ "dev": true
+ }
+ }
+ },
"sisteransi": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.3.tgz",
@@ -14779,6 +15076,12 @@
"figgy-pudding": "^3.5.1"
}
},
+ "stack-trace": {
+ "version": "0.0.10",
+ "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz",
+ "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=",
+ "dev": true
+ },
"stack-utils": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.2.tgz",
@@ -14847,6 +15150,47 @@
"integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==",
"dev": true
},
+ "streamroller": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-1.0.6.tgz",
+ "integrity": "sha512-3QC47Mhv3/aZNFpDDVO44qQb9gwB9QggMEE0sQmkTAwBVYdBRWISdsywlkfm5II1Q5y/pmrHflti/IgmIzdDBg==",
+ "dev": true,
+ "requires": {
+ "async": "^2.6.2",
+ "date-format": "^2.0.0",
+ "debug": "^3.2.6",
+ "fs-extra": "^7.0.1",
+ "lodash": "^4.17.14"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "3.2.6",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
+ "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
+ "dev": true,
+ "requires": {
+ "ms": "^2.1.1"
+ }
+ },
+ "fs-extra": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz",
+ "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==",
+ "dev": true,
+ "requires": {
+ "graceful-fs": "^4.1.2",
+ "jsonfile": "^4.0.0",
+ "universalify": "^0.1.0"
+ }
+ },
+ "ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+ "dev": true
+ }
+ }
+ },
"streamsearch": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz",
@@ -15201,6 +15545,12 @@
"integrity": "sha512-wiBrwC1EhBelW12Zy26JeOUkQ5mRu+5o8rpsJk5+2t+Y5vE7e842qtZDQ2g1NpX/29HdyFeJ4nSIhI47ENSxlQ==",
"dev": true
},
+ "text-hex": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz",
+ "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==",
+ "dev": true
+ },
"thenify": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.0.tgz",
@@ -15384,6 +15734,12 @@
"integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=",
"dev": true
},
+ "triple-beam": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.3.0.tgz",
+ "integrity": "sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==",
+ "dev": true
+ },
"ts-invariant": {
"version": "0.4.4",
"resolved": "https://registry.npmjs.org/ts-invariant/-/ts-invariant-0.4.4.tgz",
@@ -15826,6 +16182,65 @@
"execa": "^1.0.0"
}
},
+ "winston": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/winston/-/winston-3.2.1.tgz",
+ "integrity": "sha512-zU6vgnS9dAWCEKg/QYigd6cgMVVNwyTzKs81XZtTFuRwJOcDdBg7AU0mXVyNbs7O5RH2zdv+BdNZUlx7mXPuOw==",
+ "dev": true,
+ "requires": {
+ "async": "^2.6.1",
+ "diagnostics": "^1.1.1",
+ "is-stream": "^1.1.0",
+ "logform": "^2.1.1",
+ "one-time": "0.0.4",
+ "readable-stream": "^3.1.1",
+ "stack-trace": "0.0.x",
+ "triple-beam": "^1.3.0",
+ "winston-transport": "^4.3.0"
+ }
+ },
+ "winston-transport": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.3.0.tgz",
+ "integrity": "sha512-B2wPuwUi3vhzn/51Uukcao4dIduEiPOcOt9HJ3QeaXgkJ5Z7UwpBzxS4ZGNHtrxrUvTwemsQiSys0ihOf8Mp1A==",
+ "dev": true,
+ "requires": {
+ "readable-stream": "^2.3.6",
+ "triple-beam": "^1.2.0"
+ },
+ "dependencies": {
+ "isarray": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+ "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=",
+ "dev": true
+ },
+ "readable-stream": {
+ "version": "2.3.7",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz",
+ "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==",
+ "dev": true,
+ "requires": {
+ "core-util-is": "~1.0.0",
+ "inherits": "~2.0.3",
+ "isarray": "~1.0.0",
+ "process-nextick-args": "~2.0.0",
+ "safe-buffer": "~5.1.1",
+ "string_decoder": "~1.1.1",
+ "util-deprecate": "~1.0.1"
+ }
+ },
+ "string_decoder": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+ "dev": true,
+ "requires": {
+ "safe-buffer": "~5.1.0"
+ }
+ }
+ }
+ },
"wordwrap": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz",
diff --git a/package.json b/package.json
index 7faae6f3d21..249c5bcb45a 100644
--- a/package.json
+++ b/package.json
@@ -61,6 +61,7 @@
"@types/async-retry": "1.4.1",
"@types/aws-lambda": "8.10.46",
"@types/body-parser": "1.19.0",
+ "@types/bunyan": "1.8.6",
"@types/connect": "3.4.33",
"@types/fast-json-stable-stringify": "2.0.0",
"@types/hapi": "17.8.7",
@@ -89,6 +90,7 @@
"apollo-link-persisted-queries": "0.2.2",
"azure-functions-ts-essentials": "1.3.2",
"body-parser": "1.19.0",
+ "bunyan": "1.8.12",
"codecov": "3.6.5",
"connect": "3.7.0",
"deep-freeze": "0.0.1",
@@ -108,6 +110,7 @@
"js-sha256": "0.9.0",
"koa": "2.11.0",
"lerna": "3.20.2",
+ "log4js": "4.5.1",
"memcached-mock": "0.1.0",
"mock-req": "0.2.0",
"nock": "10.0.6",
@@ -121,6 +124,8 @@
"test-listen": "1.1.0",
"ts-jest": "24.3.0",
"typescript": "3.8.3",
+ "winston": "3.2.1",
+ "winston-transport": "4.3.0",
"ws": "6.2.1"
},
"jest": {
diff --git a/packages/apollo-engine-reporting/src/agent.ts b/packages/apollo-engine-reporting/src/agent.ts
index d3b0be2d1c4..6d62aee56bd 100644
--- a/packages/apollo-engine-reporting/src/agent.ts
+++ b/packages/apollo-engine-reporting/src/agent.ts
@@ -12,7 +12,7 @@ import { fetch, RequestAgent, Response } from 'apollo-server-env';
import retry from 'async-retry';
import { EngineReportingExtension } from './extension';
-import { GraphQLRequestContext } from 'apollo-server-types';
+import { GraphQLRequestContext, Logger } from 'apollo-server-types';
import { InMemoryLRUCache } from 'apollo-server-caching';
import { defaultEngineReportingSignature } from 'apollo-graphql';
@@ -185,6 +185,13 @@ export interface EngineReportingOptions {
* Creates the client information for operation traces.
*/
generateClientInfo?: GenerateClientInfo;
+
+ /**
+ * A logger interface to be used for output and errors. When not provided
+ * it will default to the server's own `logger` implementation and use
+ * `console` when that is not available.
+ */
+ logger?: Logger;
}
export interface AddTraceArgs {
@@ -209,6 +216,7 @@ const serviceHeaderDefaults = {
// to the Engine server.
export class EngineReportingAgent {
private options: EngineReportingOptions;
+ private logger: Logger = console;
private apiKey: string;
private reports: { [schemaHash: string]: FullTracesReport } = Object.create(
null,
@@ -226,6 +234,7 @@ export class EngineReportingAgent {
public constructor(options: EngineReportingOptions = {}) {
this.options = options;
+ if (options.logger) this.logger = options.logger;
this.apiKey = options.apiKey || process.env.ENGINE_API_KEY || '';
if (!this.apiKey) {
throw new Error(
@@ -236,7 +245,7 @@ export class EngineReportingAgent {
// Since calculating the signature for Engine reporting is potentially an
// expensive operation, we'll cache the signatures we generate and re-use
// them based on repeated traces for the same `queryHash`.
- this.signatureCache = createSignatureCache();
+ this.signatureCache = createSignatureCache({ logger: this.logger });
this.sendReportsImmediately = options.sendReportsImmediately;
if (!this.sendReportsImmediately) {
@@ -353,7 +362,17 @@ export class EngineReportingAgent {
await Promise.resolve();
if (this.options.debugPrintReports) {
- console.log(`Engine sending report: ${JSON.stringify(report.toJSON())}`);
+ // In terms of verbosity, and as the name of this option suggests, this
+ // message is either an "info" or a "debug" level message. However,
+ // we are using `warn` here for compatibility reasons since the
+ // `debugPrintReports` flag pre-dated the existence of log-levels and
+ // changing this to also require `debug: true` (in addition to
+ // `debugPrintReports`) just to reach the level of verbosity to produce
+ // the output would be a breaking change. The "warn" level is on by
+ // default. There is a similar theory and comment applied below.
+ this.logger.warn(
+ `Engine sending report: ${JSON.stringify(report.toJSON())}`,
+ );
}
const protobufError = FullTracesReport.verify(report);
@@ -430,7 +449,15 @@ export class EngineReportingAgent {
);
}
if (this.options.debugPrintReports) {
- console.log(`Engine report: status ${response.status}`);
+ // In terms of verbosity, and as the name of this option suggests, this
+ // message is either an "info" or a "debug" level message. However,
+ // we are using `warn` here for compatibility reasons since the
+ // `debugPrintReports` flag pre-dated the existence of log-levels and
+ // changing this to also require `debug: true` (in addition to
+ // `debugPrintReports`) just to reach the level of verbosity to produce
+ // the output would be a breaking change. The "warn" level is on by
+ // default. There is a similar theory and comment applied above.
+ this.logger.warn(`Engine report: status ${response.status}`);
}
}
@@ -516,7 +543,7 @@ export class EngineReportingAgent {
if (this.options.reportErrorFunction) {
this.options.reportErrorFunction(err);
} else {
- console.error(err.message);
+ this.logger.error(err.message);
}
});
}
@@ -529,7 +556,11 @@ export class EngineReportingAgent {
}
}
-function createSignatureCache(): InMemoryLRUCache {
+function createSignatureCache({
+ logger,
+}: {
+ logger: Logger;
+}): InMemoryLRUCache {
let lastSignatureCacheWarn: Date;
let lastSignatureCacheDisposals: number = 0;
return new InMemoryLRUCache({
@@ -558,7 +589,7 @@ function createSignatureCache(): InMemoryLRUCache {
) {
// Log the time that we last displayed the message.
lastSignatureCacheWarn = new Date();
- console.warn(
+ logger.warn(
[
'This server is processing a high number of unique operations. ',
`A total of ${lastSignatureCacheDisposals} records have been `,
diff --git a/packages/apollo-engine-reporting/src/extension.ts b/packages/apollo-engine-reporting/src/extension.ts
index 0fd804df7f7..a9199e88fd4 100644
--- a/packages/apollo-engine-reporting/src/extension.ts
+++ b/packages/apollo-engine-reporting/src/extension.ts
@@ -1,4 +1,4 @@
-import { GraphQLRequestContext, WithRequired } from 'apollo-server-types';
+import { GraphQLRequestContext, WithRequired, Logger } from 'apollo-server-types';
import { Request, Headers } from 'apollo-server-env';
import {
GraphQLResolveInfo,
@@ -30,6 +30,7 @@ const clientVersionHeaderKey = 'apollographql-client-version';
// Its public methods all implement the GraphQLExtension interface.
export class EngineReportingExtension
implements GraphQLExtension {
+ private logger: Logger = console;
private treeBuilder: EngineReportingTreeBuilder;
private explicitOperationName?: string | null;
private queryString?: string;
@@ -46,12 +47,14 @@ export class EngineReportingExtension
this.options = {
...options,
};
+ if (options.logger) this.logger = options.logger;
this.addTrace = addTrace;
this.generateClientInfo =
options.generateClientInfo || defaultGenerateClientInfo;
this.treeBuilder = new EngineReportingTreeBuilder({
rewriteError: options.rewriteError,
+ logger: this.logger,
});
}
diff --git a/packages/apollo-engine-reporting/src/treeBuilder.ts b/packages/apollo-engine-reporting/src/treeBuilder.ts
index 448c180f81c..90854a45d64 100644
--- a/packages/apollo-engine-reporting/src/treeBuilder.ts
+++ b/packages/apollo-engine-reporting/src/treeBuilder.ts
@@ -9,7 +9,7 @@ import {
PersistedQueryNotFoundError,
PersistedQueryNotSupportedError,
} from 'apollo-server-errors';
-import { InvalidGraphQLRequestError } from 'apollo-server-types';
+import { InvalidGraphQLRequestError, Logger } from 'apollo-server-types';
function internalError(message: string) {
return new Error(`[internal apollo-server error] ${message}`);
@@ -17,6 +17,7 @@ function internalError(message: string) {
export class EngineReportingTreeBuilder {
private rootNode = new Trace.Node();
+ private logger: Logger = console;
public trace = new Trace({ root: this.rootNode });
public startHrTime?: [number, number];
private stopped = false;
@@ -26,9 +27,11 @@ export class EngineReportingTreeBuilder {
private rewriteError?: (err: GraphQLError) => GraphQLError | null;
public constructor(options: {
+ logger?: Logger;
rewriteError?: (err: GraphQLError) => GraphQLError | null;
}) {
this.rewriteError = options.rewriteError;
+ if (options.logger) this.logger = options.logger;
}
public startTiming() {
@@ -137,7 +140,7 @@ export class EngineReportingTreeBuilder {
if (specificNode) {
node = specificNode;
} else {
- console.warn(
+ this.logger.warn(
`Could not find node with path ${path.join(
'.',
)}; defaulting to put errors on root node.`,
diff --git a/packages/apollo-gateway/CHANGELOG.md b/packages/apollo-gateway/CHANGELOG.md
index e73594426cc..6700d4de5d6 100644
--- a/packages/apollo-gateway/CHANGELOG.md
+++ b/packages/apollo-gateway/CHANGELOG.md
@@ -7,6 +7,7 @@
- General improvements and clarity to error messages and logging. [PR #3811](https://github.com/apollographql/apollo-server/pull/3811)
- Warn of a possible misconfiguration when local service configuration is provided (via `serviceList` or `localServiceList`) and a remote Apollo Graph Manager configuration is subsequently found as well. [PR #3868](https://github.com/apollographql/apollo-server/pull/3868)
- During composition, the unavailability of a downstream service in unmanaged federation mode will no longer result in a partially composed schema which merely lacks the types provided by the downed service. This prevents unexpected validation errors for clients querying a graph which lacks types which were merely unavailable during the initial composition but were intended to be part of the graph. [PR #3867](https://github.com/apollographql/apollo-server/pull/3867)
+- Support providing a custom logger implementation (e.g. [`winston`](https://npm.im/winston), [`bunyan`](https://npm.im/bunyan), etc.) to capture gateway-sourced console output. This allows the use of existing, production logging facilities or the possibiltiy to use advanced structure in logging, such as console output which is encapsulated in JSON. The same PR that introduces this support also introduces a `logger` property to the `GraphQLRequestContext` that is exposed to `GraphQLDataSource`s and Apollo Server plugins, making it possible to attach additional properties (as supported by the logger implementation) to specific requests, if desired, by leveraging custom implementations in those components respectively. When not provided, these will still output to `console`. [PR #3894](https://github.com/apollographql/apollo-server/pull/3894)
## 0.13.2
diff --git a/packages/apollo-gateway/src/__tests__/integration/logger.test.ts b/packages/apollo-gateway/src/__tests__/integration/logger.test.ts
new file mode 100644
index 00000000000..690c046d421
--- /dev/null
+++ b/packages/apollo-gateway/src/__tests__/integration/logger.test.ts
@@ -0,0 +1,125 @@
+import { ApolloGateway } from '../..';
+import { Logger } from "apollo-server-types";
+import { PassThrough } from "stream";
+
+import * as winston from "winston";
+import WinstonTransport from 'winston-transport';
+import * as bunyan from "bunyan";
+import * as loglevel from "loglevel";
+// We are testing an older version of `log4js` which uses older ECMAScript
+// in order to still support testing on Node.js 6.
+// This should be updated when bump the semver major for AS3.
+import * as log4js from "log4js";
+
+const LOWEST_LOG_LEVEL = "debug";
+
+const KNOWN_DEBUG_MESSAGE = "Checking service definitions...";
+
+async function triggerKnownDebugMessage(logger: Logger) {
+ // Trigger a known error.
+ // This is a bit brittle since it merely leverages a known debug log
+ // message outside of the constructor, but it seemed worth testing
+ // the compatibility with `ApolloGateway` itself rather than generically.
+ // The error does not matter, so it is caught and ignored.
+ await new ApolloGateway({ logger }).load().catch(_e => {});
+}
+
+describe("logger", () => {
+ it("works with 'winston'", async () => {
+ const sink = jest.fn();
+ const transport = new class extends WinstonTransport {
+ constructor() {
+ super({
+ format: winston.format.json(),
+ });
+ }
+
+ log(info: any) {
+ sink(info);
+ }
+ };
+
+ const logger = winston.createLogger({ level: 'debug' }).add(transport);
+
+ await triggerKnownDebugMessage(logger);
+
+ expect(sink).toHaveBeenCalledWith(expect.objectContaining({
+ level: LOWEST_LOG_LEVEL,
+ message: KNOWN_DEBUG_MESSAGE,
+ }));
+ });
+
+ it("works with 'bunyan'", async () => {
+ const sink = jest.fn();
+
+ // Bunyan uses streams for its logging implementations.
+ const writable = new PassThrough();
+ writable.on("data", data => sink(JSON.parse(data.toString())));
+
+ const logger = bunyan.createLogger({
+ name: "test-logger-bunyan",
+ streams: [{
+ level: LOWEST_LOG_LEVEL,
+ stream: writable,
+ }]
+ });
+
+ await triggerKnownDebugMessage(logger);
+
+ expect(sink).toHaveBeenCalledWith(expect.objectContaining({
+ level: bunyan.DEBUG,
+ msg: KNOWN_DEBUG_MESSAGE,
+ }));
+ });
+
+ it("works with 'loglevel'", async () => {
+ const sink = jest.fn();
+
+ const logger = loglevel.getLogger("test-logger-loglevel")
+ logger.methodFactory = (_methodName, level): loglevel.LoggingMethod =>
+ (message) => sink({ level, message });
+
+ // The `setLevel` method must be called after overwriting `methodFactory`.
+ // This is an intentional API design pattern of the loglevel package:
+ // https://www.npmjs.com/package/loglevel#writing-plugins
+ logger.setLevel(loglevel.levels.DEBUG);
+
+ await triggerKnownDebugMessage(logger);
+
+ expect(sink).toHaveBeenCalledWith({
+ level: loglevel.levels.DEBUG,
+ message: KNOWN_DEBUG_MESSAGE,
+ });
+ });
+
+ it("works with 'log4js'", async () => {
+ const sink = jest.fn();
+
+ log4js.configure({
+ appenders: {
+ custom: {
+ type: {
+ configure: () =>
+ (loggingEvent: log4js.LoggingEvent) => sink(loggingEvent)
+ }
+ }
+ },
+ categories: {
+ default: {
+ appenders: ['custom'],
+ level: LOWEST_LOG_LEVEL,
+ }
+ }
+ });
+
+ const logger = log4js.getLogger();
+ logger.level = LOWEST_LOG_LEVEL;
+
+ await triggerKnownDebugMessage(logger);
+
+ expect(sink).toHaveBeenCalledWith(expect.objectContaining({
+ level: log4js.levels.DEBUG,
+ data: [KNOWN_DEBUG_MESSAGE],
+ }));
+ });
+});
diff --git a/packages/apollo-gateway/src/executeQueryPlan.ts b/packages/apollo-gateway/src/executeQueryPlan.ts
index cb499dfdbd0..d6f9cb8d49a 100644
--- a/packages/apollo-gateway/src/executeQueryPlan.ts
+++ b/packages/apollo-gateway/src/executeQueryPlan.ts
@@ -202,6 +202,7 @@ async function executeFetch(
_path: ResponsePath,
traceNode: Trace.QueryPlanNode.FetchNode | null,
): Promise {
+ const logger = context.requestContext.logger || console;
const service = context.serviceMap[fetch.serviceName];
if (!service) {
throw new Error(`Couldn't find service with name "${fetch.serviceName}"`);
@@ -351,7 +352,7 @@ async function executeFetch(
// supports that, but there's not a no-deps base64 implementation.
traceBuffer = Buffer.from(traceBase64, 'base64');
} catch (err) {
- console.error(
+ logger.error(
`error decoding base64 for federated trace from ${fetch.serviceName}: ${err}`,
);
traceParsingFailed = true;
@@ -362,7 +363,7 @@ async function executeFetch(
const trace = Trace.decode(traceBuffer);
traceNode.trace = trace;
} catch (err) {
- console.error(
+ logger.error(
`error decoding protobuf for federated trace from ${fetch.serviceName}: ${err}`,
);
traceParsingFailed = true;
diff --git a/packages/apollo-gateway/src/index.ts b/packages/apollo-gateway/src/index.ts
index e6cc0f211ab..64f6ca18e46 100644
--- a/packages/apollo-gateway/src/index.ts
+++ b/packages/apollo-gateway/src/index.ts
@@ -7,6 +7,7 @@ import {
import {
GraphQLExecutionResult,
GraphQLRequestContext,
+ Logger,
WithRequired,
} from 'apollo-server-types';
import { InMemoryLRUCache } from 'apollo-server-caching';
@@ -19,7 +20,7 @@ import {
} from 'graphql';
import { GraphQLSchemaValidationError } from 'apollo-graphql';
import { composeAndValidate, ServiceDefinition } from '@apollo/federation';
-import loglevel, { Logger } from 'loglevel';
+import loglevel from 'loglevel';
import loglevelDebug from 'loglevel-debug';
import { buildQueryPlan, buildOperationContext } from './buildQueryPlan';
@@ -48,6 +49,7 @@ export type ServiceEndpointDefinition = Pick;
interface GatewayConfigBase {
debug?: boolean;
+ logger?: Logger;
// TODO: expose the query plan in a more flexible JSON format in the future
// and remove this config option in favor of `exposeQueryPlan`. Playground
// should cutover to use the new option when it's built.
@@ -165,7 +167,7 @@ export class ApolloGateway implements GraphQLService {
public schema?: GraphQLSchema;
protected serviceMap: DataSourceCache = Object.create(null);
protected config: GatewayConfig;
- protected logger: Logger;
+ private logger: Logger;
protected queryPlanStore?: InMemoryLRUCache;
private engineConfig: GraphQLServiceEngineConfig | undefined;
private pollingTimer?: NodeJS.Timer;
@@ -213,15 +215,22 @@ export class ApolloGateway implements GraphQLService {
...config,
};
- // Setup logging facilities, scoped under the appropriate name.
- this.logger = loglevel.getLogger(`apollo-gateway:`);
+ // Setup logging facilities
+ if (this.config.logger) {
+ this.logger = this.config.logger;
+ } else {
+ // If the user didn't provide their own logger, we'll initialize one.
+ const loglevelLogger = loglevel.getLogger(`apollo-gateway:`);
- // Support DEBUG environment variable, à la https://npm.im/debug/.
- loglevelDebug(this.logger);
+ // Support DEBUG environment variable, à la https://npm.im/debug/.
+ loglevelDebug(loglevelLogger);
- // And also support the `debug` option, if it's truthy.
- if (this.config.debug === true) {
- this.logger.enableAll();
+ // And also support the `debug` option, if it's truthy.
+ if (this.config.debug === true) {
+ loglevelLogger.enableAll();
+ }
+
+ this.logger = loglevelLogger;
}
if (isLocalConfig(this.config)) {
@@ -346,7 +355,7 @@ export class ApolloGateway implements GraphQLService {
} catch (e) {
this.logger.error(
"An error was thrown from an 'onSchemaChange' listener. " +
- "The schema will still update: ", e);
+ "The schema will still update: " + (e && e.message || e));
}
if (this.experimental_didUpdateComposition) {
@@ -588,7 +597,11 @@ export class ApolloGateway implements GraphQLService {
// is returning a non-native `Promise` (e.g. Bluebird, etc.).
Promise.resolve(
this.queryPlanStore.set(queryPlanStoreKey, queryPlan),
- ).catch(err => this.logger.warn('Could not store queryPlan', err));
+ ).catch(err =>
+ this.logger.warn(
+ 'Could not store queryPlan' + ((err && err.messsage) || err),
+ ),
+ );
}
}
diff --git a/packages/apollo-server-core/package.json b/packages/apollo-server-core/package.json
index be38b512cc2..dd7c21dbf80 100644
--- a/packages/apollo-server-core/package.json
+++ b/packages/apollo-server-core/package.json
@@ -42,6 +42,7 @@
"graphql-tag": "^2.9.2",
"graphql-tools": "^4.0.0",
"graphql-upload": "^8.0.2",
+ "loglevel": "^1.6.7",
"sha.js": "^2.4.11",
"subscriptions-transport-ws": "^0.9.11",
"ws": "^6.0.0"
diff --git a/packages/apollo-server-core/src/ApolloServer.ts b/packages/apollo-server-core/src/ApolloServer.ts
index 6d225b4c6cf..e56fba35136 100644
--- a/packages/apollo-server-core/src/ApolloServer.ts
+++ b/packages/apollo-server-core/src/ApolloServer.ts
@@ -4,6 +4,7 @@ import {
GraphQLParseOptions,
} from 'graphql-tools';
import { Server as HttpServer } from 'http';
+import loglevel from 'loglevel';
import {
execute,
GraphQLSchema,
@@ -68,6 +69,7 @@ import {
import { Headers } from 'apollo-server-env';
import { buildServiceDefinition } from '@apollographql/apollo-tools';
+import { Logger } from "apollo-server-types";
const NoIntrospection = (context: ValidationContext) => ({
Field(node: FieldDefinitionNode) {
@@ -131,6 +133,7 @@ type SchemaDerivedData = {
};
export class ApolloServerBase {
+ private logger: Logger;
public subscriptionsPath?: string;
public graphqlPath: string = '/graphql';
public requestOptions: Partial> = Object.create(null);
@@ -185,6 +188,28 @@ export class ApolloServerBase {
...requestOptions
} = config;
+ // Setup logging facilities
+ if (config.logger) {
+ this.logger = config.logger;
+ } else {
+ // If the user didn't provide their own logger, we'll initialize one.
+ const loglevelLogger = loglevel.getLogger("apollo-server");
+
+ // We don't do much logging in Apollo Server right now. There's a notion
+ // of a `debug` flag, but it doesn't do much besides change stack traces
+ // in some error messages, but it would be odd for it to not introduce
+ // debug or higher level errors (which includes `info`, if we happen to
+ // start introducing those. We'll default to `warn` as a sensible default
+ // of things you'd probably want to be alerted to.
+ if (this.config.debug === true) {
+ loglevelLogger.setLevel(loglevel.levels.DEBUG);
+ } else {
+ loglevelLogger.setLevel(loglevel.levels.WARN);
+ }
+
+ this.logger = loglevelLogger;
+ }
+
if (gateway && (modules || schema || typeDefs || resolvers)) {
throw new Error(
'Cannot define both `gateway` and any of: `modules`, `schema`, `typeDefs`, or `resolvers`',
@@ -267,7 +292,7 @@ export class ApolloServerBase {
if (uploads !== false && !forbidUploadsForTesting) {
if (this.supportsUploads()) {
if (!runtimeSupportsUploads) {
- printNodeFileUploadsMessage();
+ printNodeFileUploadsMessage(this.logger);
throw new Error(
'`graphql-upload` is no longer supported on Node.js < v8.5.0. ' +
'See https://bit.ly/gql-upload-node-6.',
@@ -288,8 +313,13 @@ export class ApolloServerBase {
}
}
- // Normalize the legacy option maskErrorDetails.
if (engine && typeof engine === 'object') {
+ // Use the `ApolloServer` logger unless a more granular logger is set.
+ if (!engine.logger) {
+ engine.logger = this.logger;
+ }
+
+ // Normalize the legacy option maskErrorDetails.
if (engine.maskErrorDetails && engine.rewriteError) {
throw new Error("Can't set both maskErrorDetails and rewriteError!");
} else if (
@@ -318,7 +348,9 @@ export class ApolloServerBase {
if (this.engineServiceId) {
const { EngineReportingAgent } = require('apollo-engine-reporting');
this.engineReportingAgent = new EngineReportingAgent(
- typeof engine === 'object' ? engine : Object.create(null),
+ typeof engine === 'object' ? engine : Object.create({
+ logger: this.logger,
+ }),
);
// Don't add the extension here (we want to add it later in generateSchemaDerivedData).
}
@@ -430,7 +462,7 @@ export class ApolloServerBase {
// error will propogate to the client. We will, however, log the error
// for observation in the logs.
const message = "This data graph is missing a valid configuration.";
- console.error(message + " " + (err && err.message || err));
+ this.logger.error(message + " " + (err && err.message || err));
throw new Error(
message + " More details may be available in the server logs.");
});
@@ -534,7 +566,7 @@ export class ApolloServerBase {
// their own gateway or running a federated service on its own. Nonetheless, in
// the likely case it was accidental, we warn users that they should only report
// metrics from the Gateway.
- console.warn(
+ this.logger.warn(
"It looks like you're running a federated schema and you've configured your service " +
'to report metrics to Apollo Graph Manager. You should only configure your Apollo gateway ' +
'to report metrics to Apollo Graph Manager.',
@@ -588,6 +620,7 @@ export class ApolloServerBase {
}
const service: GraphQLServiceContext = {
+ logger: this.logger,
schema: schema,
schemaHash: schemaHash,
engine: {
@@ -795,6 +828,7 @@ export class ApolloServerBase {
return {
schema,
+ logger: this.logger,
plugins: this.plugins,
documentStore,
extensions,
@@ -822,6 +856,7 @@ export class ApolloServerBase {
}
const requestCtx: GraphQLRequestContext = {
+ logger: this.logger,
request,
context: options.context || Object.create(null),
cache: options.cache!,
@@ -836,8 +871,8 @@ export class ApolloServerBase {
}
}
-function printNodeFileUploadsMessage() {
- console.error(
+function printNodeFileUploadsMessage(logger: Logger) {
+ logger.error(
[
'*****************************************************************',
'* *',
diff --git a/packages/apollo-server-core/src/__tests__/logger.test.ts b/packages/apollo-server-core/src/__tests__/logger.test.ts
new file mode 100644
index 00000000000..a4a34b45b30
--- /dev/null
+++ b/packages/apollo-server-core/src/__tests__/logger.test.ts
@@ -0,0 +1,137 @@
+import { ApolloServerBase } from '../..';
+import { Logger } from "apollo-server-types";
+import { PassThrough } from "stream";
+import gql from "graphql-tag";
+
+import * as winston from "winston";
+import WinstonTransport from 'winston-transport';
+import * as bunyan from "bunyan";
+import * as loglevel from "loglevel";
+// We are testing an older version of `log4js` which uses older ECMAScript
+// in order to still support testing on Node.js 6.
+// This should be updated when bump the semver major for AS3.
+import * as log4js from "log4js";
+
+const LOWEST_LOG_LEVEL = "debug";
+
+const KNOWN_DEBUG_MESSAGE = "The request has started.";
+
+async function triggerLogMessage(loggerToUse: Logger) {
+ await (new ApolloServerBase({
+ typeDefs: gql`
+ type Query {
+ field: String!
+ }
+ `,
+ logger: loggerToUse,
+ plugins: [
+ {
+ requestDidStart({ logger }) {
+ logger.debug(KNOWN_DEBUG_MESSAGE);
+ }
+ }
+ ]
+ })).executeOperation({
+ query: '{ field }'
+ });
+}
+
+describe("logger", () => {
+ it("works with 'winston'", async () => {
+ const sink = jest.fn();
+ const transport = new class extends WinstonTransport {
+ constructor() {
+ super({
+ format: winston.format.json(),
+ });
+ }
+
+ log(info: any) {
+ sink(info);
+ }
+ };
+
+ const logger = winston.createLogger({ level: 'debug' }).add(transport);
+
+ await triggerLogMessage(logger);
+
+ expect(sink).toHaveBeenCalledWith(expect.objectContaining({
+ level: LOWEST_LOG_LEVEL,
+ message: KNOWN_DEBUG_MESSAGE,
+ }));
+ });
+
+ it("works with 'bunyan'", async () => {
+ const sink = jest.fn();
+
+ // Bunyan uses streams for its logging implementations.
+ const writable = new PassThrough();
+ writable.on("data", data => sink(JSON.parse(data.toString())));
+
+ const logger = bunyan.createLogger({
+ name: "test-logger-bunyan",
+ streams: [{
+ level: LOWEST_LOG_LEVEL,
+ stream: writable,
+ }]
+ });
+
+ await triggerLogMessage(logger);
+
+ expect(sink).toHaveBeenCalledWith(expect.objectContaining({
+ level: bunyan.DEBUG,
+ msg: KNOWN_DEBUG_MESSAGE,
+ }));
+ });
+
+ it("works with 'loglevel'", async () => {
+ const sink = jest.fn();
+
+ const logger = loglevel.getLogger("test-logger-loglevel")
+ logger.methodFactory = (_methodName, level): loglevel.LoggingMethod =>
+ (message) => sink({ level, message });
+
+ // The `setLevel` method must be called after overwriting `methodFactory`.
+ // This is an intentional API design pattern of the loglevel package:
+ // https://www.npmjs.com/package/loglevel#writing-plugins
+ logger.setLevel(loglevel.levels.DEBUG);
+
+ await triggerLogMessage(logger);
+
+ expect(sink).toHaveBeenCalledWith({
+ level: loglevel.levels.DEBUG,
+ message: KNOWN_DEBUG_MESSAGE,
+ });
+ });
+
+ it("works with 'log4js'", async () => {
+ const sink = jest.fn();
+
+ log4js.configure({
+ appenders: {
+ custom: {
+ type: {
+ configure: () =>
+ (loggingEvent: log4js.LoggingEvent) => sink(loggingEvent)
+ }
+ }
+ },
+ categories: {
+ default: {
+ appenders: ['custom'],
+ level: LOWEST_LOG_LEVEL,
+ }
+ }
+ });
+
+ const logger = log4js.getLogger();
+ logger.level = LOWEST_LOG_LEVEL;
+
+ await triggerLogMessage(logger);
+
+ expect(sink).toHaveBeenCalledWith(expect.objectContaining({
+ level: log4js.levels.DEBUG,
+ data: [KNOWN_DEBUG_MESSAGE],
+ }));
+ });
+});
diff --git a/packages/apollo-server-core/src/graphqlOptions.ts b/packages/apollo-server-core/src/graphqlOptions.ts
index b9abdd0f949..e6da70d37b0 100644
--- a/packages/apollo-server-core/src/graphqlOptions.ts
+++ b/packages/apollo-server-core/src/graphqlOptions.ts
@@ -17,12 +17,14 @@ import {
ValueOrPromise,
GraphQLResponse,
GraphQLRequestContext,
+ Logger,
} from 'apollo-server-types';
/*
* GraphQLServerOptions
*
* - schema: an executable GraphQL schema used to fulfill requests.
+ * - (optional) logger: a `Logger`-compatible implementation to be used for server-level messages.
* - (optional) formatError: Formatting function applied to all errors before response is sent
* - (optional) rootValue: rootValue passed to GraphQL execution, or a function to resolving the rootValue from the DocumentNode
* - (optional) context: the context passed to GraphQL execution
@@ -40,6 +42,7 @@ export interface GraphQLServerOptions<
TRootValue = any
> {
schema: GraphQLSchema;
+ logger?: Logger;
formatError?: (error: GraphQLError) => GraphQLFormattedError;
rootValue?: ((parsedQuery: DocumentNode) => TRootValue) | TRootValue;
context?: TContext | (() => never);
diff --git a/packages/apollo-server-core/src/requestPipeline.ts b/packages/apollo-server-core/src/requestPipeline.ts
index 786ade95b37..3d41fc9243b 100644
--- a/packages/apollo-server-core/src/requestPipeline.ts
+++ b/packages/apollo-server-core/src/requestPipeline.ts
@@ -108,6 +108,11 @@ export async function processGraphQLRequest(
config: GraphQLRequestPipelineConfig,
requestContext: Mutable>,
): Promise {
+ // For legacy reasons, this exported method may exist without a `logger` on
+ // the context. We'll need to make sure we account for that, even though
+ // all of our own machinery will certainly set it now.
+ const logger = requestContext.logger || console;
+
let cacheControlExtension: CacheControlExtension | undefined;
const extensionStack = initializeExtensionStack();
(requestContext.context as any)._extensionStack = extensionStack;
@@ -215,9 +220,9 @@ export async function processGraphQLRequest(
try {
requestContext.document = await config.documentStore.get(queryHash);
} catch (err) {
- console.warn(
- 'An error occurred while attempting to read from the documentStore.',
- err,
+ logger.warn(
+ 'An error occurred while attempting to read from the documentStore. '
+ + (err && err.messsage) || err,
);
}
}
@@ -274,7 +279,10 @@ export async function processGraphQLRequest(
Promise.resolve(
config.documentStore.set(queryHash, requestContext.document),
).catch(err =>
- console.warn('Could not store validated document.', err),
+ logger.warn(
+ 'Could not store validated document. ' +
+ (err && err.message) || err
+ )
);
}
}
@@ -341,7 +349,7 @@ export async function processGraphQLRequest(
}
: Object.create(null),
),
- ).catch(console.warn);
+ ).catch(logger.warn);
}
let response: GraphQLResponse | null = await dispatcher.invokeHooksUntilNonNull(
diff --git a/packages/apollo-server-core/src/runHttpQuery.ts b/packages/apollo-server-core/src/runHttpQuery.ts
index c9d2d3ed0df..c229fe87cf7 100644
--- a/packages/apollo-server-core/src/runHttpQuery.ts
+++ b/packages/apollo-server-core/src/runHttpQuery.ts
@@ -161,6 +161,7 @@ export async function runHttpQuery(
const config = {
schema: options.schema,
+ logger: options.logger,
rootValue: options.rootValue,
context: options.context || {},
validationRules: options.validationRules,
@@ -250,6 +251,11 @@ export async function processHTTPRequest(
// in ApolloServer#graphQLServerOptions, before runHttpQuery is invoked).
const context = cloneObject(options.context);
return {
+ // While `logger` is guaranteed by internal Apollo Server usage of
+ // this `processHTTPRequest` method, this method has been publicly
+ // exported since perhaps as far back as Apollo Server 1.x. Therefore,
+ // for compatibility reasons, we'll default to `console`.
+ logger: options.logger || console,
request,
response: {
http: {
diff --git a/packages/apollo-server-core/src/types.ts b/packages/apollo-server-core/src/types.ts
index ddd1652c9bd..c62191384c1 100644
--- a/packages/apollo-server-core/src/types.ts
+++ b/packages/apollo-server-core/src/types.ts
@@ -68,6 +68,7 @@ type BaseConfig = Pick<
| 'tracing'
| 'dataSources'
| 'cache'
+ | 'logger'
>;
export type Unsubscriber = () => void;
diff --git a/packages/apollo-server-plugin-response-cache/src/ApolloServerPluginResponseCache.ts b/packages/apollo-server-plugin-response-cache/src/ApolloServerPluginResponseCache.ts
index f942d49ca6b..bd78388e3ed 100644
--- a/packages/apollo-server-plugin-response-cache/src/ApolloServerPluginResponseCache.ts
+++ b/packages/apollo-server-plugin-response-cache/src/ApolloServerPluginResponseCache.ts
@@ -222,6 +222,8 @@ export default function plugin(
},
async willSendResponse(requestContext) {
+ const logger = requestContext.logger || console;
+
if (!isGraphQLQuery(requestContext)) {
return;
}
@@ -295,13 +297,13 @@ export default function plugin(
// InMemoryLRUCache synchronously).
cache
.set(key, serializedValue, { ttl: overallCachePolicy!.maxAge })
- .catch(console.warn);
+ .catch(logger.warn);
}
const isPrivate = overallCachePolicy.scope === CacheScope.Private;
if (isPrivate) {
if (!options.sessionId) {
- console.warn(
+ logger.warn(
'A GraphQL response used @cacheControl or setCacheHint to set cache hints with scope ' +
"Private, but you didn't define the sessionId hook for " +
'apollo-server-plugin-response-cache. Not caching.',
diff --git a/packages/apollo-server-types/src/index.ts b/packages/apollo-server-types/src/index.ts
index 68b52128309..fb19ab68854 100644
--- a/packages/apollo-server-types/src/index.ts
+++ b/packages/apollo-server-types/src/index.ts
@@ -19,6 +19,7 @@ export type WithRequired = T & Required>;
type Mutable = { -readonly [P in keyof T]: T[P] };
export interface GraphQLServiceContext {
+ logger: Logger;
schema: GraphQLSchema;
schemaHash: string;
engine: {
@@ -62,6 +63,8 @@ export interface GraphQLRequestContext> {
readonly request: GraphQLRequest;
readonly response?: GraphQLResponse;
+ logger: Logger;
+
readonly context: TContext;
readonly cache: KeyValueCache;
@@ -107,3 +110,11 @@ export type GraphQLExecutionResult = {
errors?: ReadonlyArray;
extensions?: Record;
};
+
+export type Logger = {
+ // Ordered from least-severe to most-severe.
+ debug(message?: any): void;
+ info(message?: any): void;
+ warn(message?: any): void;
+ error(message?: any): void;
+}
diff --git a/renovate.json5 b/renovate.json5
index 4c2abb7dd6f..f22ff3bbc07 100644
--- a/renovate.json5
+++ b/renovate.json5
@@ -32,6 +32,10 @@
"packageNames": ["@koa/cors"],
"allowedVersions": "<3"
},
+ {
+ "packageNames": ["log4js"],
+ "allowedVersions": "<5"
+ },
{
"packageNames": ["hapi", "@types/hapi"],
"allowedVersions": "<18"