如何从GraphQl请求记录有用的数据?
#node #graphql #日志 #fastify

Manuel Spigolon

如果您使用的是Fastify,而Mercurius作为GraphQl适配器,则面临着登录客户发送的数据的问题。

如果您不使用fastify,则是最快的node.js web框架和mercurius graphql适配器,NeverMind,您将学习如何在下一节ð

中创建它

默认日志行一遍又一遍地显示相同的/graphql端点:

{
  "level": 30,
  "time": 1660395516356,
  "pid": 83316,
  "hostname": "eomm",
  "name": "gateway",
  "reqId": "req-1",
  "req": {
    "method": "POST",
    "url": "/graphql",
    "hostname": "localhost:60767",
    "remoteAddress": "127.0.0.1",
    "remotePort": 60769
  },
  "msg": "incoming request"
}

这不是很有用,因为您不知道:

  • 操作名称
  • 客户端发送的查询
  • 客户端发送的变量

因此,让我们尝试向日志线添加一些额外的信息,以在调试期间更有效。

如何创建基本的GQL应用程序?

幸运的是,Fastify和Mercurius是高度扩展的,让我们添加所有必要的信息。

首先,让我们通过创建一个新项目来创建一个示例GraphQl应用程序:

mkdir demo-logging-gql
cd demo-logging-gql
npm init --yes
npm i fastify mercurius

现在我们需要添加一个新的sample-app.js文件:

const Fastify = require('fastify')
const GQL = require('mercurius')

main()

async function main () {
  const app = Fastify({
    logger: {
      name: 'sample-app',
      level: 'info'
    }
  })

  // A simple GQL schema with a single query
  const schema = `
    type Query {
      readMeaningOfLife: Int
    }
  `

  // Register and load the schema with the GQL adapter
  await app.register(GQL, {
    schema,
    resolvers: {
      // A simple resolver that will execute the query
      readMeaningOfLife: function (schema, args, context, info) {
        return 42
      }
    }
  })

  // Start the server
  await app.listen({ port: 8080 })

  // Now we can run a GQL query
  const res = await doQuery(app, '{ one:readMeaningOfLife }')
  console.log(JSON.stringify(res.json(), null, 2)) // let's see the result

  app.close()
}

function doQuery (app, query) {
  return app.inject({
    method: 'POST',
    url: '/graphql',
    body: {
      query
    }
  })
}

太好了!现在我们可以使用命令运行应用程序:

node sample-app.js

您会看到类似的东西:

{"level":30,"time":1660401031343,"pid":97351,"hostname":"eomm","name":"sample-app","msg":"Server listening at http://127.0.0.1:8080"}
{"level":30,"time":1660401031346,"pid":97351,"hostname":"eomm","name":"sample-app","msg":"Server listening at http://[::1]:8080"}
{"level":30,"time":1660401031386,"pid":97351,"hostname":"eomm","name":"sample-app","reqId":"req-1","req":{"method":"POST","url":"/graphql","hostname":"localhost:80","remoteAddress":"127.0.0.1"},"msg":"incoming request"}
{"level":30,"time":1660401031395,"pid":97351,"hostname":"eomm","name":"sample-app","reqId":"req-1","res":{"statusCode":200},"responseTime":8.89283299446106,"msg":"request completed"}

您可以看到,日志行不包含有关客户端发送的查询的任何信息。让我们看看如何将此信息添加到日志行!

如何记录查询?

mercurius有很多hooks,使我们能够在某些任务之前或之后执行自定义代码。

在这种情况下,我们要在执行GQL解析器之前记录正在执行的内容。 preExecution钩是这样做的理想场所。

因此,我们现在可以通过在app.listen()呼叫之前添加此代码来编辑sample-app.js文件:

app.graphql.addHook('preExecution', async function logGraphQLDetails (schema, document, context) {
  console.log('preExecution', schema, document, context)
  return null
})

这个简单的片段将帮助我们检查钩子的论点并了解每个变量所包含的内容。从钩子返回null将继续执行解析器。

经过一些试验,您可能会发现此片段有用:

app.graphql.addHook('preExecution', function logGraphQLDetails (schema, document, context) {
  // use the request object to log the query. We will get the reqId to be able to match the response with the request
  context.reply.request.log.info({
    graphql: {
      // we need to traverse the document to get the data
      queries: document.definitions
        .filter(d => d.kind === 'OperationDefinition' && d.operation === 'query')
        .flatMap(d => d.selectionSet.selections)
        .map(selectionSet => selectionSet.name.value)
    }
  })

  return null
})

document参数是将要处理的graphQl文档。您可以在GraphQL specification中找到有关AST结构的更多详细信息。

现在,重新运行该应用程序将记录:

{"level":30,"time":1660402270890,"pid":99991,"hostname":"eomm","name":"sample-app","msg":"Server listening at http://127.0.0.1:8080"}
{"level":30,"time":1660402270891,"pid":99991,"hostname":"eomm","name":"sample-app","msg":"Server listening at http://[::1]:8080"}
{"level":30,"time":1660402270901,"pid":99991,"hostname":"eomm","name":"sample-app","reqId":"req-1","req":{"method":"POST","url":"/graphql","hostname":"localhost:80","remoteAddress":"127.0.0.1"},"msg":"incoming request"}
{"level":30,"time":1660402270905,"pid":99991,"hostname":"eomm","name":"sample-app","reqId":"req-1","graphql":{"queries":["readMeaningOfLife"]}}
{"level":30,"time":1660402270911,"pid":99991,"hostname":"eomm","name":"sample-app","reqId":"req-1","res":{"statusCode":200},"responseTime":10.019375085830688,"msg":"request completed"}

如果我们仔细观察日志行,我们可以看到graphql属性包含将执行的查询列表。

{
  "level": 30,
  "time": 1660402270905,
  "pid": 99991,
  "hostname": "eomm",
  "name": "sample-app",
  "reqId": "req-1",
  "graphql": {
    "queries": [
      "readMeaningOfLife"
    ]
  }
}

请注意,queries对象是一个数组,因为我们可以在一个请求中进行多个查询!您可以通过修改sample-app.js并添加新查询来尝试一下:

await doQuery(app, `{
  one:readMeaningOfLife
  two:readMeaningOfLife
}`)

概括

您现在已经学会了如何使用preExecution钩记录正在执行的查询并提取一些指标以了解GQL查询用法以及您可能想到的所有内容。

这篇博客文章是创建Mercurius插件来记录查询和突变的灵感!!检查一下koude11

如果您发现这很有用,则可以阅读this article

现在跳入source code on GitHub,开始使用Fastify中实现的GraphQL播放。评论并分享您是否喜欢这篇文章!