GraphQL Yoga v6 replaces its console-based logger with Hive Logger.
Every request now gets its own logger tagged with a requestId, and Yoga logs more of the request
lifecycle out of the box. Yoga v6 requires Node.js 22 or later and GraphQL 16 or later.
Yoga has gone through some major changes to clean up technical debt that built up over time and to improve the overall logging experience.
These changes can be grouped into three large changes:
- Hive Logger Migration
- GraphQL + Node Version Updates and Cleanup
- GraphQL Tools Major Update
Hive Logger
Yoga now uses Hive Logger, the same structured logger used across the Hive platform. You get log levels, attributes, child loggers, redaction and pluggable writers.
Per-request logging with context.log
Each request gets a child logger,
available as context.log. It carries the request’s requestId: Yoga uses a host-assigned id first,
then the incoming x-request-id header if trusted, and otherwise generates one.
Use the logger from the context whenever you are handling a request. Unlike console, a new
Logger() or the server-wide yoga.log, it automatically associates your messages with that request.
Your application logs, Yoga’s lifecycle logs and masked errors can then be correlated by the same id.
You do not need to create a logger or add the request id to each message yourself.
The logger is already available in your context factory, as well as in resolvers. Returning your
own context fields extends Yoga’s context, so you do not need to return log again:
If a service or helper called by a resolver needs to log, pass context.log to it rather than having
it use a separate global logger. That keeps its messages tied to the request too.
Logging from plugin hooks
The request logger is also available throughout the plugin lifecycle. HTTP hooks such as
onRequestParse and onResponse expose it as serverContext.log. GraphQL execution hooks expose
the GraphQL context through args.contextValue, so use args.contextValue.log in onExecute and
onSubscribe.
Read the logger from each hook’s payload rather than capturing yoga.log during plugin
initialization:
onYogaInit is the exception in this example: it runs at server initialization, not for a request,
so yoga.log is appropriate there. Inside request hooks, use the context logger instead. Keeping it
in a local variable for a completion callback, as above, preserves that request’s correlation;
storing it in a plugin-wide variable would mix up concurrent requests.
More logging out of the box
New debug-level log lines were added for:
- the request path,
operationNameand variables count when processing GraphQL parameters - the resolved GraphQL operation type after parsing, and parser errors (previously indistinguishable from other unexpected errors)
- the response status code once a result has been processed into an HTTP
Response - a successfully fetched signing key in
@graphql-yoga/plugin-jwt - a successfully loaded supergraph schema in
@graphql-yoga/apollo-managed-federation(infolevel, mirroring the existing failure log)
A one-line info-level “Request processed” summary is now logged once per HTTP request, carrying the method, path, operationName and operationType (or batchedOperations count for a batched request), the response status, the GraphQL errorCount, and the request’s durationMs.
Errors masked by useMaskedErrors are now logged through the request’s correlated logger too, so a
masked error can be traced back to the request that produced it.
Smarter request ID correlation
Yoga’s useRequestId plugin (enabled by default) now prefers a request id your host framework
already assigned to the request (for example Fastify’s request.id) over minting its own. This
means useRequestId can stay enabled alongside a host’s own request-id setup without the two
generating conflicting ids, and Yoga’s logs agree with whatever correlation id your framework or
proxy already uses. The resolved id is also written to the outgoing response’s x-request-id
header.
If no host-assigned id is available, Yoga falls back to the incoming x-request-id header, and
finally to a generated id. This is a change in precedence: previously, the x-request-id header
took priority over everything.
If your server is directly reachable by clients rather than only through a proxy or gateway that
sets the header itself, disable trusting the header with the new trustHeader option, so a client
can’t pick its own id and have it correlated across your logs as if it were trusted:
allowedHeaders.response now applies after CORS and plugins
allowedHeaders.response is now applied after the cors option and after your own plugins’
onResponse hooks, instead of before. Headers added by cors or by a plugin are now subject to
the allow-list and will be stripped unless included in it. If you use allowedHeaders.response
together with cors or a plugin that sets response headers, add those header names to the
allow-list.
@graphql-tools v13 and removed re-exports
Yoga’s @graphql-tools/utils, @graphql-tools/schema and @graphql-tools/executor dependencies
are now on their current major versions (^13.1.1, ^11.0.0 and ^4.1.2). Since Yoga is already
taking a major version bump for v6, this pulls in the breaking changes from those packages directly
instead of re-exporting removed helpers for compatibility.
Yoga no longer re-exports the following upstream helpers — import them from their own packages instead:
createGraphQLError→new GraphQLError(message, options)fromgraphql. To attach Yoga’s experimental schema coordinate to an error, use the newwithSchemaCoordinate(error, coordinate)helper (read it back withgetSchemaCoordinatefrom@graphql-tools/utils).mergeSchemas→@graphql-tools/schemaDisposableSymbols→@whatwg-node/servermapAsyncIterator,isPromise,mapMaybePromise,MaybePromise→@whatwg-node/promise-helpers(mapMaybePromiseis now namedhandleMaybePromise)
@envelop/core’s utils export no longer re-exports mapAsyncIterator or isPromise either —
import both from @whatwg-node/promise-helpers.
As part of the same bump, Yoga now maps the Accept header’s incremental delivery spec parameter
onto @graphql-tools/executor’s incrementalSpec through a lookup table
(INCREMENTAL_DELIVERY_SPECS from @graphql-tools/utils), so a future incremental delivery spec no
longer needs a Yoga-specific Accept/Content-Type patch.
Support for the latest incremental delivery (defer/stream) spec
Yoga can now speak the current graphql-js incremental delivery format (pending / id /
subPath / completed entries), in addition to the legacy one (path / label). Request it with
an incrementalSpec parameter on the Accept header:
Yoga echoes incrementalSpec=v0.2 back on the response’s Content-Type so clients can confirm which
format they received. A bare multipart/mixed Accept header, or one with deferSpec=20220824,
still gets the legacy v0.1 format, so existing clients are unaffected. An unrecognized spec
parameter value gets a 406 Not Acceptable instead of a silently mismatched response.
This is Yoga-only for now — Hive Gateway support for incrementalSpec=v0.2 is coming in a follow-up
release.
Dropped support for Node.js 18 and 20
Node.js 18 and 20 have reached end of life. Yoga v6 requires Node.js 22 or later, and future releases will only support Node.js versions that are still in LTS.
Dropped support for GraphQL 14 and 15
The minimum supported version of the graphql-js package is now 16.
Migrating to v6
-
Upgrade to Node.js 22 or later.
-
Upgrade
graphql-jsto version 16 or later. -
Update
graphql-yogaand any@graphql-yoga/*packages to their latest versions. -
If you passed a custom logger to Yoga or relied on the
Loggertype orcreateLogger, update that code to use Hive Logger: -
Rename
context.loggertocontext.log, andyoga.loggertoyoga.log. Useyoga.logfor server-level messages only. For request-specific messages, readcontext.log,serverContext.logorargs.contextValue.logfrom the resolver or hook payload, as shown above: -
If you depended on the standalone
@graphql-yoga/loggerpackage directly, it has been removed — install@graphql-hive/loggerinstead (or keep importingLogger/MemoryLogWriterfromgraphql-yoga, which re-exports both): -
If you passed a
loggeroption to the built-in GraphiQL or health-check plugin config, or to@envelop/newrelic,@envelop/auth0,@envelop/resource-limitations,@envelop/statsd,@envelop/graphql-jit,@envelop/response-cacheor@envelop/response-cache-cloudflare-kv, rename it tolog.@envelop/apollo-federation’suseApolloFederationkeeps itsloggeroption name, but it’s now typed asLoggerfrom@graphql-hive/loggerinstead of a loosely-typed{ warn, debug, info, error }interface, and defaults tonew Logger()instead of the globalconsole. -
If you use
@graphql-yoga/nestjs, it now logs through Nest’s ownLoggerby default instead of not logging at all. Passlogging: falseto keep the previous behavior, or, to keep logging through Fastify’s Pino instance, pass it explicitly: -
If you imported
createGraphQLError,mergeSchemas,DisposableSymbols,mapAsyncIterator,isPromise,mapMaybePromiseor theMaybePromisetype fromgraphql-yoga, import them from their upstream packages instead: -
If you use
@envelop/core’smapAsyncIteratororisPromise, import them from@whatwg-node/promise-helpersinstead. -
If you depend on
@graphql-tools/utils,@graphql-tools/schemaor@graphql-tools/executordirectly, update them to^13.1.1,^11.0.0and^4.1.2respectively.