-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #110 from MakerXStudio/support-subscription-operat…
…ion-logging-with-custom-log-level Support subscription operation logging with custom log level
- Loading branch information
Showing
5 changed files
with
125 additions
and
56 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,55 +1,136 @@ | ||
import { isLocalDev, Logger } from '@makerx/node-common' | ||
import { ExecutionArgs, GraphQLFormattedError, print } from 'graphql' | ||
import { ExecutionArgs, GraphQLFormattedError, OperationTypeNode, print } from 'graphql' | ||
import { ExecutionResult } from 'graphql-ws' | ||
import omitBy from 'lodash.omitby' | ||
import { isIntrospectionQuery, isNil } from './utils' | ||
import { GraphQLContext } from './context' | ||
import { isIntrospectionQuery, isNil } from './utils' | ||
|
||
interface GraphQLLogOperationInfo { | ||
started: number | ||
type LogFunction = Logger['info'] | ||
export type LoggerLogFunctions<T extends Logger> = { | ||
[Property in keyof T]: LogFunction | ||
} | ||
|
||
/** | ||
* Info for logging a GraphQL operation in a consistent format with the option of including any additional data. | ||
*/ | ||
export interface GraphQLLogOperationInfo<TLogger extends Logger = Logger> extends Record<string, unknown> { | ||
/** | ||
* The message to log, defaults to 'GraphQL operation'. | ||
*/ | ||
message?: string | ||
/** | ||
* The timestamp when the operation started, if supplied, the duration will be logged. | ||
*/ | ||
started?: number | ||
/** | ||
* The type of GraphQL operation. | ||
*/ | ||
type?: OperationTypeNode | null | ||
/** | ||
* The name of the GraphQL operation. | ||
*/ | ||
operationName?: string | null | ||
/** | ||
* The formatted GraphQL query. | ||
*/ | ||
query?: string | null | ||
/** | ||
* The GraphQL variables. | ||
*/ | ||
variables?: Record<string, unknown> | null | ||
result: { | ||
/** | ||
* The result of the GraphQL operation. | ||
* Generally, we don't log the data or extensions, just errors. | ||
*/ | ||
result?: { | ||
data?: Record<string, unknown> | null | ||
errors?: readonly GraphQLFormattedError[] | null | ||
extensions?: Record<string, unknown> | null | ||
hasNext?: boolean | ||
} | ||
logger: Logger | ||
/** | ||
* Whether the operation is an introspection query. | ||
*/ | ||
isIntrospectionQuery?: boolean | ||
/** | ||
* Whether the operation is part of an incremental response. | ||
*/ | ||
isIncrementalResponse?: boolean | ||
/** | ||
* Whether the operation is a subsequent payload of an incremental response. | ||
*/ | ||
isSubsequentPayload?: boolean | ||
/** | ||
* The logger to use. | ||
*/ | ||
logger: TLogger | ||
/** | ||
* The logger function to use, defaults to 'info'. | ||
*/ | ||
logLevel?: keyof LoggerLogFunctions<TLogger> | ||
} | ||
|
||
export const logGraphQLOperation = ({ started, operationName, query, variables, result: { errors }, logger }: GraphQLLogOperationInfo) => { | ||
/** | ||
* Logs a GraphQL operation in a consistent format with the option of including any additional data. | ||
* Top level and result entries with null or undefined values will be omitted. | ||
*/ | ||
export const logGraphQLOperation = <TLogger extends Logger = Logger>({ | ||
message = 'GraphQL operation', | ||
started, | ||
type, | ||
operationName, | ||
query, | ||
variables, | ||
result, | ||
logger, | ||
logLevel = 'info', | ||
...rest | ||
}: GraphQLLogOperationInfo<TLogger>) => { | ||
const isIntrospection = query && isIntrospectionQuery(query) | ||
if (isLocalDev && isIntrospection) return | ||
logger.info( | ||
'GraphQL operation', | ||
logger[logLevel as keyof Logger]( | ||
message, | ||
omitBy( | ||
{ | ||
type, | ||
operationName, | ||
query: isIntrospection ? 'IntrospectionQuery' : query, | ||
query, | ||
variables: variables && Object.keys(variables).length > 0 ? variables : undefined, | ||
duration: Date.now() - started, | ||
errors, | ||
duration: started ? Date.now() - started : undefined, | ||
result: result ? omitBy(result, isNil) : undefined, | ||
isIntrospectionQuery: isIntrospection || undefined, | ||
...omitBy(rest, isNil), | ||
}, | ||
isNil, | ||
), | ||
) | ||
} | ||
|
||
/** | ||
* Logs `operationName`, `query` and `variables` params from the GraphQL `ExecutionArgs`. | ||
* If `args.contextValue` has a `logger` property, it will be used, otherwise the `logger` param will be used. | ||
*/ | ||
export const logGraphQLExecutionArgs = (args: ExecutionArgs, message: string, logger?: Logger) => { | ||
export const logSubscriptionOperation = <TLogger extends Logger = Logger>({ | ||
args, | ||
result, | ||
message, | ||
logLevel, | ||
}: { | ||
args: ExecutionArgs | ||
result?: ExecutionResult | ||
message?: string | ||
logLevel?: keyof LoggerLogFunctions<TLogger> | ||
}) => { | ||
const logger = (args.contextValue as GraphQLContext).logger as TLogger | ||
if (!logger) return | ||
|
||
const { operationName, variableValues, document } = args | ||
const contextLogger = (args.contextValue as Partial<GraphQLContext>).logger ?? logger | ||
contextLogger?.info( | ||
const { data, ...resultWithoutData } = result ?? {} | ||
|
||
logGraphQLOperation({ | ||
message, | ||
omitBy( | ||
{ | ||
operationName, | ||
query: print(document), | ||
variables: variableValues, | ||
}, | ||
isNil, | ||
), | ||
) | ||
type: OperationTypeNode.SUBSCRIPTION, | ||
operationName, | ||
query: print(document), | ||
variables: variableValues, | ||
result: resultWithoutData, | ||
logger, | ||
logLevel, | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters