-
Notifications
You must be signed in to change notification settings - Fork 3
/
index.js
136 lines (112 loc) · 3.75 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
'use strict'
const fp = require('fastify-plugin')
function noop () {
return undefined
}
function simpleBody (_context, body) {
return body.query
}
function conditionalBody (fn, context, body) {
try {
if (fn(context, body) === true) {
return body.query
}
} catch (error) {
context.app.log.debug(error, 'mercurius-logging: error in logBody function')
}
return undefined
}
function customLogMessage (fn, context) {
try {
return fn(context)
} catch (error) {
context.app.log.debug(error, 'mercurius-logging: error in logMessage function')
}
return undefined
}
function mercuriusLogging (app, opts, next) {
const options = Object.assign({}, {
logLevel: 'info',
prependAlias: false,
logBody: false,
logVariables: false,
logRequest: false,
logMessage: undefined
}, opts)
options.buildBody = opts.logBody === true
? simpleBody
: typeof opts.logBody === 'function'
? conditionalBody.bind(null, opts.logBody)
: noop
options.buildLogMessage = typeof opts.logMessage === 'function'
? customLogMessage.bind(null, opts.logMessage)
: undefined
app.graphql.addHook('preExecution', logGraphQLDetails.bind(null, options))
next()
}
function logGraphQLDetails (opts, schema, document, context) {
// Reply object could be undefined. In this case, we can't log.
if (!context.reply) {
return
}
const queryOps = readOps(document, 'query', opts)
const mutationOps = readOps(document, 'mutation', opts)
const requestBody = context.reply.request.method !== 'GET'
? context.reply.request.body
: context.reply.request.query
// operationName can be provided at the request level, or it can be provided by query/mutation.
// If it's provided by both, the request level takes precedence.
const operationName = requestBody.operationName || readOperationName(document)
const isCurrentOperation = (op) => op.operationName === operationName
// Runs on a single operation at a time in a batched query, so we need to pull out
// the relevant operation from the batch to be able to log variables for it.
const isBatch = Array.isArray(requestBody)
const isDetailedLog = opts.logVariables || opts.logBody
const currentBody = isDetailedLog && isBatch
? requestBody.find(isCurrentOperation)
: requestBody
const logData = {
req: opts.logRequest === true ? context.reply.request : undefined,
graphql: {
queries: queryOps.length > 0 ? queryOps : undefined,
mutations: mutationOps.length > 0 ? mutationOps : undefined,
operationName,
body: opts.buildBody(context, currentBody),
variables: opts.logVariables === true ? currentBody?.variables || null : undefined
}
}
const logMessage = opts.buildLogMessage?.(context)
if (!logMessage) {
context.reply.request.log[opts.logLevel](logData)
return
}
context.reply.request.log[opts.logLevel].apply(context.reply.request.log, [logData].concat(logMessage))
}
function readOperationName (document) {
return document.definitions
.filter((d) => d.kind === 'OperationDefinition')
.map((d) => d.name)
.find((d) => d?.kind === 'Name')?.value
}
function readOps (document, operation, opts) {
return document.definitions
.filter(d => d.kind === 'OperationDefinition' && d.operation === operation)
.flatMap(d => d.selectionSet.selections)
.map(selectionSet => {
const opName = selectionSet.name.value
if (opts.prependAlias && selectionSet.alias) {
return selectionSet.alias.value + ':' + opName
}
return opName
})
}
const plugin = fp(mercuriusLogging,
{
name: 'mercurius-logging',
fastify: '^5.x',
dependencies: ['mercurius']
}
)
module.exports = plugin
module.exports.default = plugin
module.exports.mercuriusLogging = plugin