-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathgraphql-operation-logging-plugin.ts
137 lines (127 loc) · 5.61 KB
/
graphql-operation-logging-plugin.ts
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
137
import type {
ApolloServerPlugin,
GraphQLExperimentalFormattedSubsequentIncrementalExecutionResult,
GraphQLRequestContextWillSendResponse,
GraphQLRequestListener,
} from '@apollo/server'
import { isIntrospectionQuery, logGraphQLOperation, type GraphQLContext, type LoggerLogFunctions } from '@makerx/graphql-core'
import type { Logger } from '@makerx/node-common'
import { OperationTypeNode } from 'graphql'
import { omitNil } from '../utils'
export interface GraphQLOperationLoggingPluginOptions<TContext extends GraphQLContext<TLogger, any, any>, TLogger extends Logger = Logger> {
/**
* This level will be used to log operations via the logging method of the specified key (default: `info`)
*/
logLevel?: keyof LoggerLogFunctions<TLogger>
/***
* If provided, this logger will be used to log context creation failures as errors
*/
contextCreationFailureLogger?: Logger
/***
* If provided, will be bound to the plugin contextCreationDidFail hook, to log or otherwise react to context creation failures.
* You may provide this option in addition to the contextCreationFailureLogger, both will be called.
*/
contextCreationDidFail?: ApolloServerPlugin['contextCreationDidFail']
/***
* If provided, this function will be called to determine whether to ignore logging for a given response
*/
shouldIgnore?: (ctx: GraphQLRequestContextWillSendResponse<TContext>) => boolean
/**
* If true, introspection queries will not be logged (default: `true`)
*/
ignoreIntrospectionQueries?: boolean
/**
* If true, the response data will be logged for queries and mutations
*/
includeResponseData?: boolean
/**
* If true, the response data will be logged for mutations
*/
includeMutationResponseData?: boolean
/**
* Can be used to adjust the variables before logging, e.g. redacting sensitive data
*/
adjustVariables?: (variables: Record<string, any>) => Record<string, any>
/**
* Can be used to adjust the result data before logging, e.g. redacting sensitive data
*/
adjustResultData?: (data: Record<string, any>) => Record<string, any>
/**
* Can be used to augment the log entry with additional properties
*/
augmentLogEntry?: (ctx: TContext) => Record<string, any>
}
/**
* This plugin logs GraphQL operations and context creation failure (if specified via options).
* See options for more details.
*/
export function graphqlOperationLoggingPlugin<TContext extends GraphQLContext<TLogger, any, any>, TLogger extends Logger = Logger>({
logLevel = 'info',
contextCreationDidFail,
contextCreationFailureLogger,
shouldIgnore,
ignoreIntrospectionQueries = true,
includeResponseData,
includeMutationResponseData,
adjustVariables,
adjustResultData,
augmentLogEntry,
}: GraphQLOperationLoggingPluginOptions<TContext, TLogger> = {}): ApolloServerPlugin<TContext> {
return {
contextCreationDidFail: async ({ error }) => {
contextCreationFailureLogger?.error('Context creation failed', { error })
await contextCreationDidFail?.({ error })
},
requestDidStart: ({ contextValue }): Promise<GraphQLRequestListener<TContext>> => {
function log(
ctx: GraphQLRequestContextWillSendResponse<TContext>,
subsequentPayload?: GraphQLExperimentalFormattedSubsequentIncrementalExecutionResult,
) {
const { started, logger } = contextValue
const { operationName, query, variables } = ctx.request
const isIntrospection = query && isIntrospectionQuery(query)
if (isIntrospection && ignoreIntrospectionQueries) return
const type = ctx.operation?.operation
const result = ctx.response.body.kind === 'single' ? ctx.response.body.singleResult : ctx.response.body.initialResult
const errors = result.errors
const adjustedVariables = adjustVariables && variables ? adjustVariables(variables) : variables
const data = subsequentPayload ? subsequentPayload.incremental : result.data
let adjustedData = includeResponseData
? data
: includeMutationResponseData && type === OperationTypeNode.MUTATION
? data
: undefined
if (adjustResultData && adjustedData) adjustedData = adjustResultData(adjustedData)
const adjustedResult = omitNil({ errors, data: adjustedData }) as Record<string, any>
const additionalLogEntryProperties = augmentLogEntry ? (omitNil(augmentLogEntry(contextValue)) as Record<string, any>) : undefined
logGraphQLOperation({
logger,
logLevel,
type,
operationName,
query,
started,
variables: adjustedVariables && Object.keys(adjustedVariables).length > 0 ? adjustedVariables : undefined,
result: Object.keys(adjustedResult).length ? adjustedResult : undefined,
isIntrospectionQuery: isIntrospection || undefined,
isIncrementalResponse: ctx.response.body.kind === 'incremental' || undefined,
isSubsequentPayload: !!subsequentPayload || undefined,
...additionalLogEntryProperties,
})
}
const responseListener: GraphQLRequestListener<TContext> = {
willSendResponse(ctx: GraphQLRequestContextWillSendResponse<TContext>): Promise<void> {
if (shouldIgnore?.(ctx)) return Promise.resolve()
log(ctx)
return Promise.resolve()
},
willSendSubsequentPayload(ctx: GraphQLRequestContextWillSendResponse<TContext>, payload): Promise<void> {
if (shouldIgnore?.(ctx)) return Promise.resolve()
log(ctx, payload)
return Promise.resolve()
},
}
return Promise.resolve(responseListener)
},
}
}